diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4f75c95 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.git +.gitignore +*.md +dist diff --git a/.gitignore b/.gitignore index 8a1dac4..0675b30 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ dist-ssr .env* !.env.example +docker-compose.*.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ed88577 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM node:20-slim AS base +WORKDIR /app + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +FROM base AS frontend +COPY package.json pnpm-lock.yaml ./ +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile + +COPY . . +RUN pnpm run build + +FROM base AS backend-deps +COPY backend/package.json backend/pnpm-lock.yaml ./ +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install -prod --frozen-lockfile +COPY backend . + +FROM oven/bun:alpine AS backend +WORKDIR /app +COPY --from=backend-deps /app . +RUN bun run build + +FROM oven/bun:alpine +WORKDIR /app + +ENV NODE_ENV=production +ENV DIST_ROOT=./dist + +COPY --from=frontend /app/dist /app/dist +COPY --from=backend /app/dist /app + +ENTRYPOINT [ "bun" , "run", "main.js" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..302d07b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Khairul Hidayat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 74872fd..6be8727 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,122 @@ -# React + TypeScript + Vite +# Garage Web UI -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +[![image](misc/img/garage-webui.png)](misc/img/garage-webui.png) -Currently, two official plugins are available: +A simple admin web UI for [Garage](https://garagehq.deuxfleurs.fr/), a self-hosted, S3-compatible, distributed object storage service. -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +[ [Screenshots](misc/SCREENSHOTS.md) | [Install Garage](https://garagehq.deuxfleurs.fr/documentation/quick-start/) | [Garage Git](https://git.deuxfleurs.fr/Deuxfleurs/garage) ] -## Expanding the ESLint configuration +## Installation -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +The Garage Web UI is available as a Docker image. You can install it using the command line or with Docker Compose. -- Configure the top-level `parserOptions` property like this: +### Docker CLI -```js -export default tseslint.config({ - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) +```sh +$ docker run -p 3909:3909 -v ./garage.toml:/etc/garage.toml --restart unless-stopped --name garage-webui khairul169/garage-webui:latest ``` -- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` -- Optionally add `...tseslint.configs.stylisticTypeChecked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: +### Docker Compose -```js -// eslint.config.js -import react from 'eslint-plugin-react' +If you install Garage using Docker, you can install this web UI alongside Garage as follows: -export default tseslint.config({ - // Set the react version - settings: { react: { version: '18.3' } }, - plugins: { - // Add the react plugin - react, - }, - rules: { - // other rules... - // Enable its recommended rules - ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, - }, -}) +```yml +services: + garage: + image: dxflrs/garage:v1.0.0 + container_name: garage + volumes: + - ./garage.toml:/etc/garage.toml + - ./meta:/var/lib/garage/meta + - ./data:/var/lib/garage/data + restart: unless-stopped + network_mode: host + + webui: + image: khairul169/garage-webui:latest + container_name: garage-webui + restart: unless-stopped + volumes: + - ./garage.toml:/etc/garage.toml + ports: + - 3909:3909 ``` + +### Configuration + +To simplify installation, the Garage Web UI uses values from the Garage configuration, such as `rpc_public_addr`, `admin.admin_token`, `s3_web.root_domain`, etc. + +Example content of `config.toml`: + +```toml +metadata_dir = "/var/lib/garage/meta" +data_dir = "/var/lib/garage/data" +db_engine = "sqlite" +metadata_auto_snapshot_interval = "6h" + +replication_factor = 3 +compression_level = 2 + +rpc_bind_addr = "[::]:3901" +rpc_public_addr = "localhost:3901" # Required +rpc_secret = "YOUR_RPC_SECRET_HERE" + +[s3_api] +s3_region = "garage" +api_bind_addr = "[::]:3900" +root_domain = ".s3.domain.com" + +[s3_web] # Optional, if you want to expose bucket as web +bind_addr = "[::]:3902" +root_domain = ".web.domain.com" +index = "index.html" + +[admin] # Required +api_bind_addr = "[::]:3903" +admin_token = "YOUR_ADMIN_TOKEN_HERE" +metrics_token = "YOUR_METRICS_TOKEN_HERE" +``` + +However, if it fails to load, you can set these environment variables instead: + +- `CONFIG_PATH`: Path to the Garage `config.toml` file. Defaults to `/etc/garage.toml`. +- `API_BASE_URL`: Garage admin API endpoint URL. +- `API_ADMIN_KEY`: Admin API key. + +### Running + +Once your instance of Garage Web UI is started, you can open the web UI at http://your-ip:3909. You can place it behind a reverse proxy to secure it with SSL. + +## Development + +This project is bootstrapped using TypeScript, Bun, React, and Hono. If you want to build it yourself or add additional features, follow these steps: + +### Setup + +```sh +$ git clone https://github.com/khairul169/garage-webui.git +$ cd garage-webui && pnpm install +$ cd backend && pnpm install && cd .. +``` + +### Running + +Start both the client and server concurrently: + +```sh +$ pnpm run dev # or npm run dev +``` + +Or start each instance separately: + +```sh +$ pnpm run dev:client +$ cd backend +$ pnpm run dev:server +``` + +## Troubleshooting + +Make sure you are using the latest version of Garage. If the data cannot be loaded, please check whether your instance of Garage has the admin API enabled and the ports are accessible. + +If you encounter any problems, please do not hesitate to submit an issue [here](https://github.com/khairul169/garage-webui/issues). You can describe the problem and attach the error logs. diff --git a/backend/lib/consts.ts b/backend/lib/consts.ts new file mode 100644 index 0000000..a031531 --- /dev/null +++ b/backend/lib/consts.ts @@ -0,0 +1,2 @@ +// +export const __PROD = import.meta.env.NODE_ENV === "production"; diff --git a/backend/lib/garage.ts b/backend/lib/garage.ts index 07a37a3..9d0befc 100644 --- a/backend/lib/garage.ts +++ b/backend/lib/garage.ts @@ -1,4 +1,16 @@ import type { Config } from "../types/garage"; import { readTomlFile } from "./utils"; -export const config = readTomlFile(process.env.CONFIG_PATH); +const CONFIG_PATH = process.env.CONFIG_PATH || "/etc/garage.toml"; + +export const config = await readTomlFile(CONFIG_PATH); + +if (!config?.rpc_public_addr) { + throw new Error( + "Cannot load garage config! Missing `rpc_public_addr` in config file." + ); +} + +if (!config?.admin?.admin_token) { + throw new Error("Missing `admin.admin_token` in config."); +} diff --git a/backend/lib/proxy-api.ts b/backend/lib/proxy-api.ts index 4da488d..c076fcc 100644 --- a/backend/lib/proxy-api.ts +++ b/backend/lib/proxy-api.ts @@ -3,7 +3,8 @@ import { API_ADMIN_KEY, API_BASE_URL } from "./api"; export const proxyApi = async (c: Context) => { const url = new URL(c.req.url); - const reqUrl = new URL(API_BASE_URL + url.pathname + url.search); + const pathname = url.pathname.replace(/\/api\//, "/"); + const reqUrl = new URL(API_BASE_URL + pathname + url.search); try { const headers = c.req.raw.headers; diff --git a/backend/lib/utils.ts b/backend/lib/utils.ts index df2a108..197068f 100644 --- a/backend/lib/utils.ts +++ b/backend/lib/utils.ts @@ -1,9 +1,14 @@ -import fs from "node:fs"; import toml from "toml"; -export const readTomlFile = (path?: string | null) => { - if (!path || !fs.existsSync(path)) { +export const readTomlFile = async (path?: string | null) => { + if (!path) { return undefined; } - return toml.parse(fs.readFileSync(path, "utf8")) as T; + + const file = Bun.file(path); + if (!(await file.exists())) { + return undefined; + } + + return toml.parse(await file.text()) as T; }; diff --git a/backend/main.ts b/backend/main.ts index 1239c6f..8ab447b 100644 --- a/backend/main.ts +++ b/backend/main.ts @@ -1,20 +1,34 @@ import { Hono } from "hono"; import { logger } from "hono/logger"; +import { serveStatic } from "hono/bun"; import router from "./routes"; -import { proxyApi } from "./lib/proxy-api"; +import { __PROD } from "./lib/consts"; const HOST = import.meta.env.HOST || "0.0.0.0"; const PORT = Number(import.meta.env.PORT) || 3909; +const DIST_ROOT = import.meta.env.DIST_ROOT || "./dist"; const app = new Hono(); app.use(logger()); // API router -app.route("/", router); +app.route("/api", router); -// Proxy to garage admin API -app.all("*", proxyApi); +if (__PROD) { + // Serve client dist + app.use(serveStatic({ root: DIST_ROOT })); + app.use(async (c, next) => { + try { + const file = Bun.file(DIST_ROOT + "/index.html"); + return c.html(await file.text()); + } catch (err) { + next(); + } + }); + + console.log(`Listening on http://${HOST}:${PORT}`); +} export default { fetch: app.fetch, diff --git a/backend/package.json b/backend/package.json index 718b63a..d49165e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -3,7 +3,9 @@ "module": "main.ts", "type": "module", "scripts": { - "dev": "bun --watch main.ts" + "dev": "bun --watch main.ts", + "build": "bun build main.ts --minify --sourcemap --outdir ./dist", + "start": "NODE_ENV=production bun run ./dist/main.js" }, "devDependencies": { "@types/bun": "latest" diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 9828792..cbfb90c 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: toml: specifier: ^3.0.0 version: 3.0.0 + typescript: + specifier: ^5.0.0 + version: 5.5.4 devDependencies: '@types/bun': specifier: latest @@ -40,6 +43,11 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -66,4 +74,6 @@ snapshots: toml@3.0.0: {} + typescript@5.5.4: {} + undici-types@5.26.5: {} diff --git a/backend/routes/index.ts b/backend/routes/index.ts index 7577e8d..aa187d4 100644 --- a/backend/routes/index.ts +++ b/backend/routes/index.ts @@ -1,10 +1,13 @@ import { Hono } from "hono"; import { buckets } from "./buckets"; import { configRoute } from "./config"; +import { proxyApi } from "../lib/proxy-api"; const router = new Hono() // .route("/config", configRoute) - .route("/buckets", buckets); + .route("/buckets", buckets) + + .all("*", proxyApi); export default router; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..13f7409 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +services: + garage: + image: dxflrs/garage:v1.0.0 + container_name: garage + volumes: + - ./garage.toml:/etc/garage.toml + - ./meta:/var/lib/garage/meta + - ./data:/var/lib/garage/data + restart: unless-stopped + network_mode: host + + webui: + image: khairul169/garage-webui:latest + container_name: garage-webui + restart: unless-stopped + environment: + - CONFIG_PATH=/etc/garage.toml + volumes: + - ./garage.toml:/etc/garage.toml + ports: + - 3909:3909 diff --git a/index.html b/index.html index 958f78d..fed9617 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,15 @@ - + - - Vite + React + TS + + + + + + + Garage Web UI
diff --git a/misc/SCREENSHOTS.md b/misc/SCREENSHOTS.md new file mode 100644 index 0000000..580e4c3 --- /dev/null +++ b/misc/SCREENSHOTS.md @@ -0,0 +1,8 @@ +# Garage Web UI Screenshots + +[![image](img/home.png)](img/home.png) +[![image](img/cluster.png)](img/cluster.png) +[![image](img/buckets.png)](img/buckets.png) +[![image](img/buckets-overview.png)](img/buckets-overview.png) +[![image](img/buckets-permissions.png)](img/buckets-permissions.png) +[![image](img/keys.png)](img/keys.png) diff --git a/misc/build.sh b/misc/build.sh new file mode 100755 index 0000000..5716288 --- /dev/null +++ b/misc/build.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +IMAGE_NAME="khairul169/garage-webui" +PACKAGE_VERSION=$(cat package.json | grep \"version\" | cut -d'"' -f 4) + +echo "Building version $PACKAGE_VERSION" + +docker buildx build --platform linux/amd64,linux/arm64 \ + -t "$IMAGE_NAME:latest" -t "$IMAGE_NAME:$PACKAGE_VERSION" --push . diff --git a/misc/img/buckets-overview.png b/misc/img/buckets-overview.png new file mode 100755 index 0000000..02bff06 Binary files /dev/null and b/misc/img/buckets-overview.png differ diff --git a/misc/img/buckets-permissions.png b/misc/img/buckets-permissions.png new file mode 100755 index 0000000..024ab45 Binary files /dev/null and b/misc/img/buckets-permissions.png differ diff --git a/misc/img/buckets.png b/misc/img/buckets.png new file mode 100755 index 0000000..5bee499 Binary files /dev/null and b/misc/img/buckets.png differ diff --git a/misc/img/cluster.png b/misc/img/cluster.png new file mode 100755 index 0000000..93e2956 Binary files /dev/null and b/misc/img/cluster.png differ diff --git a/misc/img/garage-webui.png b/misc/img/garage-webui.png new file mode 100755 index 0000000..bb10eba Binary files /dev/null and b/misc/img/garage-webui.png differ diff --git a/misc/img/home.png b/misc/img/home.png new file mode 100755 index 0000000..4a8efdc Binary files /dev/null and b/misc/img/home.png differ diff --git a/misc/img/keys.png b/misc/img/keys.png new file mode 100755 index 0000000..7fa132f Binary files /dev/null and b/misc/img/keys.png differ diff --git a/package.json b/package.json index b4e1e06..8bca875 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,15 @@ { "name": "garage-webui", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev:client": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "dev:server": "cd backend && npm run dev", + "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"" }, "dependencies": { "@hookform/resolvers": "^3.9.0", @@ -32,6 +34,7 @@ "@types/react-dom": "^18.3.0", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.20", + "concurrently": "^8.2.2", "daisyui": "^4.12.10", "eslint": "^9.8.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ab163f..a6f3db3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.41) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 daisyui: specifier: ^4.12.10 version: 4.12.10(postcss@8.4.41) @@ -772,6 +775,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -796,6 +803,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -826,6 +838,10 @@ packages: resolution: {integrity: sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==} engines: {node: '>=16.9.0'} + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + debug@4.3.6: resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} @@ -992,6 +1008,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1140,6 +1160,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1380,6 +1403,10 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1400,6 +1427,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -1416,6 +1446,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -1438,6 +1471,9 @@ packages: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} + spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1474,6 +1510,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -1504,6 +1544,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -1513,6 +1557,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1608,6 +1655,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -1617,6 +1668,14 @@ packages: engines: {node: '>= 14'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2260,6 +2319,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.1.1: {} color-convert@1.9.3: @@ -2278,6 +2343,18 @@ snapshots: concat-map@0.0.1: {} + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.1 + shell-quote: 1.8.1 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + convert-source-map@1.9.0: {} cosmiconfig@7.1.0: @@ -2314,6 +2391,10 @@ snapshots: transitivePeerDependencies: - postcss + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.25.0 + debug@4.3.6: dependencies: ms: 2.1.2 @@ -2507,6 +2588,8 @@ snapshots: function-bind@1.1.2: {} + get-caller-file@2.0.5: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2631,6 +2714,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -2853,6 +2938,8 @@ snapshots: regenerator-runtime@0.14.1: {} + require-directory@2.1.1: {} + resolve-from@4.0.0: {} resolve@1.22.8: @@ -2889,6 +2976,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rxjs@7.8.1: + dependencies: + tslib: 2.6.3 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -2901,6 +2992,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.1: {} + signal-exit@4.1.0: {} slash@3.0.0: {} @@ -2914,6 +3007,8 @@ snapshots: source-map@0.5.7: {} + spawn-command@0.0.2: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -2956,6 +3051,10 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} tailwind-merge@2.5.2: {} @@ -3003,12 +3102,16 @@ snapshots: dependencies: is-number: 7.0.0 + tree-kill@1.2.2: {} + ts-api-utils@1.3.0(typescript@5.5.4): dependencies: typescript: 5.5.4 ts-interface-checker@0.1.13: {} + tslib@2.6.3: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -3077,10 +3180,24 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + y18n@5.0.8: {} + yaml@1.10.2: {} yaml@2.5.0: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} zod@3.23.8: {} diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100755 index 0000000..87c115b Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100755 index 0000000..f573161 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100755 index 0000000..c68ea1f Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100755 index 0000000..acdbc41 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100755 index 0000000..67d863b Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100755 index 0000000..3dbc485 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100755 index 0000000..45dc8a2 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/garage-logo.svg b/src/assets/garage-logo.svg new file mode 100755 index 0000000..a7b5bbc --- /dev/null +++ b/src/assets/garage-logo.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/containers/sidebar.tsx b/src/components/containers/sidebar.tsx index 84983e3..7b03f40 100644 --- a/src/components/containers/sidebar.tsx +++ b/src/components/containers/sidebar.tsx @@ -11,6 +11,7 @@ import { Link, useLocation } from "react-router-dom"; import Button from "../ui/button"; import { themes } from "@/app/themes"; import appStore from "@/stores/app-store"; +import garageLogo from "@/assets/garage-logo.svg"; const pages = [ { icon: LayoutDashboard, title: "Dashboard", path: "/", exact: true }, @@ -26,7 +27,7 @@ const Sidebar = () => {