From 378727ac576914f012c2283527f551457528bc40 Mon Sep 17 00:00:00 2001 From: NanamiAdmin Date: Tue, 8 Sep 2026 23:15:50 +0800 Subject: [PATCH] feat(frontend): implement basic frontend interface --- frontend/.gitignore | 4 + frontend/README.md | 62 + frontend/index.html | 20 + frontend/package-lock.json | 1707 +++++++++++++++++++++ frontend/package.json | 22 + frontend/src/App.vue | 34 + frontend/src/api/index.js | 20 + frontend/src/components/HeadersEditor.vue | 91 ++ frontend/src/components/Modal.vue | 32 + frontend/src/components/SideBar.vue | 141 ++ frontend/src/components/TagsEditor.vue | 118 ++ frontend/src/components/Toggle.vue | 29 + frontend/src/components/TopBar.vue | 180 +++ frontend/src/main.js | 11 + frontend/src/router/index.js | 60 + frontend/src/styles/theme.css | 95 ++ frontend/src/styles/ui.css | 845 ++++++++++ frontend/src/utils/auth.js | 39 + frontend/src/utils/fmt.js | 43 + frontend/src/utils/http.js | 37 + frontend/src/utils/theme.js | 32 + frontend/src/utils/toast.js | 39 + frontend/src/views/Layout.vue | 38 + frontend/src/views/Login.vue | 242 +++ frontend/src/views/Logs.vue | 272 ++++ frontend/src/views/Overview.vue | 309 ++++ frontend/src/views/Settings.vue | 350 +++++ frontend/src/views/Trusted.vue | 298 ++++ frontend/vite.config.js | 20 + 29 files changed, 5190 insertions(+) create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/api/index.js create mode 100644 frontend/src/components/HeadersEditor.vue create mode 100644 frontend/src/components/Modal.vue create mode 100644 frontend/src/components/SideBar.vue create mode 100644 frontend/src/components/TagsEditor.vue create mode 100644 frontend/src/components/Toggle.vue create mode 100644 frontend/src/components/TopBar.vue create mode 100644 frontend/src/main.js create mode 100644 frontend/src/router/index.js create mode 100644 frontend/src/styles/theme.css create mode 100644 frontend/src/styles/ui.css create mode 100644 frontend/src/utils/auth.js create mode 100644 frontend/src/utils/fmt.js create mode 100644 frontend/src/utils/http.js create mode 100644 frontend/src/utils/theme.js create mode 100644 frontend/src/utils/toast.js create mode 100644 frontend/src/views/Layout.vue create mode 100644 frontend/src/views/Login.vue create mode 100644 frontend/src/views/Logs.vue create mode 100644 frontend/src/views/Overview.vue create mode 100644 frontend/src/views/Settings.vue create mode 100644 frontend/src/views/Trusted.vue create mode 100644 frontend/vite.config.js diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..b518b92 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.local +.DS_Store diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..7a45b6e --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,62 @@ +# Nukumizu Console + +Web console for the Nukumizu backend — a remote-server monitor and alert bot. Built with Vue 3 + Vite, no UI framework; styling is hand-rolled on CSS custom properties with a light/dark theme. + +## Features + +- **Sign in / first-run admin** — login, or create the very first admin while the database is still empty (the backend rejects further registrations once any user exists). +- **Nodes (overview)** — live server cards from the backend status/info APIs: online state, CPU / RAM / disk load, plus the per-node *status notify* switch persisted to `bot_node_config.json`. Searchable, auto-refreshes. +- **Bot trust** — manage QQ (NapCat) and Telegram admins & trusted groups in `bot_user_config.json`: add / remove members and toggle each member's `status notify`, `startup notify`, and `command replies`. +- **Settings** — structured forms for the global `config.json` (system, debug, Komari dashboard, controller methods, message templates, storage paths). Each card saves only its own section. +- **System logs** — live WebSocket log stream (`/api/system/getLogs`) with severity filter chips, search, pause/resume and export. + +## Development + +```bash +npm install +npm run dev # http://localhost:5173 +``` + +The dev server proxies `/api` (and the log websocket) to the backend. By default it targets `http://127.0.0.1:8080`; override with: + +```bash +# Windows PowerShell +$env:NUKUMIZU_API = "http://192.168.20.4:8080"; npm run dev +``` + +Production build (static assets only): + +```bash +npm run build # outputs dist/ +npm run preview +``` + +The backend does not serve static files, so put `dist/` behind any static server and proxy `/api` (and `ws://…/api/system/getLogs`) to the Nukumizu backend. + +## API contract notes + +- Auth uses two headers on every request: `X-Token` (from login) and `X-Timestamp` — a **Unix timestamp in seconds** (not milliseconds) with a ±30 min tolerance (skipped when `system.debugMode` is on). +- Token & user are kept in `localStorage`. On a `401` the session is cleared and you are returned to the login page. +- The panel talks to `/api/server/getStatus`, `/api/server/getInfo`, `/api/settings/get`, `/api/settings/set` and `/api/user/login|register`. All management endpoints require an `admin` token. +- `/api/settings/set` is a deep merge: sending a JSON `null` for a key removes it (used when deleting a trusted member). + +## Project structure + +``` +frontend/ +├── index.html +├── vite.config.js +└── src/ + ├── main.js / App.vue + ├── router/ # routes + auth guard + ├── api/index.js # authApi, serverApi, settingsApi + ├── utils/ # auth, http client, theme, toasts, formatting + ├── styles/ # theme.css (tokens) + ui.css (primitives) + ├── components/ # TopBar, SideBar, Modal, Toggle, TagsEditor, HeadersEditor + └── views/ # Login, Layout, Overview, Trusted, Settings, Logs +``` + +## Caveats + +- The backend's `/api/system/getLogs` websocket is currently unauthenticated — anyone who can reach the port can read logs. Consider gating it in a future backend change. +- Registering more than one user is intentionally impossible; the backend only accepts the very first registration. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..ed31a18 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,20 @@ + + + + + + + Nukumizu Console + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..45adb41 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1707 @@ +{ + "name": "nukumizu-console", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nukumizu-console", + "version": "0.1.0", + "dependencies": { + "@fortawesome/fontawesome-free": "^6.7.2", + "axios": "^1.7.9", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", + "integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==", + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "engines": { + "node": ">=6" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", + "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.42", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", + "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", + "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.42", + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-ssr": "3.5.42", + "@vue/shared": "3.5.42", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", + "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", + "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", + "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", + "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/runtime-core": "3.5.42", + "@vue/shared": "3.5.42", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", + "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", + "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", + "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-sfc": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/server-renderer": "3.5.42", + "@vue/shared": "3.5.42" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..aa7da80 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "nukumizu-console", + "private": true, + "version": "0.1.0", + "description": "Web console for Nukumizu — remote server monitor & alert bot", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.7.2", + "axios": "^1.7.9", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.0.7" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..3135605 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,34 @@ + + + diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 0000000..ec26120 --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,20 @@ +import http from '../utils/http.js'; + +export const authApi = { + login: (username, password) => http.post('/user/login', { username, password }), + register: (username, password) => http.post('/user/register', { username, password }) +}; + +// /api/server/getStatus?uuid=all → { success, "": { uuid, name, online, report } } +export const serverApi = { + statusAll: () => http.get('/server/getStatus?uuid=all'), + infoAll: () => http.get('/server/getInfo?uuid=all') +}; + +// /api/settings/get?type=… / /api/settings/set?type=… +// `type` is one of global | bot_user_config | bot_node_config. +// For set, pass a partial object; a JSON null value removes that key. +export const settingsApi = { + get: (type) => http.get(`/settings/get?type=${encodeURIComponent(type)}`), + set: (type, patch) => http.post(`/settings/set?type=${encodeURIComponent(type)}`, patch) +}; diff --git a/frontend/src/components/HeadersEditor.vue b/frontend/src/components/HeadersEditor.vue new file mode 100644 index 0000000..16b873d --- /dev/null +++ b/frontend/src/components/HeadersEditor.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/frontend/src/components/Modal.vue b/frontend/src/components/Modal.vue new file mode 100644 index 0000000..46fce49 --- /dev/null +++ b/frontend/src/components/Modal.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/src/components/SideBar.vue b/frontend/src/components/SideBar.vue new file mode 100644 index 0000000..7fbd921 --- /dev/null +++ b/frontend/src/components/SideBar.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/frontend/src/components/TagsEditor.vue b/frontend/src/components/TagsEditor.vue new file mode 100644 index 0000000..c2ccd01 --- /dev/null +++ b/frontend/src/components/TagsEditor.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/frontend/src/components/Toggle.vue b/frontend/src/components/Toggle.vue new file mode 100644 index 0000000..56042a2 --- /dev/null +++ b/frontend/src/components/Toggle.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/components/TopBar.vue b/frontend/src/components/TopBar.vue new file mode 100644 index 0000000..83a480c --- /dev/null +++ b/frontend/src/components/TopBar.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..c34b4b8 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,11 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import router from './router/index.js'; +import { initTheme } from './utils/theme.js'; +import '@fortawesome/fontawesome-free/css/all.min.css'; +import './styles/theme.css'; +import './styles/ui.css'; + +initTheme(); + +createApp(App).use(router).mount('#app'); diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..5364c20 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,60 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import { isLoggedIn } from '../utils/auth.js'; + +const routes = [ + { + path: '/login', + name: 'Login', + component: () => import('../views/Login.vue'), + meta: { public: true } + }, + { + path: '/', + component: () => import('../views/Layout.vue'), + redirect: { name: 'Overview' }, + children: [ + { + path: 'overview', + name: 'Overview', + component: () => import('../views/Overview.vue'), + meta: { title: 'Nodes' } + }, + { + path: 'trusted', + name: 'Trusted', + component: () => import('../views/Trusted.vue'), + meta: { title: 'Bot trust' } + }, + { + path: 'settings', + name: 'Settings', + component: () => import('../views/Settings.vue'), + meta: { title: 'Settings' } + }, + { + path: 'logs', + name: 'Logs', + component: () => import('../views/Logs.vue'), + meta: { title: 'Logs' } + } + ] + }, + { path: '/:pathMatch(.*)*', redirect: '/' } +]; + +const router = createRouter({ + history: createWebHistory(), + routes +}); + +router.beforeEach((to) => { + if (!to.meta.public && !isLoggedIn()) { + return { name: 'Login', query: { redirect: to.fullPath } }; + } + if (to.name === 'Login' && isLoggedIn()) { + return { name: 'Overview' }; + } + return true; +}); + +export default router; diff --git a/frontend/src/styles/theme.css b/frontend/src/styles/theme.css new file mode 100644 index 0000000..463e86a --- /dev/null +++ b/frontend/src/styles/theme.css @@ -0,0 +1,95 @@ +/* Nukumizu Console — design tokens. Light is the default, dark is applied by + setting data-theme="dark" on . The look is a restrained, Apple-school + console: neutral warm-gray fields, one accent reserved for interaction, and + color used only where it carries meaning (health, log severity). */ + +:root { + --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace; + + --bg: #eef0f3; + --bg-grad: linear-gradient(180deg, #f4f5f7 0%, #eaedf1 100%); + --surface: #ffffff; + --surface-2: #fafbfc; + --surface-3: #eef0f3; + --surface-hover: #e7eaee; + + --text: #1b1e24; + --text-2: rgba(27, 30, 36, 0.68); + --text-3: rgba(27, 30, 36, 0.46); + + --line: rgba(20, 24, 32, 0.09); + --line-strong: rgba(20, 24, 32, 0.16); + + --accent: #006ee0; + --accent-strong: #0059b8; + --accent-soft: rgba(0, 110, 224, 0.09); + --accent-contrast: #ffffff; + + --ok: #1f9d58; + --ok-soft: rgba(31, 157, 88, 0.12); + --warn: #c07b06; + --warn-soft: rgba(192, 123, 6, 0.13); + --bad: #d73a49; + --bad-soft: rgba(215, 58, 73, 0.1); + --muted: #8a919c; + + --log-debug: #188a4b; + --log-info: #0a62d0; + --log-warn: #b5720a; + --log-error: #cc3340; + --log-fatal: #a31b28; + + --r-s: 7px; + --r-m: 11px; + --r-l: 16px; + --r-pill: 999px; + + --shadow-1: 0 1px 2px rgba(16, 22, 30, 0.05); + --shadow-2: 0 1px 1px rgba(16, 22, 30, 0.04), 0 6px 24px -8px rgba(16, 22, 30, 0.14); + --shadow-3: 0 2px 4px rgba(16, 22, 30, 0.06), 0 18px 48px -12px rgba(16, 22, 30, 0.22); + + --nav-bg: rgba(15, 17, 21, 0.86); + --nav-text: rgba(255, 255, 255, 0.72); + --nav-text-hover: #ffffff; + --nav-line: rgba(255, 255, 255, 0.08); +} + +html[data-theme="dark"] { + --bg: #0b0c10; + --bg-grad: linear-gradient(180deg, #0e1014 0%, #090a0e 100%); + --surface: #15171d; + --surface-2: #1b1e25; + --surface-3: #24272f; + --surface-hover: #2b2f38; + + --text: #eceef2; + --text-2: rgba(236, 238, 242, 0.66); + --text-3: rgba(236, 238, 242, 0.44); + + --line: rgba(255, 255, 255, 0.08); + --line-strong: rgba(255, 255, 255, 0.14); + + --accent: #2f8dff; + --accent-strong: #57a3ff; + --accent-soft: rgba(47, 141, 255, 0.16); + + --ok: #3ecf83; + --ok-soft: rgba(62, 207, 131, 0.16); + --warn: #e3a23a; + --warn-soft: rgba(227, 162, 58, 0.16); + --bad: #ff6b76; + --bad-soft: rgba(255, 107, 118, 0.15); + --muted: #6b7280; + + --log-debug: #43cf86; + --log-info: #4b9dff; + --log-warn: #efb458; + --log-error: #ff7b84; + --log-fatal: #ff525e; + + --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-2: 0 1px 1px rgba(0, 0, 0, 0.4), 0 8px 28px -10px rgba(0, 0, 0, 0.6); + --shadow-3: 0 2px 4px rgba(0, 0, 0, 0.4), 0 22px 56px -16px rgba(0, 0, 0, 0.7); +} diff --git a/frontend/src/styles/ui.css b/frontend/src/styles/ui.css new file mode 100644 index 0000000..a706623 --- /dev/null +++ b/frontend/src/styles/ui.css @@ -0,0 +1,845 @@ +/* Nukumizu Console — shared UI primitives. Components may add scoped styles, + but everything reusable lives here. */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100%; +} + +body { + font-family: var(--font-ui); + font-size: 14px; + line-height: 1.5; + letter-spacing: -0.01em; + color: var(--text); + background: var(--bg-grad) fixed; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + transition: background 0.25s ease, color 0.25s ease; +} + +#app { + min-height: 100vh; +} + +::selection { + background: var(--accent-soft); +} + +a { + color: var(--accent); + text-decoration: none; +} + +button { + font: inherit; + color: inherit; + background: none; + border: 0; + cursor: pointer; +} + +input, select, textarea { + font: inherit; + color: inherit; +} + +h1, h2, h3, h4 { + font-family: var(--font-display); + font-weight: 600; + letter-spacing: -0.02em; + color: var(--text); +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--line-strong); + border-radius: 999px; + border: 3px solid transparent; + background-clip: content-box; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--muted); + background-clip: content-box; + border: 3px solid transparent; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +/* --- Page scaffolding --------------------------------------------------- */ + +.page { + padding: 34px 40px 60px; + max-width: 1180px; + margin: 0 auto; +} + +.page-head { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 20px; + margin-bottom: 26px; +} + +.page-head .eyebrow { + font-size: 12px; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 6px; +} + +.page-head h1 { + font-size: 30px; + line-height: 1.12; +} + +.page-head .lead { + margin-top: 6px; + color: var(--text-2); + font-size: 14px; + max-width: 620px; +} + +.head-actions { + display: flex; + gap: 10px; + align-items: center; + flex: none; +} + +.section-title { + font-size: 13px; + font-weight: 600; + letter-spacing: 0.02em; + color: var(--text-2); + margin: 26px 0 12px; +} + +.mono { + font-family: var(--font-mono); + font-size: 0.92em; +} + +/* --- Buttons ------------------------------------------------------------ */ + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; + padding: 8px 15px; + border-radius: var(--r-m); + font-size: 14px; + font-weight: 500; + line-height: 1.2; + border: 1px solid transparent; + background: var(--surface-3); + color: var(--text); + transition: background 0.15s ease, border-color 0.15s ease, transform 0.08s ease, opacity 0.15s ease, box-shadow 0.15s ease; + white-space: nowrap; + user-select: none; +} + +.btn:hover { + background: var(--surface-hover); +} + +.btn:active { + transform: translateY(1px); +} + +.btn:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.btn-primary { + background: var(--accent); + color: var(--accent-contrast); +} + +.btn-primary:hover { + background: var(--accent-strong); +} + +.btn-danger { + background: var(--bad-soft); + color: var(--bad); +} + +.btn-danger:hover { + background: color-mix(in srgb, var(--bad) 16%, transparent); +} + +.btn-ghost { + background: transparent; + color: var(--text-2); + border-color: var(--line); +} + +.btn-ghost:hover { + background: var(--surface-3); + color: var(--text); +} + +.btn-sm { + padding: 5px 11px; + font-size: 13px; + border-radius: var(--r-s); +} + +.btn-icon { + width: 34px; + height: 34px; + padding: 0; + border-radius: 50%; +} + +.icon-btn { + width: 34px; + height: 34px; + display: inline-grid; + place-items: center; + border-radius: 50%; + color: var(--text-2); + font-size: 15px; + transition: background 0.15s ease, color 0.15s ease; +} + +.icon-btn:hover { + background: var(--surface-3); + color: var(--text); +} + +.icon-btn.danger:hover { + background: var(--bad-soft); + color: var(--bad); +} + +/* --- Cards -------------------------------------------------------------- */ + +.card { + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--r-l); + box-shadow: var(--shadow-1); +} + +.card + .card { + margin-top: 18px; +} + +.card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 16px 20px; + border-bottom: 1px solid var(--line); +} + +.card-head h3 { + font-size: 16px; +} + +.card-head .hint { + color: var(--text-3); + font-size: 12.5px; + margin-top: 2px; +} + +.card-body { + padding: 20px; +} + +/* --- Forms -------------------------------------------------------------- */ + +.field { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.field > label { + font-size: 12.5px; + font-weight: 600; + letter-spacing: 0.01em; + color: var(--text-2); +} + +.field > label .required { + color: var(--bad); + margin-left: 2px; +} + +.field .help { + font-size: 12px; + color: var(--text-3); +} + +.input, .select, .textarea { + width: 100%; + padding: 9px 12px; + background: var(--surface-2); + border: 1px solid var(--line-strong); + border-radius: var(--r-s); + color: var(--text); + transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; +} + +.input:hover, .select:hover, .textarea:hover { + border-color: var(--muted); +} + +.input:focus, .select:focus, .textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); + background: var(--surface); +} + +.textarea { + resize: vertical; + min-height: 84px; + line-height: 1.55; + font-family: var(--font-mono); + font-size: 13px; + tab-size: 4; +} + +.select { + appearance: none; + background-image: linear-gradient(45deg, transparent 50%, var(--text-2) 50%), + linear-gradient(135deg, var(--text-2) 50%, transparent 50%); + background-position: calc(100% - 17px) 55%, calc(100% - 12px) 55%; + background-size: 5px 5px; + background-repeat: no-repeat; + padding-right: 30px; + cursor: pointer; +} + +.field input[type="color"] { + padding: 2px; + height: 38px; +} + +.form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px 20px; +} + +.form-grid .span-2 { + grid-column: span 2; +} + +.form-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 8px; +} + +/* --- Switch ------------------------------------------------------------- */ + +.switch { + position: relative; + display: inline-flex; + align-items: center; + gap: 10px; + cursor: pointer; +} + +.switch input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.switch .track { + width: 40px; + height: 24px; + border-radius: 999px; + background: var(--surface-3); + border: 1px solid var(--line-strong); + position: relative; + transition: background 0.18s ease, border-color 0.18s ease; + flex: none; +} + +.switch .track::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #fff; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); + transition: transform 0.18s ease; +} + +.switch input:checked + .track { + background: var(--accent); + border-color: var(--accent); +} + +.switch input:checked + .track::after { + transform: translateX(16px); +} + +.switch input:focus-visible + .track { + box-shadow: 0 0 0 3px var(--accent-soft); +} + +.switch .switch-label { + font-size: 13px; + color: var(--text-2); +} + +.switch input:checked ~ .switch-label { + color: var(--text); +} + +/* --- Badges / dots ------------------------------------------------------- */ + +.badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 10px; + border-radius: var(--r-pill); + font-size: 12px; + font-weight: 600; + line-height: 1.4; + white-space: nowrap; +} + +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex: none; + background: var(--muted); +} + +.badge-online, .b-online .dot { background: var(--ok-soft); color: var(--ok); } +.badge-online .dot { background: var(--ok); } +.badge-offline, .b-offline .dot { background: var(--surface-3); color: var(--text-3); } +.badge-offline .dot { background: var(--muted); } +.badge-warn { background: var(--warn-soft); color: var(--warn); } +.badge-danger { background: var(--bad-soft); color: var(--bad); } +.badge-accent { background: var(--accent-soft); color: var(--accent); } + +/* --- Stat / metric ------------------------------------------------------- */ + +.metric { + display: flex; + flex-direction: column; + gap: 2px; +} + +.metric .m-label { + font-size: 12px; + color: var(--text-3); + font-weight: 500; +} + +.metric .m-value { + font-size: 17px; + font-weight: 600; + letter-spacing: -0.02em; + font-variant-numeric: tabular-nums; +} + +.metric .m-value small { + font-size: 12px; + font-weight: 500; + color: var(--text-3); +} + +/* --- Meter (soft progress) ----------------------------------------------- */ + +.meter { + height: 5px; + border-radius: 999px; + background: var(--surface-3); + overflow: hidden; +} + +.meter > i { + display: block; + height: 100%; + border-radius: 999px; + background: var(--accent); + transition: width 0.3s ease; +} + +.meter > i.warn { background: var(--warn); } +.meter > i.bad { background: var(--bad); } +.meter > i.ok { background: var(--ok); } + +/* --- Server list --------------------------------------------------------- */ + +.grid-2 { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.grid-3 { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; +} + +@media (max-width: 980px) { + .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .form-grid { grid-template-columns: 1fr; } +} + +@media (max-width: 720px) { + .grid-2, .grid-3 { grid-template-columns: 1fr; } +} + +.server-card { + display: flex; + flex-direction: column; + gap: 14px; + padding: 18px 18px 16px; + cursor: default; +} + +.server-card .row-1 { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.server-card .row-2 { + display: flex; + justify-content: space-between; + gap: 10px; +} + +.server-card .sv-name { + font-size: 16px; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.2; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.server-card .sv-uuid { + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--text-3); + margin-top: 3px; + overflow: hidden; + text-overflow: ellipsis; +} + +.server-card .row-3 { + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: 12px; +} + +/* --- Members / options list ---------------------------------------------- */ + +.opt-list { + display: flex; + flex-direction: column; +} + +.opt-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 12px 14px; + border-bottom: 1px solid var(--line); +} + +.opt-row:last-child { + border-bottom: 0; +} + +.opt-row .who { + display: flex; + flex-direction: column; + min-width: 0; +} + +.opt-row .who .id { + font-family: var(--font-mono); + font-size: 14px; + font-weight: 600; +} + +.opt-row .who .sub { + font-size: 12px; + color: var(--text-3); + margin-top: 2px; +} + +.opt-row .toggles { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; +} + +.chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 2px 9px; + border-radius: var(--r-pill); + font-size: 11.5px; + font-weight: 600; + background: var(--surface-3); + color: var(--text-2); + letter-spacing: 0.02em; + text-transform: uppercase; +} + +/* --- Empty / skeleton ----------------------------------------------------- */ + +.empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 48px 20px; + color: var(--text-3); + text-align: center; +} + +.empty .e-icon { + font-size: 30px; + opacity: 0.5; +} + +/* --- Toolbar --------------------------------------------------------------- */ + +.toolbar { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.toolbar .spacer { + flex: 1; +} + +.search { + position: relative; + flex: 1; + min-width: 200px; + max-width: 340px; +} + +.search .icon { + position: absolute; + left: 11px; + top: 50%; + transform: translateY(-50%); + color: var(--text-3); + pointer-events: none; + font-size: 13px; +} + +.search input { + width: 100%; + padding: 8px 12px 8px 32px; + background: var(--surface-2); + border: 1px solid var(--line-strong); + border-radius: var(--r-pill); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.search input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); +} + +/* --- Modal ------------------------------------------------------------------ */ + +.modal-mask { + position: fixed; + inset: 0; + z-index: 90; + display: flex; + align-items: flex-start; + justify-content: center; + padding: 10vh 20px 40px; + background: rgba(12, 14, 18, 0.5); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + animation: fade-in 0.18s ease; + overflow-y: auto; +} + +.modal { + width: 100%; + max-width: 480px; + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--r-l); + box-shadow: var(--shadow-3); + animation: rise 0.22s cubic-bezier(0.2, 0.9, 0.3, 1); +} + +.modal.wide { max-width: 640px; } + +.modal-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 20px 14px; +} + +.modal-head h3 { font-size: 17px; } + +.modal-body { + padding: 4px 20px 8px; +} + +.modal-foot { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 20px 18px; +} + +/* --- Toasts ------------------------------------------------------------------ */ + +.toasts { + position: fixed; + top: 18px; + left: 50%; + transform: translateX(-50%); + z-index: 120; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + pointer-events: none; + width: min(420px, calc(100vw - 32px)); +} + +.toast { + pointer-events: auto; + display: flex; + align-items: flex-start; + gap: 11px; + width: 100%; + padding: 12px 15px; + border-radius: var(--r-m); + background: var(--surface); + border: 1px solid var(--line); + box-shadow: var(--shadow-3); + font-size: 13.5px; + animation: toast-in 0.28s cubic-bezier(0.2, 0.9, 0.3, 1); +} + +.toast .t-icon { + flex: none; + width: 22px; + height: 22px; + border-radius: 50%; + display: grid; + place-items: center; + font-size: 12px; + color: #fff; + background: var(--muted); +} + +.toast.success .t-icon { background: var(--ok); } +.toast.error .t-icon { background: var(--bad); } +.toast.info .t-icon { background: var(--accent); } +.toast.warn .t-icon { background: var(--warn); } + +.toast .t-msg { + flex: 1; + line-height: 1.45; + padding-top: 2px; + word-break: break-word; +} + +.toast.leaving { animation: toast-out 0.25s ease forwards; } + +/* --- Animations -------------------------------------------------------------- */ + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes rise { + from { opacity: 0; transform: translateY(14px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes toast-in { + from { opacity: 0; transform: translateY(-16px) scale(0.97); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + +@keyframes toast-out { + to { opacity: 0; transform: translateY(-14px) scale(0.97); } +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.spinner { + display: inline-block; + width: 18px; + height: 18px; + border: 2px solid var(--line-strong); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.7s linear infinite; +} + +/* route transition */ +.fade-switch-enter-active, .fade-switch-leave-active { + transition: opacity 0.16s ease, transform 0.16s ease; +} + +.fade-switch-enter-from { + opacity: 0; + transform: translateY(6px); +} + +.fade-switch-leave-to { + opacity: 0; +} diff --git a/frontend/src/utils/auth.js b/frontend/src/utils/auth.js new file mode 100644 index 0000000..3ff8365 --- /dev/null +++ b/frontend/src/utils/auth.js @@ -0,0 +1,39 @@ +import { reactive } from 'vue'; + +const TOKEN_KEY = 'nukumizu_token'; +const USER_KEY = 'nukumizu_user'; + +function readUser() { + try { + return JSON.parse(localStorage.getItem(USER_KEY)) || null; + } catch { + return null; + } +} + +export const auth = reactive({ + token: localStorage.getItem(TOKEN_KEY) || '', + user: readUser() +}); + +export function setSession(token, user) { + auth.token = token; + auth.user = user; + localStorage.setItem(TOKEN_KEY, token); + localStorage.setItem(USER_KEY, JSON.stringify(user)); +} + +export function clearSession() { + auth.token = ''; + auth.user = null; + localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(USER_KEY); +} + +export function getToken() { + return auth.token; +} + +export function isLoggedIn() { + return !!auth.token; +} diff --git a/frontend/src/utils/fmt.js b/frontend/src/utils/fmt.js new file mode 100644 index 0000000..74400b2 --- /dev/null +++ b/frontend/src/utils/fmt.js @@ -0,0 +1,43 @@ +// Severity levels as sent by the backend log websocket (see postLog package): +// 0=DEBUG 1=INFO 2=WARN 3=ERROR 4=FATAL +export const LOG_LEVELS = [ + { key: 'DEBUG', value: 0 }, + { key: 'INFO', value: 1 }, + { key: 'WARN', value: 2 }, + { key: 'ERROR', value: 3 }, + { key: 'FATAL', value: 4 } +]; + +export const levelOf = (value) => LOG_LEVELS.find((l) => l.value === value) || { key: 'LOG', value }; + +export function formatBytes(n, digits = 1) { + if (n === null || n === undefined || Number.isNaN(n)) return '-'; + if (n < 1024) return `${Math.round(n)} B`; + const units = ['KB', 'MB', 'GB', 'TB', 'PB']; + let v = n; + let u = -1; + do { + v /= 1024; + u += 1; + } while (v >= 1024 && u < units.length - 1); + return `${v.toFixed(digits)} ${units[u]}`; +} + +export function formatPercent(used, total) { + if (!total) return '—'; + return `${Math.min(100, Math.max(0, Math.round((used / total) * 100)))}%`; +} + +export function shortUUID(uuid = '') { + if (!uuid) return ''; + if (uuid.length <= 13) return uuid; + return `${uuid.slice(0, 8)}…${uuid.slice(-4)}`; +} + +// Online/offline counts + aggregate online ratio. Report arrays carry the +// report; when absent the server has no live telemetry yet. +export function sumUp(entries) { + const total = entries.length; + const online = entries.filter((e) => e.online).length; + return { total, online, offline: total - online }; +} diff --git a/frontend/src/utils/http.js b/frontend/src/utils/http.js new file mode 100644 index 0000000..20a7e8d --- /dev/null +++ b/frontend/src/utils/http.js @@ -0,0 +1,37 @@ +import axios from 'axios'; +import { getToken, clearSession } from './auth.js'; + +const http = axios.create({ + baseURL: '/api', + timeout: 15000 +}); + +http.interceptors.request.use((config) => { + const token = getToken(); + if (token) { + config.headers['X-Token'] = token; + } + // Backend expects a Unix timestamp in *seconds* with a ±30 min tolerance. + config.headers['X-Timestamp'] = Math.floor(Date.now() / 1000); + return config; +}); + +http.interceptors.response.use( + (response) => response.data, + (error) => { + const status = error.response ? error.response.status : 0; + const payload = error.response && error.response.data; + const message = (payload && payload.message) || error.message || 'Network error'; + + if (status === 401) { + clearSession(); + window.dispatchEvent(new CustomEvent('auth:expired')); + } + + const err = new Error(message); + err.status = status; + return Promise.reject(err); + } +); + +export default http; diff --git a/frontend/src/utils/theme.js b/frontend/src/utils/theme.js new file mode 100644 index 0000000..4db4822 --- /dev/null +++ b/frontend/src/utils/theme.js @@ -0,0 +1,32 @@ +const KEY = 'nukumizu_theme'; + +function systemPrefersDark() { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +} + +export function applyTheme(theme) { + const dark = theme === 'dark' || (theme !== 'light' && systemPrefersDark()); + document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); + return dark ? 'dark' : 'light'; +} + +export function currentTheme() { + return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'; +} + +export function initTheme() { + const saved = localStorage.getItem(KEY); + applyTheme(saved || 'system'); +} + +export function setTheme(theme) { + localStorage.setItem(KEY, theme); + return applyTheme(theme); +} + +export function toggleTheme() { + const next = currentTheme() === 'dark' ? 'light' : 'dark'; + localStorage.setItem(KEY, next); + applyTheme(next); + return next; +} diff --git a/frontend/src/utils/toast.js b/frontend/src/utils/toast.js new file mode 100644 index 0000000..e349e26 --- /dev/null +++ b/frontend/src/utils/toast.js @@ -0,0 +1,39 @@ +import { reactive } from 'vue'; + +let seq = 0; + +export const toasts = reactive([]); + +function dismiss(id) { + const idx = toasts.findIndex((t) => t.id === id); + if (idx !== -1) toasts[idx].leaving = true; + setTimeout(() => { + const i = toasts.findIndex((t) => t.id === id); + if (i !== -1) toasts.splice(i, 1); + }, 240); +} + +function push(type, message, timeout) { + const id = ++seq; + toasts.push({ id, type, message, leaving: false }); + if (timeout > 0) { + setTimeout(() => dismiss(id), timeout); + } + return id; +} + +export const toast = { + success(message, timeout = 3600) { + return push('success', message, timeout); + }, + error(message, timeout = 5200) { + return push('error', message, timeout); + }, + info(message, timeout = 3200) { + return push('info', message, timeout); + }, + warn(message, timeout = 4200) { + return push('warn', message, timeout); + }, + dismiss +}; diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue new file mode 100644 index 0000000..2ab080c --- /dev/null +++ b/frontend/src/views/Layout.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue new file mode 100644 index 0000000..e9d60ae --- /dev/null +++ b/frontend/src/views/Login.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/frontend/src/views/Logs.vue b/frontend/src/views/Logs.vue new file mode 100644 index 0000000..1dfca51 --- /dev/null +++ b/frontend/src/views/Logs.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/frontend/src/views/Overview.vue b/frontend/src/views/Overview.vue new file mode 100644 index 0000000..cf0c21c --- /dev/null +++ b/frontend/src/views/Overview.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue new file mode 100644 index 0000000..5df4a02 --- /dev/null +++ b/frontend/src/views/Settings.vue @@ -0,0 +1,350 @@ + + +