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 @@
+
+
+
+
+
+
+ {{ glyph[t.type] || 'i' }}
+ {{ t.message }}
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
{{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ label }}
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+ Nukumizu
+ Console
+
+
+
+
+
+
+
+
+
+
{{ initial }}
+
+ {{ user.username || '—' }}
+ {{ user.level || 'admin' }}
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
Nukumizu
+
Remote server monitoring & alert bot — admin console
+
+
+
+
+ Sign in
+ Create admin
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
Telemetry
+
System logs
+
Live log stream from the server. Newest entries appear first.
+
+
+
+ {{ statusText.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
No matching log entries.
+
+
+
+ {{ m.timestamp }}
+
+ {{ levelOf(m.level).key }}
+
+ {{ m.content }}
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
Overview
+
Nodes
+
Monitored servers with live load, online state, and the per-node status-notify switch.
+
+
+
+
+
+
+
+
+
+
+
+
+
Cannot reach the backend service
+
Retry
+
+
+
+
+
+
+
+
+
No monitored nodes yet
+
+
+
+
+
No nodes match “{{ query }}”
+
+
+
+
+
+
+
+
{{ e.name }}
+
+ {{ shortUUID(e.uuid) }}
+
+
+
+
+ {{ e.online ? 'Online' : 'Offline' }}
+
+
+
+
+
+
CPU
+
+
{{ e.report.cpu.usage.toFixed(1) }}%
+
+
+
RAM
+
+
{{ formatBytes(e.report.ram.used) }} / {{ formatBytes(e.report.ram.total) }}
+
+
+
Disk
+
+
{{ formatBytes(e.report.disk.used) }} / {{ formatBytes(e.report.disk.total) }}
+
+
+
+
+ Online, waiting for the first status report…
+
+
+
+ {{ e.report && e.report.message ? e.report.message : 'Currently offline' }}
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
Configuration
+
System settings
+
Edit the global config.json . Every card saves only its own section — other fields stay untouched.
+
+
+ Reload
+
+
+
+
+
+
Failed to load settings
+
Retry
+
+
+
+
+
+
+
+
+
{{ s.title }}
+
{{ s.hint }}
+
+
+
+ Save
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/Trusted.vue b/frontend/src/views/Trusted.vue
new file mode 100644
index 0000000..5790e2c
--- /dev/null
+++ b/frontend/src/views/Trusted.vue
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
Bot trust
+
Trust management
+
Which admins and groups each bot channel answers to, plus per-member notification and reply switches.
+
+
+
+ Reload
+
+
+
+
+
+
+
Failed to load, please check the backend connection
+
Retry
+
+
+
+
+
+
{{ c.label }}
+
{{ c.hint }}
+
+
+ {{ summary[c.key].admins }} admins · {{ summary[c.key].trustedGroups }} groups
+
+
+
+
+
+
+ {{ k.label }}
+
+ Add
+
+
+
+
+
+ No {{ k.label.toLowerCase() }} yet
+
+
+
+
+ {{ r.id }}
+
+
+
+ {{ o.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+ Add
+
+
+
+
+
+ Remove {{ removing.id }} from
+ {{ KINDS.find((k) => k.key === removing.kind)?.label }} ?
+
+
+ Cancel
+ Remove
+
+
+
+
+
+
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 0000000..7fdf2b4
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,20 @@
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+
+// Backend the dev server proxies /api (and the log websocket) to.
+const BACKEND = process.env.NUKUMIZU_API || 'http://127.0.0.1:8080';
+
+export default defineConfig({
+ plugins: [vue()],
+ server: {
+ host: '0.0.0.0',
+ port: 5173,
+ proxy: {
+ '/api': {
+ target: BACKEND,
+ changeOrigin: true,
+ ws: true
+ }
+ }
+ }
+});