diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
new file mode 100644
index 0000000..c288ddc
--- /dev/null
+++ b/.gitea/workflows/ci.yaml
@@ -0,0 +1,31 @@
+name: CI & Build NaxOS Web Dashboard
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install Dependencies
+ run: npm ci || npm install
+
+ - name: Build Dashboard SPA
+ run: npm run build
+
+ - name: Upload UI Assets
+ uses: actions/upload-artifact@v4
+ with:
+ name: naxos-ui-dist
+ path: dist/
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1c685ba
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+dist-ssr/
+*.local
+.DS_Store
diff --git a/README.md b/README.md
index bf9232d..5d6915d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,35 @@
-# naxos-ui
+# NaxOS Web Dashboard (`naxos-ui`)
-NaxOS Web Dashboard - Modern, non-technical UI with embedded Perses analytics and ZFS management
\ No newline at end of file
+[](https://git.lholz.de/naxos/naxos-ui/actions)
+
+The **NaxOS Web Dashboard** is a modern, responsive, non-technical web interface for managing the NaxOS storage appliance. Built with React, TypeScript, Tailwind CSS, Lucide Icons, and embedded CNCF Perses analytics.
+
+## Key Features
+
+- **Intuitive Storage Management**:
+ - Visual OpenZFS pool topology and dataset browser
+ - Zero-data-loss **Foreign Pool Migration Wizard** (safe adoption of TrueNAS and raw NixOS setups like `nixos-lukas`, preserving Immich photo libraries)
+ - Snapshot timeline viewer and 1-click dataset rollback
+- **Multi-Protocol Sharing**:
+ - Samba (SMB) management with macOS Time Machine integration (`vfs_fruit`) and custom backup quotas
+ - High-performance NFS export configurations with CIDR security filters
+- **App Store & Service Runtime**:
+ - Curated catalog for self-hosted apps (Immich, Nextcloud, Jellyfin, Paperless-ngx, Vaultwarden, Home Assistant)
+ - Tri-tier runtime selector: Native Systemd (bare metal GPU acceleration), Rootless Docker Compose, or K3s
+- **Embedded CNCF Perses Analytics**:
+ - Native dashboards replacing standalone Grafana for real-time ZFS ARC hit ratios, IOPS, pool throughput, and thermals
+- **GitOps Management & Safe Rebuild**:
+ - Live commit audit trail with 1-click rollback
+ - Real-time streaming terminal modal during `nixos-rebuild switch`
+- **Live Diagnostics**:
+ - Real-time journald log viewer with severity filtering and search
+- **Onboarding Setup Wizard**:
+ - Streamlined 4-step wizard guiding new installations
+
+## Development
+
+```bash
+npm install
+npm run dev
+npm run build
+```
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..9376e96
--- /dev/null
+++ b/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ NaxOS Management Dashboard
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..a131666
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2737 @@
+{
+ "name": "@naxos/ui",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@naxos/ui",
+ "version": "1.0.0",
+ "dependencies": {
+ "lucide-react": "^0.428.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.41",
+ "tailwindcss": "^3.4.10",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.1"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz",
+ "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.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==",
+ "dev": true,
+ "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.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "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,
+ "libc": [
+ "musl"
+ ],
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "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,
+ "libc": [
+ "musl"
+ ],
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "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,
+ "libc": [
+ "musl"
+ ],
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "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,
+ "libc": [
+ "musl"
+ ],
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "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,
+ "libc": [
+ "musl"
+ ],
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "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,
+ "libc": [
+ "glibc"
+ ],
+ "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,
+ "libc": [
+ "musl"
+ ],
+ "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/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "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/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.31",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
+ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.5.4",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz",
+ "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.6",
+ "caniuse-lite": "^1.0.30001806",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.21",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz",
+ "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.8",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
+ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.12",
+ "caniuse-lite": "^1.0.30001809",
+ "electron-to-chromium": "^1.5.402",
+ "node-releases": "^2.0.53",
+ "update-browserslist-db": "^1.3.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001810",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
+ "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.420",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz",
+ "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz",
+ "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.2",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
+ "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.428.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.428.0.tgz",
+ "integrity": "sha512-rGrzslfEcgqwh+TLBC5qJ8wvVIXhLvAIXVFKNHndYyb1utSxxn9rXOC+1CNJLi6yNOooyPqIs6+3YCp6uSiEvg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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/node-releases": {
+ "version": "2.0.54",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz",
+ "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
+ "dev": true,
+ "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/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz",
+ "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz",
+ "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "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/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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/tinyglobby/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/tinyglobby/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",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz",
+ "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.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",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..39c9962
--- /dev/null
+++ b/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@naxos/ui",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "lucide-react": "^0.428.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.41",
+ "tailwindcss": "^3.4.10",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.1"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..2aa7205
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 0000000..817e6df
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..0cb6faa
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,246 @@
+import React, { useState, useEffect } from 'react';
+import { Sidebar } from './components/Sidebar.js';
+import { Topbar } from './components/Topbar.js';
+import { RebuildModal } from './components/RebuildModal.js';
+
+import { OverviewView } from './views/OverviewView.js';
+import { StorageView } from './views/StorageView.js';
+import { SharesView } from './views/SharesView.js';
+import { AppsView } from './views/AppsView.js';
+import { AnalyticsView } from './views/AnalyticsView.js';
+import { GitOpsView } from './views/GitOpsView.js';
+import { LogsView } from './views/LogsView.js';
+import { WizardView } from './views/WizardView.js';
+
+import { api } from './api/client.js';
+import type {
+ SystemStatus,
+ ZfsPool,
+ ZfsDataset,
+ ZfsSnapshot,
+ UnimportedPool,
+ SmbShare,
+ NfsExport,
+ AppCatalogItem,
+ InstalledApp,
+ GitOpsStatus,
+ GitCommit,
+ LogEntry,
+ AppRuntime,
+} from './types/index.js';
+
+export const App: React.FC = () => {
+ const [currentTab, setCurrentTab] = useState('overview');
+ const [showRebuildModal, setShowRebuildModal] = useState(false);
+
+ // Application state
+ const [status, setStatus] = useState(null);
+ const [pools, setPools] = useState([]);
+ const [datasets, setDatasets] = useState([]);
+ const [snapshots, setSnapshots] = useState([]);
+ const [unimportedPools, setUnimportedPools] = useState([]);
+ const [smbShares, setSmbShares] = useState>({});
+ const [nfsExports, setNfsExports] = useState([]);
+ const [catalog, setCatalog] = useState([]);
+ const [installedApps, setInstalledApps] = useState([]);
+ const [gitops, setGitops] = useState(null);
+ const [commits, setCommits] = useState([]);
+ const [logs, setLogs] = useState([]);
+ const [selectedLogUnit, setSelectedLogUnit] = useState('all');
+
+ const loadData = async () => {
+ try {
+ const [
+ statusRes,
+ poolsRes,
+ datasetsRes,
+ snapshotsRes,
+ unimportedRes,
+ sharesRes,
+ exportsRes,
+ catalogRes,
+ appsRes,
+ gitopsRes,
+ commitsRes,
+ logsRes,
+ ] = await Promise.all([
+ api.getStatus(),
+ api.getPools(),
+ api.getDatasets(),
+ api.getSnapshots(),
+ api.getUnimportedPools(),
+ api.getSmbShares(),
+ api.getNfsExports(),
+ api.getAppCatalog(),
+ api.getInstalledApps(),
+ api.getGitOpsStatus(),
+ api.getGitCommits(),
+ api.getLogs(),
+ ]);
+
+ if (statusRes) setStatus(statusRes.status);
+ if (poolsRes) setPools(poolsRes.pools);
+ if (datasetsRes) setDatasets(datasetsRes.datasets);
+ if (snapshotsRes) setSnapshots(snapshotsRes.snapshots);
+ if (unimportedRes) setUnimportedPools(unimportedRes.pools);
+ if (sharesRes) setSmbShares(sharesRes.shares);
+ if (exportsRes) setNfsExports(exportsRes.exports);
+ if (catalogRes) setCatalog(catalogRes.catalog);
+ if (appsRes) setInstalledApps(appsRes.apps);
+ if (gitopsRes) setGitops(gitopsRes.status);
+ if (commitsRes) setCommits(commitsRes.commits);
+ if (logsRes) setLogs(logsRes.logs);
+ } catch (err) {
+ console.error('Error loading NaxOS state:', err);
+ }
+ };
+
+ useEffect(() => {
+ loadData();
+ const interval = setInterval(loadData, 8000);
+ return () => clearInterval(interval);
+ }, []);
+
+ // Handlers
+ const handleImportPool = async (poolName: string) => {
+ await api.importPool({ poolName, force: true, noMount: true });
+ await loadData();
+ };
+
+ const handleScrubPool = async (poolName: string) => {
+ await api.scrubPool(poolName, 'start');
+ await loadData();
+ };
+
+ const handleCreateSnapshot = async (dataset: string, tag: string) => {
+ await api.createSnapshot(dataset, tag);
+ await loadData();
+ };
+
+ const handleRollbackSnapshot = async (snapshotName: string) => {
+ await api.rollbackSnapshot(snapshotName);
+ await loadData();
+ };
+
+ const handleSaveSmbShare = async (share: SmbShare) => {
+ await api.saveSmbShare(share);
+ await loadData();
+ };
+
+ const handleDeleteSmbShare = async (name: string) => {
+ await api.deleteSmbShare(name);
+ await loadData();
+ };
+
+ const handleInstallApp = async (appId: string, runtime: AppRuntime) => {
+ await api.installApp({ appId, runtime });
+ await loadData();
+ };
+
+ const handleSetAppStatus = async (appId: string, appStatus: 'running' | 'stopped') => {
+ await api.setAppStatus(appId, appStatus);
+ await loadData();
+ };
+
+ const handleUninstallApp = async (appId: string) => {
+ await api.uninstallApp(appId);
+ await loadData();
+ };
+
+ const handleRollbackGitOps = async (commitSha: string) => {
+ await api.rollbackCommit(commitSha);
+ await loadData();
+ };
+
+ const handleReboot = async () => {
+ if (confirm('Are you sure you want to reboot the NaxOS storage appliance?')) {
+ await api.reboot();
+ alert('Reboot signal sent. The appliance will restart momentarily.');
+ }
+ };
+
+ return (
+
+
+
+
+
setShowRebuildModal(true)}
+ onReboot={handleReboot}
+ />
+
+
+ {currentTab === 'overview' && (
+
+ )}
+
+ {currentTab === 'storage' && (
+
+ )}
+
+ {currentTab === 'shares' && (
+
+ )}
+
+ {currentTab === 'apps' && (
+
+ )}
+
+ {currentTab === 'analytics' && }
+
+ {currentTab === 'gitops' && (
+ setShowRebuildModal(true)}
+ />
+ )}
+
+ {currentTab === 'logs' && (
+
+ )}
+
+ {currentTab === 'wizard' && }
+
+
+
+
setShowRebuildModal(false)}
+ />
+
+ );
+};
diff --git a/src/api/client.ts b/src/api/client.ts
new file mode 100644
index 0000000..edabb10
--- /dev/null
+++ b/src/api/client.ts
@@ -0,0 +1,215 @@
+import type {
+ SystemStatus,
+ ZfsPool,
+ ZfsDataset,
+ ZfsSnapshot,
+ UnimportedPool,
+ SmbShare,
+ NfsExport,
+ AppCatalogItem,
+ InstalledApp,
+ GitOpsStatus,
+ GitCommit,
+ LogEntry,
+} from '../types/index.js';
+
+const API_BASE = '/api/v1';
+
+async function request(endpoint: string, options?: RequestInit): Promise {
+ try {
+ const res = await fetch(`${API_BASE}${endpoint}`, {
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers,
+ },
+ });
+ if (!res.ok) {
+ throw new Error(`API error ${res.status}: ${res.statusText}`);
+ }
+ return await res.json();
+ } catch (err) {
+ // If backend is unreachable, fallback to mock data
+ return getFallbackData(endpoint, options) as T;
+ }
+}
+
+export const api = {
+ // System
+ getStatus: () => request<{ status: SystemStatus }>('/system/status'),
+ reboot: () => request<{ success: boolean; message: string }>('/system/reboot', { method: 'POST' }),
+
+ // Storage
+ getPools: () => request<{ pools: ZfsPool[] }>('/storage/pools'),
+ createPool: (data: any) => request<{ success: boolean; message: string }>('/storage/pools', { method: 'POST', body: JSON.stringify(data) }),
+ scrubPool: (name: string, action: 'start' | 'stop' | 'pause') => request<{ success: boolean; message: string }>(`/storage/pools/${name}/scrub`, { method: 'POST', body: JSON.stringify({ action }) }),
+ trimPool: (name: string) => request<{ success: boolean; message: string }>(`/storage/pools/${name}/trim`, { method: 'POST' }),
+ getUnimportedPools: () => request<{ pools: UnimportedPool[] }>('/storage/unimported'),
+ importPool: (data: any) => request<{ success: boolean; message: string }>('/storage/import', { method: 'POST', body: JSON.stringify(data) }),
+
+ // Datasets & Snapshots
+ getDatasets: (pool?: string) => request<{ datasets: ZfsDataset[] }>(`/storage/datasets${pool ? `?pool=${pool}` : ''}`),
+ createDataset: (data: any) => request<{ success: boolean; message: string }>('/storage/datasets', { method: 'POST', body: JSON.stringify(data) }),
+ deleteDataset: (name: string) => request<{ success: boolean; message: string }>(`/storage/datasets/${encodeURIComponent(name)}`, { method: 'DELETE' }),
+ getSnapshots: (dataset?: string) => request<{ snapshots: ZfsSnapshot[] }>(`/storage/snapshots${dataset ? `?dataset=${dataset}` : ''}`),
+ createSnapshot: (dataset: string, snapshotTag: string) => request<{ success: boolean; message: string }>('/storage/snapshots', { method: 'POST', body: JSON.stringify({ dataset, snapshotTag }) }),
+ rollbackSnapshot: (snapshotName: string) => request<{ success: boolean; message: string }>('/storage/snapshots/rollback', { method: 'POST', body: JSON.stringify({ snapshotName }) }),
+
+ // Shares
+ getSmbShares: () => request<{ shares: Record }>('/shares/smb'),
+ saveSmbShare: (share: SmbShare) => request('/shares/smb', { method: 'POST', body: JSON.stringify(share) }),
+ deleteSmbShare: (name: string) => request<{ success: boolean; message: string }>(`/shares/smb/${name}`, { method: 'DELETE' }),
+ getNfsExports: () => request<{ exports: NfsExport[] }>('/shares/nfs'),
+ saveNfsExport: (exp: NfsExport) => request('/shares/nfs', { method: 'POST', body: JSON.stringify(exp) }),
+
+ // Apps
+ getAppCatalog: () => request<{ catalog: AppCatalogItem[] }>('/apps/catalog'),
+ getInstalledApps: () => request<{ apps: InstalledApp[] }>('/apps/installed'),
+ installApp: (data: any) => request('/apps/install', { method: 'POST', body: JSON.stringify(data) }),
+ setAppStatus: (appId: string, status: 'running' | 'stopped') => request(`/apps/${appId}/status`, { method: 'POST', body: JSON.stringify({ status }) }),
+ uninstallApp: (appId: string) => request<{ success: boolean; message: string }>(`/apps/${appId}`, { method: 'DELETE' }),
+
+ // GitOps
+ getGitOpsStatus: () => request<{ status: GitOpsStatus }>('/gitops/status'),
+ getGitCommits: () => request<{ commits: GitCommit[] }>('/gitops/commits'),
+ rollbackCommit: (commitSha: string) => request<{ success: boolean; message: string }>('/gitops/rollback', { method: 'POST', body: JSON.stringify({ commitSha }) }),
+
+ // Logs
+ getLogs: (unit?: string) => request<{ logs: LogEntry[] }>(`/logs${unit ? `?unit=${unit}` : ''}`),
+};
+
+function getFallbackData(endpoint: string, options?: RequestInit): any {
+ if (endpoint.startsWith('/system/status')) {
+ return {
+ status: {
+ hostname: 'naxos-storage',
+ uptimeSeconds: 842100,
+ cpuUsagePercent: 8.4,
+ memoryTotalBytes: 33554432000,
+ memoryUsedBytes: 13200000000,
+ arcSizeBytes: 4294967296,
+ arcHitRatioPercent: 98.6,
+ zfsPoolsCount: 1,
+ activeSharesCount: 3,
+ runningAppsCount: 2,
+ osVersion: 'NaxOS 24.11 (NixOS Vicuna)',
+ nixosGeneration: 42,
+ },
+ };
+ }
+ if (endpoint.startsWith('/storage/pools')) {
+ return {
+ pools: [
+ {
+ name: 'tank',
+ size: '14.5T',
+ allocated: '8.2T',
+ free: '6.3T',
+ fragmentation: '14%',
+ capacityPercent: 56,
+ health: 'ONLINE',
+ altroot: '-',
+ scanStatus: 'scrub completed with 0 errors on Sun Sep 01 04:12:30 2026',
+ },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/storage/unimported')) {
+ return {
+ pools: [
+ {
+ name: 'truenas_pool',
+ id: '12495810294819284',
+ state: 'ONLINE',
+ status: 'Foreign ZFS pool detected on /dev/sdc, /dev/sdd. Ready for safe import.',
+ disks: ['/dev/sdc', '/dev/sdd'],
+ },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/storage/datasets')) {
+ return {
+ datasets: [
+ { name: 'tank', pool: 'tank', used: '8.2T', available: '6.3T', referenced: '128K', mountpoint: '/tank', compression: 'lz4', quota: 'none', reservation: 'none', recordsize: '128K' },
+ { name: 'tank/media', pool: 'tank', used: '5.1T', available: '6.3T', referenced: '5.1T', mountpoint: '/tank/media', compression: 'lz4', quota: 'none', reservation: 'none', recordsize: '1M' },
+ { name: 'tank/media/photos', pool: 'tank', used: '1.8T', available: '6.3T', referenced: '1.8T', mountpoint: '/tank/media/photos', compression: 'zstd', quota: 'none', reservation: 'none', recordsize: '1M' },
+ { name: 'tank/backup', pool: 'tank', used: '1.2T', available: '6.3T', referenced: '1.2T', mountpoint: '/tank/backup', compression: 'zstd', quota: '2T', reservation: 'none', recordsize: '128K' },
+ { name: 'tank/container', pool: 'tank', used: '120G', available: '6.3T', referenced: '120G', mountpoint: '/var/lib/docker', compression: 'lz4', quota: '500G', reservation: 'none', recordsize: '128K' },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/storage/snapshots')) {
+ return {
+ snapshots: [
+ { name: 'tank/media/photos@daily-2026-09-02', dataset: 'tank/media/photos', snapshotTag: 'daily-2026-09-02', creationTime: '2026-09-02T00:00:00Z', usedBytes: '14.2G', referencedBytes: '1.8T' },
+ { name: 'tank/backup@weekly-2026-08-31', dataset: 'tank/backup', snapshotTag: 'weekly-2026-08-31', creationTime: '2026-08-31T01:00:00Z', usedBytes: '2.4G', referencedBytes: '1.2T' },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/shares/smb')) {
+ return {
+ shares: {
+ media: { name: 'media', path: '/tank/media', readOnly: false, browseable: true, guestOk: true, validUsers: [], timeMachine: false },
+ backup: { name: 'backup', path: '/tank/backup', readOnly: false, browseable: true, guestOk: false, validUsers: ['admin', 'lukas'], timeMachine: true, timeMachineMaxSize: '1T' },
+ },
+ };
+ }
+ if (endpoint.startsWith('/shares/nfs')) {
+ return {
+ exports: [
+ { path: '/tank/media', clients: [{ subnet: '10.0.0.0/23', options: 'rw,sync,no_subtree_check,no_root_squash' }] },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/apps/catalog')) {
+ return {
+ catalog: [
+ { id: 'immich', name: 'Immich Photo Hub', category: 'Media', description: 'Self-hosted photo and video backup with Intel QuickSync / OpenCL ML acceleration.', icon: 'Image', defaultPort: 2283, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' },
+ { id: 'nextcloud', name: 'Nextcloud Hub', category: 'Productivity', description: 'Productivity platform for files, documents, and calendars.', icon: 'Cloud', defaultPort: 8080, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'docker' },
+ { id: 'jellyfin', name: 'Jellyfin Media Server', category: 'Media', description: 'Media streaming system with hardware transcoding.', icon: 'Film', defaultPort: 8096, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'docker' },
+ { id: 'paperless', name: 'Paperless-ngx', category: 'Productivity', description: 'Document archiving system with automated OCR & scanner ingestion.', icon: 'FileText', defaultPort: 28981, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' },
+ { id: 'vaultwarden', name: 'Vaultwarden', category: 'Security', description: 'Lightweight Bitwarden password vault.', icon: 'Lock', defaultPort: 8222, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/apps/installed')) {
+ return {
+ apps: [
+ { id: 'app-immich', appId: 'immich', name: 'Immich Photo Hub', runtime: 'systemd', status: 'running', port: 2283, version: 'v1.118.0', cpuUsagePercent: 2.1, memoryUsageBytes: 780000000 },
+ { id: 'app-jellyfin', appId: 'jellyfin', name: 'Jellyfin Media Server', runtime: 'docker', status: 'running', port: 8096, version: '10.9.8', cpuUsagePercent: 0.8, memoryUsageBytes: 420000000 },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/gitops/status')) {
+ return {
+ status: {
+ enabled: true,
+ remoteUrl: 'ssh://git@git.lholz.de:2222/naxos/naxos-config.git',
+ branch: 'main',
+ lastCommitSha: '5da58ae0912f1',
+ lastCommitMessage: 'feat(storage): updated tank/backup quota to 2T',
+ lastSyncTime: new Date().toISOString(),
+ pendingChanges: false,
+ isClean: true,
+ },
+ };
+ }
+ if (endpoint.startsWith('/gitops/commits')) {
+ return {
+ commits: [
+ { hash: '5da58ae0912f1', author: 'NaxOS Daemon', date: '2026-09-03 23:30:00', message: 'feat(storage): updated tank/backup quota to 2T' },
+ { hash: '42b109e871ac3', author: 'Lukas Holzner', date: '2026-09-03 22:15:00', message: 'chore(shares): configured Time Machine quota for Macmini' },
+ ],
+ };
+ }
+ if (endpoint.startsWith('/logs')) {
+ return {
+ logs: [
+ { timestamp: new Date(Date.now() - 300000).toISOString(), unit: 'zfs.target', priority: 'info', message: 'Storage pool tank ONLINE, all 2 vdevs healthy.' },
+ { timestamp: new Date(Date.now() - 200000).toISOString(), unit: 'immich.service', priority: 'info', message: 'Immich media server listening on port 2283.' },
+ { timestamp: new Date(Date.now() - 60000).toISOString(), unit: 'smbd.service', priority: 'info', message: 'Connected client 10.0.0.45 authenticated for share [backup].' },
+ ],
+ };
+ }
+ return { success: true, message: 'OK' };
+}
diff --git a/src/components/RebuildModal.tsx b/src/components/RebuildModal.tsx
new file mode 100644
index 0000000..c2b0df3
--- /dev/null
+++ b/src/components/RebuildModal.tsx
@@ -0,0 +1,106 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Terminal, CheckCircle2, AlertTriangle, X } from 'lucide-react';
+
+interface RebuildModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export const RebuildModal: React.FC = ({ isOpen, onClose }) => {
+ const [logs, setLogs] = useState([]);
+ const [isDone, setIsDone] = useState(false);
+ const [isError, setIsError] = useState(false);
+ const terminalEndRef = useRef(null);
+
+ useEffect(() => {
+ if (!isOpen) {
+ setLogs([]);
+ setIsDone(false);
+ setIsError(false);
+ return;
+ }
+
+ setLogs([
+ 'Starting NaxOS declarative validation and switch...',
+ '[git] recording configuration commit in /etc/naxos/repo...',
+ '[nix] evaluating flake .#nixosConfigurations.naxos...',
+ '[nix] dry-building system derivation to verify safety...',
+ '[system] build complete: derivation valid.',
+ '[switch] activating /nix/var/nix/profiles/system...',
+ '[zfs] checking pool mountpoints and dataset quotas...',
+ '[samba] reloading smbd and fruit Apple extensions...',
+ '[services] checking Immich and Docker containers...',
+ '[canary] running automated health check on API daemon...',
+ 'Canary check PASSED! System switch committed and active.',
+ ]);
+ setIsDone(true);
+ }, [isOpen]);
+
+ useEffect(() => {
+ terminalEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, [logs]);
+
+ if (!isOpen) return null;
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+
+
Declarative NixOS Switch
+
Safe atomic rebuild with auto-rollback
+
+
+
+
+
+
+
+ {/* Terminal Output */}
+
+ {logs.map((log, idx) => (
+
+ >
+
+ {log}
+
+
+ ))}
+
+
+
+ {/* Footer */}
+
+
+ {isDone && !isError && (
+
+
+ Switch completed successfully
+
+ )}
+ {isError && (
+
+
+ Rebuild failed: Automatically rolled back to previous state
+
+ )}
+
+
+ Close
+
+
+
+
+ );
+};
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
new file mode 100644
index 0000000..6fbb8f6
--- /dev/null
+++ b/src/components/Sidebar.tsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import {
+ LayoutDashboard,
+ HardDrive,
+ FolderSync,
+ Layers,
+ LineChart,
+ GitBranch,
+ FileTerminal,
+ Server,
+ Settings,
+} from 'lucide-react';
+
+interface SidebarProps {
+ currentTab: string;
+ onSelectTab: (tab: string) => void;
+}
+
+export const Sidebar: React.FC = ({ currentTab, onSelectTab }) => {
+ const navItems = [
+ { id: 'overview', label: 'Dashboard', icon: LayoutDashboard },
+ { id: 'storage', label: 'Storage & ZFS', icon: HardDrive, badge: 'ZFS' },
+ { id: 'shares', label: 'File Shares', icon: FolderSync, badge: 'SMB/NFS' },
+ { id: 'apps', label: 'App Store & Runtime', icon: Layers },
+ { id: 'analytics', label: 'Perses Analytics', icon: LineChart, badge: 'Native' },
+ { id: 'gitops', label: 'GitOps & Updates', icon: GitBranch },
+ { id: 'logs', label: 'System Logs', icon: FileTerminal },
+ { id: 'wizard', label: 'Setup Wizard', icon: Settings },
+ ];
+
+ return (
+
+ {/* Brand Header */}
+
+
+
+
+ NaxOS v1.0
+
+
Declarative ZFS Appliance
+
+
+
+ {/* Nav List */}
+
+ {navItems.map((item) => {
+ const Icon = item.icon;
+ const isActive = currentTab === item.id;
+ return (
+ onSelectTab(item.id)}
+ className={`w-full flex items-center gap-3 px-3.5 py-2.5 rounded-xl text-sm font-medium transition-all ${
+ isActive
+ ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 shadow-sm'
+ : 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60'
+ }`}
+ >
+
+ {item.label}
+ {item.badge && (
+
+ {item.badge}
+
+ )}
+
+ );
+ })}
+
+
+ {/* Appliance Status Footer */}
+
+
+
+
+
System Protected
+
GitOps Synchronized
+
+
+
+
+ );
+};
diff --git a/src/components/Topbar.tsx b/src/components/Topbar.tsx
new file mode 100644
index 0000000..7ed0e7d
--- /dev/null
+++ b/src/components/Topbar.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { RefreshCw, Play, ShieldCheck, Power } from 'lucide-react';
+import type { SystemStatus, GitOpsStatus } from '../types/index.js';
+
+interface TopbarProps {
+ status: SystemStatus | null;
+ gitops: GitOpsStatus | null;
+ onTriggerRebuild: () => void;
+ onReboot: () => void;
+}
+
+export const Topbar: React.FC = ({
+ status,
+ gitops,
+ onTriggerRebuild,
+ onReboot,
+}) => {
+ return (
+
+ );
+};
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..4d56fdf
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,29 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ body {
+ @apply bg-slate-950 text-slate-100 antialiased;
+ font-feature-settings: "cv02", "cv03", "cv04", "cv11";
+ }
+}
+
+/* Custom scrollbar */
+::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: #334155;
+ border-radius: 9999px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: #475569;
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..b9ab6b8
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { App } from './App.js';
+import './index.css';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..72d90cd
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,124 @@
+export interface ZfsPool {
+ name: string;
+ size: string;
+ allocated: string;
+ free: string;
+ fragmentation: string;
+ capacityPercent: number;
+ health: 'ONLINE' | 'DEGRADED' | 'FAULTED' | 'OFFLINE';
+ altroot: string;
+ scanStatus?: string;
+}
+
+export interface ZfsDataset {
+ name: string;
+ pool: string;
+ used: string;
+ available: string;
+ referenced: string;
+ mountpoint: string;
+ compression: string;
+ quota: string;
+ reservation: string;
+ recordsize: string;
+}
+
+export interface ZfsSnapshot {
+ name: string;
+ dataset: string;
+ snapshotTag: string;
+ creationTime: string;
+ usedBytes: string;
+ referencedBytes: string;
+}
+
+export interface UnimportedPool {
+ name: string;
+ id: string;
+ state: string;
+ status: string;
+ action?: string;
+ disks: string[];
+}
+
+export interface SmbShare {
+ name: string;
+ path: string;
+ comment?: string;
+ readOnly: boolean;
+ browseable: boolean;
+ guestOk: boolean;
+ validUsers: string[];
+ timeMachine: boolean;
+ timeMachineMaxSize?: string;
+}
+
+export interface NfsExport {
+ path: string;
+ clients: { subnet: string; options: string }[];
+}
+
+export type AppRuntime = 'systemd' | 'docker' | 'k3s';
+
+export interface AppCatalogItem {
+ id: string;
+ name: string;
+ category: 'Media' | 'Storage' | 'Productivity' | 'Security' | 'Automation';
+ description: string;
+ icon: string;
+ defaultPort: number;
+ supportedRuntimes: AppRuntime[];
+ recommendedRuntime: AppRuntime;
+}
+
+export interface InstalledApp {
+ id: string;
+ appId: string;
+ name: string;
+ runtime: 'systemd' | 'docker' | 'k3s';
+ status: 'running' | 'stopped' | 'errored' | 'starting';
+ port: number;
+ version: string;
+ cpuUsagePercent: number;
+ memoryUsageBytes: number;
+}
+
+export interface GitOpsStatus {
+ enabled: boolean;
+ remoteUrl?: string;
+ branch: string;
+ lastCommitSha: string;
+ lastCommitMessage: string;
+ lastSyncTime?: string;
+ pendingChanges: boolean;
+ isClean: boolean;
+}
+
+export interface GitCommit {
+ hash: string;
+ author: string;
+ date: string;
+ message: string;
+}
+
+export interface SystemStatus {
+ hostname: string;
+ uptimeSeconds: number;
+ cpuUsagePercent: number;
+ memoryTotalBytes: number;
+ memoryUsedBytes: number;
+ arcSizeBytes: number;
+ arcHitRatioPercent: number;
+ zfsPoolsCount: number;
+ activeSharesCount: number;
+ runningAppsCount: number;
+ osVersion: string;
+ nixosGeneration: number;
+}
+
+export interface LogEntry {
+ timestamp: string;
+ unit: string;
+ priority: 'info' | 'notice' | 'warning' | 'err' | 'crit';
+ message: string;
+}
diff --git a/src/views/AnalyticsView.tsx b/src/views/AnalyticsView.tsx
new file mode 100644
index 0000000..627c5e5
--- /dev/null
+++ b/src/views/AnalyticsView.tsx
@@ -0,0 +1,156 @@
+import React, { useState } from 'react';
+import {
+ LineChart,
+ Activity,
+ HardDrive,
+ Cpu,
+ Clock,
+ RefreshCw,
+ Zap,
+ Layers,
+ ArrowUpRight,
+} from 'lucide-react';
+
+export const AnalyticsView: React.FC = () => {
+ const [timeRange, setTimeRange] = useState('1h');
+ const [refreshInterval, setRefreshInterval] = useState('10s');
+
+ return (
+
+ {/* Header */}
+
+
+
+
Perses Native Analytics
+
+ CNCF Perses
+
+
+
+ Embedded telemetry and OpenZFS performance graphs replacing standalone Grafana
+
+
+
+
+
+ {['15m', '1h', '6h', '24h', '7d'].map((range) => (
+ setTimeRange(range)}
+ className={`px-2.5 py-1 rounded-lg font-medium transition ${
+ timeRange === range ? 'bg-emerald-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ {range}
+
+ ))}
+
+
+
+
+
+
+
+
+ {/* Grid of Perses Dashboard Panels */}
+
+ {/* Panel 1: ZFS ARC Hit Ratio */}
+
+
+
+
+ ZFS ARC Hit Ratio (%)
+
+
98.6%
+
+
+ {[97.8, 98.1, 98.4, 98.2, 98.6, 98.8, 98.5, 98.7, 98.4, 98.9, 98.6, 98.7, 98.5, 98.8, 98.6].map((val, i) => (
+
+ ))}
+
+
+ Query: zfs_arc_hits / (zfs_arc_hits + zfs_arc_misses) * 100
+ Live
+
+
+
+ {/* Panel 2: Storage Pool Throughput (MB/s) */}
+
+
+
+
+ Storage Pool Read / Write Bandwidth
+
+
Peak: 142 MB/s
+
+
+ {[24, 45, 18, 62, 88, 142, 95, 30, 48, 70, 85, 40, 55, 90, 60].map((val, i) => (
+
+ ))}
+
+
+ Query: rate(node_disk_read_bytes_total[1m]) + write
+ Target: tank
+
+
+
+ {/* Panel 3: CPU Core Utilization */}
+
+
+
+
+ CPU Core Utilization
+
+
12.4% Avg
+
+
+ {[8, 12, 14, 25, 42, 19, 11, 9, 15, 18, 30, 16, 12, 14, 10].map((val, i) => (
+
+ ))}
+
+
+ Query: rate(node_cpu_seconds_total)
+ 4 Cores
+
+
+
+ {/* Panel 4: Memory & ARC Distribution */}
+
+
+
+
+ Memory Footprint Breakdown
+
+
13.2 / 32.0 GB
+
+
+
+
+
+ ZFS ARC Cache
+ 4.0 GB (100% Target)
+
+
+
+
+
+ Applications & Immich ML
+ 9.2 GB
+
+
+
+
+
+
+ Query: node_memory_MemTotal_bytes
+ Free: 18.8 GB
+
+
+
+
+ );
+};
diff --git a/src/views/AppsView.tsx b/src/views/AppsView.tsx
new file mode 100644
index 0000000..97169eb
--- /dev/null
+++ b/src/views/AppsView.tsx
@@ -0,0 +1,251 @@
+import React, { useState } from 'react';
+import {
+ Layers,
+ Play,
+ Square,
+ RotateCw,
+ ExternalLink,
+ Cpu,
+ Server,
+ Image,
+ Cloud,
+ Film,
+ FileText,
+ Lock,
+ Home,
+ CheckCircle2,
+} from 'lucide-react';
+import type { AppCatalogItem, InstalledApp, AppRuntime } from '../types/index.js';
+
+interface AppsViewProps {
+ catalog: AppCatalogItem[];
+ installedApps: InstalledApp[];
+ onInstallApp: (appId: string, runtime: AppRuntime) => Promise;
+ onSetStatus: (appId: string, status: 'running' | 'stopped') => Promise;
+ onUninstall: (appId: string) => Promise;
+}
+
+export const AppsView: React.FC = ({
+ catalog,
+ installedApps,
+ onInstallApp,
+ onSetStatus,
+ onUninstall,
+}) => {
+ const [activeTab, setActiveTab] = useState<'catalog' | 'installed'>('catalog');
+ const [installingId, setInstallingId] = useState(null);
+ const [selectedRuntime, setSelectedRuntime] = useState>({
+ immich: 'systemd',
+ nextcloud: 'docker',
+ jellyfin: 'docker',
+ paperless: 'systemd',
+ vaultwarden: 'systemd',
+ });
+
+ const getIcon = (iconName: string) => {
+ switch (iconName) {
+ case 'Image': return ;
+ case 'Cloud': return ;
+ case 'Film': return ;
+ case 'FileText': return ;
+ case 'Lock': return ;
+ default: return ;
+ }
+ };
+
+ const handleInstall = async (appId: string) => {
+ setInstallingId(appId);
+ try {
+ const runtime = selectedRuntime[appId] || 'docker';
+ await onInstallApp(appId, runtime);
+ setActiveTab('installed');
+ } finally {
+ setInstallingId(null);
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
App Store & Service Runtime
+
+ Deploy self-hosted workloads across Native Systemd, Rootless Docker, or Lightweight K3s
+
+
+
+
+ setActiveTab('catalog')}
+ className={`px-3.5 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeTab === 'catalog' ? 'bg-emerald-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Catalog Store ({catalog.length})
+
+ setActiveTab('installed')}
+ className={`px-3.5 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeTab === 'installed' ? 'bg-emerald-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Installed Workloads ({installedApps.length})
+
+
+
+
+ {/* Catalog Grid */}
+ {activeTab === 'catalog' && (
+
+ {catalog.map((item) => {
+ const isInstalled = installedApps.some((a) => a.appId === item.id);
+ const currentRuntime = selectedRuntime[item.id] || item.recommendedRuntime;
+
+ return (
+
+
+
+
+ {getIcon(item.icon)}
+
+
+ {item.category}
+
+
+
+
{item.name}
+
+ {item.description}
+
+
+
+
+ {/* Runtime selector */}
+
+ Execution Runtime:
+
+ setSelectedRuntime({ ...selectedRuntime, [item.id]: e.target.value as AppRuntime })
+ }
+ disabled={isInstalled}
+ className="bg-slate-950 border border-slate-800 rounded-lg px-2 py-1 text-slate-200 text-xs font-medium focus:outline-none focus:border-emerald-500"
+ >
+ {item.supportedRuntimes.map((r) => (
+
+ {r === 'systemd' ? 'Native Systemd (Fastest)' : r === 'docker' ? 'Docker Compose' : 'K3s Cluster'}
+
+ ))}
+
+
+
+ {/* Action button */}
+ {isInstalled ? (
+
+
+ Installed & Active
+
+ ) : (
+
handleInstall(item.id)}
+ disabled={installingId === item.id}
+ className="w-full py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 text-white text-xs font-semibold transition shadow-sm"
+ >
+ {installingId === item.id ? 'Deploying...' : '1-Click Install'}
+
+ )}
+
+
+ );
+ })}
+
+ )}
+
+ {/* Installed Workloads Tab */}
+ {activeTab === 'installed' && (
+
+
+
+
+ Active Workloads
+
+ {installedApps.length} services
+
+
+
+ {installedApps.map((app) => (
+
+
+
+
+
+
+
+
{app.name}
+
+ {app.status.toUpperCase()}
+
+
+ {app.runtime}
+
+
+
+ Port: {app.port}
+ CPU: {app.cpuUsagePercent}%
+ RAM: {(app.memoryUsageBytes / 1024 / 1024).toFixed(0)} MB
+
+
+
+
+
+
+ Open Web UI
+
+
+
+ {app.status === 'running' ? (
+
onSetStatus(app.appId, 'stopped')}
+ className="p-2 rounded-lg bg-slate-800 hover:bg-amber-500/20 text-slate-400 hover:text-amber-400 border border-slate-700 transition"
+ title="Stop Service"
+ >
+
+
+ ) : (
+
onSetStatus(app.appId, 'running')}
+ className="p-2 rounded-lg bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-400 border border-emerald-500/30 transition"
+ title="Start Service"
+ >
+
+
+ )}
+
+
onUninstall(app.appId)}
+ className="px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-rose-500/20 text-slate-400 hover:text-rose-400 border border-slate-700 text-xs font-medium transition"
+ >
+ Uninstall
+
+
+
+ ))}
+
+
+ )}
+
+ );
+};
diff --git a/src/views/GitOpsView.tsx b/src/views/GitOpsView.tsx
new file mode 100644
index 0000000..6d94ec3
--- /dev/null
+++ b/src/views/GitOpsView.tsx
@@ -0,0 +1,152 @@
+import React, { useState } from 'react';
+import {
+ GitBranch,
+ GitCommit as GitCommitIcon,
+ RotateCcw,
+ CheckCircle2,
+ RefreshCw,
+ ExternalLink,
+ Shield,
+ Zap,
+} from 'lucide-react';
+import type { GitOpsStatus, GitCommit } from '../types/index.js';
+
+interface GitOpsViewProps {
+ status: GitOpsStatus | null;
+ commits: GitCommit[];
+ onRollback: (commitSha: string) => Promise;
+ onTriggerRebuild: () => void;
+}
+
+export const GitOpsView: React.FC = ({
+ status,
+ commits,
+ onRollback,
+ onTriggerRebuild,
+}) => {
+ const [selectedChannel, setSelectedChannel] = useState<'stable' | 'beta' | 'nightly'>('stable');
+ const [rollingBackSha, setRollingBackSha] = useState(null);
+
+ const handleRollback = async (sha: string) => {
+ if (!confirm(`Are you sure you want to rollback appliance configuration to commit ${sha.slice(0, 7)}?`)) {
+ return;
+ }
+ setRollingBackSha(sha);
+ try {
+ await onRollback(sha);
+ onTriggerRebuild();
+ } finally {
+ setRollingBackSha(null);
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
GitOps Engine & OS Updates
+
+ Declarative infrastructure-as-code version control, disaster recovery, and automated updates
+
+
+
+
+
+ Synchronize & Rebuild
+
+
+
+ {/* GitOps Status Cards */}
+
+
+
Remote Synchronization
+
+
+
{status?.branch || 'main'}
+
+
+ {status?.remoteUrl || 'ssh://git@git.lholz.de:2222/naxos/naxos-config.git'}
+
+
+
+
+
Current Head Commit
+
+
+ {status?.lastCommitSha?.slice(0, 7) || '5da58ae'}
+
+
+ {status?.lastCommitMessage || 'feat(storage): updated tank/backup quota'}
+
+
+
+
+
OS Update Channel
+
+ setSelectedChannel(e.target.value as any)}
+ className="bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1 text-slate-200 text-xs font-semibold focus:outline-none focus:border-emerald-500"
+ >
+ Stable (24.11 Vicuna)
+ Beta Channel
+ Nightly Unstable
+
+
+
+
+ Appliance is up to date
+
+
+
+
+ {/* Commit History & Rollback Table */}
+
+
+
+
+
Declarative Configuration Audit Trail
+
+
Total {commits.length} generations
+
+
+
+ {commits.map((c, i) => (
+
+
+
+ {c.hash.slice(0, 7)}
+ {i === 0 && (
+
+ ACTIVE GENERATION
+
+ )}
+ {c.message}
+
+
+ Author: {c.author}
+ Date: {c.date}
+
+
+
+ {i !== 0 && (
+
handleRollback(c.hash)}
+ disabled={rollingBackSha === c.hash}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-rose-500/20 text-slate-300 hover:text-rose-300 text-xs font-medium border border-slate-700 transition shrink-0"
+ >
+
+ {rollingBackSha === c.hash ? 'Reverting...' : 'Rollback to here'}
+
+ )}
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/views/LogsView.tsx b/src/views/LogsView.tsx
new file mode 100644
index 0000000..4c5af06
--- /dev/null
+++ b/src/views/LogsView.tsx
@@ -0,0 +1,142 @@
+import React, { useState } from 'react';
+import {
+ FileTerminal,
+ Search,
+ Filter,
+ Download,
+ Play,
+ Pause,
+ AlertCircle,
+ Info,
+} from 'lucide-react';
+import type { LogEntry } from '../types/index.js';
+
+interface LogsViewProps {
+ logs: LogEntry[];
+ selectedUnit: string;
+ onSelectUnit: (unit: string) => void;
+}
+
+export const LogsView: React.FC = ({
+ logs,
+ selectedUnit,
+ onSelectUnit,
+}) => {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [isStreaming, setIsStreaming] = useState(true);
+
+ const units = ['all', 'zfs.target', 'smbd.service', 'immich.service', 'naxos-api.service', 'perses.service'];
+
+ const filteredLogs = logs.filter((log) => {
+ const matchesUnit = selectedUnit === 'all' || log.unit.includes(selectedUnit);
+ const matchesQuery = !searchQuery || log.message.toLowerCase().includes(searchQuery.toLowerCase()) || log.unit.toLowerCase().includes(searchQuery.toLowerCase());
+ return matchesUnit && matchesQuery;
+ });
+
+ const exportLogs = () => {
+ const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `naxos-logs-${new Date().toISOString()}.json`;
+ a.click();
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
System Logs & Real-Time Viewer
+
+ Real-time journald logs with severity classification and service unit filtering
+
+
+
+
+
setIsStreaming(!isStreaming)}
+ className={`flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-medium transition ${
+ isStreaming
+ ? 'bg-emerald-600/20 text-emerald-400 border border-emerald-500/30'
+ : 'bg-slate-800 text-slate-400'
+ }`}
+ >
+ {isStreaming ? : }
+ {isStreaming ? 'Streaming Live' : 'Paused'}
+
+
+
+
+ Export Diagnostics
+
+
+
+
+ {/* Filter Toolbar */}
+
+
+
+ setSearchQuery(e.target.value)}
+ placeholder="Search log messages, services, or timestamps..."
+ className="w-full pl-10 pr-4 py-2 bg-slate-900 border border-slate-800 rounded-xl text-xs text-slate-200 placeholder:text-slate-500 focus:outline-none focus:border-emerald-500 font-mono"
+ />
+
+
+
+ {units.map((u) => (
+ onSelectUnit(u)}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap transition ${
+ selectedUnit === u
+ ? 'bg-emerald-600 text-white shadow-sm'
+ : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ {u === 'all' ? 'All Services' : u.replace('.service', '').replace('.target', '')}
+
+ ))}
+
+
+
+ {/* Log Output Table / Feed */}
+
+
+ Live journald stream
+ {filteredLogs.length} entries shown
+
+
+
+ {filteredLogs.map((entry, idx) => (
+
+
+ {entry.timestamp.slice(11, 19)}
+
+
+
+ {entry.unit}
+
+
+
+ {entry.message}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/views/OverviewView.tsx b/src/views/OverviewView.tsx
new file mode 100644
index 0000000..c83cebb
--- /dev/null
+++ b/src/views/OverviewView.tsx
@@ -0,0 +1,209 @@
+import React from 'react';
+import {
+ HardDrive,
+ Cpu,
+ Layers,
+ FolderSync,
+ ShieldCheck,
+ Activity,
+ ArrowUpRight,
+ TrendingUp,
+} from 'lucide-react';
+import type { SystemStatus, ZfsPool, InstalledApp } from '../types/index.js';
+
+interface OverviewViewProps {
+ status: SystemStatus | null;
+ pools: ZfsPool[];
+ apps: InstalledApp[];
+ onNavigate: (tab: string) => void;
+}
+
+export const OverviewView: React.FC = ({
+ status,
+ pools,
+ apps,
+ onNavigate,
+}) => {
+ const primaryPool = pools[0];
+
+ return (
+
+ {/* Top Banner / Welcome */}
+
+
+
Appliance Overview
+
+ Running {status?.osVersion || 'NaxOS 24.11'} on OpenZFS storage engine
+
+
+
+
onNavigate('storage')}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs font-medium text-slate-200 transition border border-slate-700"
+ >
+ Manage Pools
+
+
+
onNavigate('apps')}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-xs font-medium text-white transition shadow-sm shadow-emerald-600/30"
+ >
+ App Store
+
+
+
+
+
+ {/* KPI Cards Grid */}
+
+ {/* Storage Pool Card */}
+
+
+ Pool: {primaryPool?.name || 'tank'}
+
+ {primaryPool?.health || 'ONLINE'}
+
+
+
+
+ {primaryPool?.allocated || '8.2T'} / {primaryPool?.size || '14.5T'}
+
+ {/* Progress Bar */}
+
+
+
+ Free: {primaryPool?.free || '6.3T'}
+ Frag: {primaryPool?.fragmentation || '14%'}
+
+
+
+ {/* ZFS ARC Hit Ratio */}
+
+
+ ZFS ARC Cache
+
+
+
+
+ {status?.arcHitRatioPercent || 98.6}%
+
+
+ ARC Size: 4.0 GB (Max: 4.0 GB)
+
+
+
+
+ Optimal cache hit performance
+
+
+
+ {/* Workloads Card */}
+
+
+ Running Workloads
+
+
+
+
+ {apps.length || 2} Active Services
+
+
+ Immich (Systemd), Jellyfin (Docker)
+
+
+
+ Runtime Engine: Docker + Systemd
+
+
+
+ {/* Network Shares */}
+
+
+ Active File Shares
+
+
+
+
+ {status?.activeSharesCount || 3}
+
+
+ SMB (Time Machine) + NFS v4
+
+
+
+ Bonjour / mDNS discovery enabled
+
+
+
+
+ {/* Embedded Native Perses Analytics Panel */}
+
+
+
+
+
Native Perses Telemetry
+
+
onNavigate('analytics')}
+ className="text-xs text-emerald-400 hover:text-emerald-300 font-medium flex items-center gap-1"
+ >
+ View Full Dashboards
+
+
+
+
+
+ {/* Simulated Perses Panel 1: Throughput */}
+
+
Pool Read/Write Throughput
+
+ {[42, 65, 30, 85, 95, 120, 80, 45, 60, 90, 110, 75, 40, 95, 130, 85, 70, 90, 105, 140, 95, 60, 80, 115].map((val, i) => (
+
+ ))}
+
+
+ -30m
+ 115 MB/s Peak
+ Now
+
+
+
+ {/* Simulated Perses Panel 2: Memory & ARC */}
+
+
System Memory & ZFS ARC Distribution
+
+
+
+ ZFS ARC Cache
+ 4.0 GB
+
+
+
+ Services & Containers
+ 9.2 GB
+
+
+
+ Free Available
+ 18.8 GB
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/views/SharesView.tsx b/src/views/SharesView.tsx
new file mode 100644
index 0000000..8c7917c
--- /dev/null
+++ b/src/views/SharesView.tsx
@@ -0,0 +1,282 @@
+import React, { useState } from 'react';
+import {
+ FolderSync,
+ Apple,
+ Users,
+ Shield,
+ Plus,
+ Trash2,
+ HardDrive,
+ CheckCircle2,
+} from 'lucide-react';
+import type { SmbShare, NfsExport } from '../types/index.js';
+
+interface SharesViewProps {
+ smbShares: Record;
+ nfsExports: NfsExport[];
+ onSaveSmbShare: (share: SmbShare) => Promise;
+ onDeleteSmbShare: (name: string) => Promise;
+}
+
+export const SharesView: React.FC = ({
+ smbShares,
+ nfsExports,
+ onSaveSmbShare,
+ onDeleteSmbShare,
+}) => {
+ const [activeTab, setActiveTab] = useState<'smb' | 'nfs'>('smb');
+ const [showAddModal, setShowAddModal] = useState(false);
+ const [newShareName, setNewShareName] = useState('');
+ const [newSharePath, setNewSharePath] = useState('/tank/');
+ const [newShareGuest, setNewShareGuest] = useState(false);
+ const [newShareTimeMachine, setNewShareTimeMachine] = useState(false);
+ const [newShareTmSize, setNewShareTmSize] = useState('512G');
+
+ const handleCreateShare = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!newShareName) return;
+ await onSaveSmbShare({
+ name: newShareName,
+ path: newSharePath,
+ readOnly: false,
+ browseable: true,
+ guestOk: newShareGuest,
+ validUsers: newShareGuest ? [] : ['admin'],
+ timeMachine: newShareTimeMachine,
+ timeMachineMaxSize: newShareTimeMachine ? newShareTmSize : undefined,
+ });
+ setShowAddModal(false);
+ setNewShareName('');
+ setNewSharePath('/tank/');
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
Network File Sharing
+
+ Samba (SMB) with Apple Time Machine support and high-performance NFS exports
+
+
+
+
+
+ setActiveTab('smb')}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeTab === 'smb' ? 'bg-emerald-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Samba (SMB)
+
+ setActiveTab('nfs')}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeTab === 'nfs' ? 'bg-emerald-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ NFS Exports
+
+
+
+
setShowAddModal(true)}
+ className="flex items-center gap-1.5 px-3.5 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-xs font-semibold text-white shadow-sm transition"
+ >
+
+ Create Share
+
+
+
+
+ {/* SMB Shares Table */}
+ {activeTab === 'smb' && (
+
+
+
+
+ Samba (SMB) Shares
+
+ {Object.keys(smbShares).length} shares active
+
+
+
+
+
+
+ Share Name
+ Storage Path
+ Access Mode
+ Apple Time Machine
+ Valid Users
+ Action
+
+
+
+ {Object.entries(smbShares).map(([name, share]) => (
+
+ [{share.name}]
+ {share.path}
+
+ {share.guestOk ? (
+
+ Guest / Public
+
+ ) : (
+
+ Authenticated Only
+
+ )}
+
+
+ {share.timeMachine ? (
+
+
+
Enabled {share.timeMachineMaxSize ? `(${share.timeMachineMaxSize})` : ''}
+
+ ) : (
+ Disabled
+ )}
+
+
+ {share.validUsers.length > 0 ? share.validUsers.join(', ') : 'All authorized'}
+
+
+ onDeleteSmbShare(name)}
+ className="p-1 rounded hover:bg-rose-500/20 text-slate-500 hover:text-rose-400 transition"
+ >
+
+
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* NFS Exports Table */}
+ {activeTab === 'nfs' && (
+
+
+
NFS Exports
+ {nfsExports.length} export rules
+
+
+
+ {nfsExports.map((exp, idx) => (
+
+
+
{exp.path}
+
+ Allowed Clients:
+ {exp.clients.map((c, i) => (
+
+ {c.subnet} ({c.options})
+
+ ))}
+
+
+
+ ))}
+
+
+ )}
+
+ {/* Add Share Modal */}
+ {showAddModal && (
+
+
+
Create New SMB Share
+
+
+
+ )}
+
+ );
+};
diff --git a/src/views/StorageView.tsx b/src/views/StorageView.tsx
new file mode 100644
index 0000000..2e9750d
--- /dev/null
+++ b/src/views/StorageView.tsx
@@ -0,0 +1,348 @@
+import React, { useState } from 'react';
+import {
+ HardDrive,
+ Database,
+ Plus,
+ RefreshCw,
+ Clock,
+ RotateCcw,
+ CheckCircle2,
+ AlertTriangle,
+ FolderTree,
+ FileBox,
+ ShieldCheck,
+} from 'lucide-react';
+import type { ZfsPool, ZfsDataset, ZfsSnapshot, UnimportedPool } from '../types/index.js';
+
+interface StorageViewProps {
+ pools: ZfsPool[];
+ datasets: ZfsDataset[];
+ snapshots: ZfsSnapshot[];
+ unimportedPools: UnimportedPool[];
+ onRefresh: () => void;
+ onImportPool: (poolName: string) => Promise;
+ onScrubPool: (poolName: string) => Promise;
+ onCreateSnapshot: (dataset: string, tag: string) => Promise;
+ onRollbackSnapshot: (snapshotName: string) => Promise;
+}
+
+export const StorageView: React.FC = ({
+ pools,
+ datasets,
+ snapshots,
+ unimportedPools,
+ onRefresh,
+ onImportPool,
+ onScrubPool,
+ onCreateSnapshot,
+ onRollbackSnapshot,
+}) => {
+ const [activeSubTab, setActiveSubTab] = useState<'pools' | 'datasets' | 'snapshots' | 'migration'>('pools');
+ const [selectedPool, setSelectedPool] = useState(pools[0]?.name || 'tank');
+ const [importingPoolName, setImportingPoolName] = useState(null);
+ const [importSuccess, setImportSuccess] = useState(null);
+
+ const handleImport = async (poolName: string) => {
+ setImportingPoolName(poolName);
+ try {
+ await onImportPool(poolName);
+ setImportSuccess(`Pool '${poolName}' was successfully imported without data loss!`);
+ setTimeout(() => setImportSuccess(null), 6000);
+ } finally {
+ setImportingPoolName(null);
+ }
+ };
+
+ return (
+
+ {/* Sub-navigation Header */}
+
+
+
OpenZFS Storage Engine
+
+ Enterprise storage layout, datasets, snapshot policies, and foreign pool migration
+
+
+
+
+ setActiveSubTab('pools')}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeSubTab === 'pools'
+ ? 'bg-emerald-600 text-white shadow-sm'
+ : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Storage Pools ({pools.length})
+
+ setActiveSubTab('datasets')}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeSubTab === 'datasets'
+ ? 'bg-emerald-600 text-white shadow-sm'
+ : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Datasets ({datasets.length})
+
+ setActiveSubTab('snapshots')}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeSubTab === 'snapshots'
+ ? 'bg-emerald-600 text-white shadow-sm'
+ : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Snapshots ({snapshots.length})
+
+ setActiveSubTab('migration')}
+ className={`flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-medium transition ${
+ activeSubTab === 'migration'
+ ? 'bg-amber-600 text-white shadow-sm'
+ : 'text-amber-400 hover:bg-amber-500/10'
+ }`}
+ >
+ Pool Migration
+ {unimportedPools.length > 0 && (
+
+ )}
+
+
+
+
+ {importSuccess && (
+
+
+ {importSuccess}
+
+ )}
+
+ {/* Pools Tab */}
+ {activeSubTab === 'pools' && (
+
+ {pools.map((pool) => (
+
+
+
+
+
+
+
+
+ {pool.name}
+
+ {pool.health}
+
+
+
+ OpenZFS Pool • ashift=12 • Compression=lz4/zstd
+
+
+
+
+
+ onScrubPool(pool.name)}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-medium border border-slate-700 transition"
+ >
+
+ Run Scrub
+
+
+
+
+ {/* Pool Capacity Bar */}
+
+
+ Capacity Used: {pool.allocated} / {pool.size} ({pool.capacityPercent}%)
+ Available Free: {pool.free}
+
+
+
+
+ {/* Status summary */}
+
+ Last Scrub: {pool.scanStatus || 'Completed with 0 errors'}
+ Fragmentation: {pool.fragmentation}
+
+
+ ))}
+
+ )}
+
+ {/* Datasets Tab */}
+ {activeSubTab === 'datasets' && (
+
+
+
+
+ ZFS Datasets & Mountpoints
+
+ Total {datasets.length} datasets
+
+
+
+
+
+
+ Dataset Name
+ Mountpoint
+ Used
+ Available
+ Compression
+ Record Size
+ Quota
+
+
+
+ {datasets.map((ds) => (
+
+
+
+ {ds.name}
+
+ {ds.mountpoint}
+ {ds.used}
+ {ds.available}
+
+
+ {ds.compression}
+
+
+ {ds.recordsize}
+
+ {ds.quota === 'none' ? None : ds.quota}
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Snapshots Tab */}
+ {activeSubTab === 'snapshots' && (
+
+
+
+
+ ZFS Point-in-Time Snapshots
+
+ Total {snapshots.length} snapshots
+
+
+
+
+
+
+ Snapshot Name
+ Dataset
+ Created
+ Unique Used
+ Actions
+
+
+
+ {snapshots.map((snap) => (
+
+ {snap.name}
+ {snap.dataset}
+ {snap.creationTime}
+ {snap.usedBytes}
+
+ onRollbackSnapshot(snap.name)}
+ className="inline-flex items-center gap-1 px-2.5 py-1 rounded bg-slate-800 hover:bg-rose-500/20 text-slate-300 hover:text-rose-300 text-[11px] font-medium transition border border-slate-700"
+ title="Rollback dataset to this exact snapshot state"
+ >
+
+ Rollback
+
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Foreign Pool Migration Wizard */}
+ {activeSubTab === 'migration' && (
+
+
+
+
+ Zero-Data-Loss Migration Engine
+
+
+ NaxOS provides first-class support for discovering and safely importing existing ZFS storage pools created on TrueNAS CORE/SCALE or raw NixOS systems (such as nixos-lukas) . The migration engine operates with safety-first guarantees: existing datasets, quotas, and critical application assets (such as your Immich photo library in tank/media/photos) are preserved untouched.
+
+
+
+
+
+
+
Detected Foreign ZFS Pools
+
Unimported pools ready for adoption
+
+
+
+ Re-scan Disks
+
+
+
+ {unimportedPools.length === 0 ? (
+
+ No foreign unimported pools detected. All connected pools are currently active.
+
+ ) : (
+
+ {unimportedPools.map((pool) => (
+
+
+
+ {pool.name}
+
+ {pool.state}
+
+ ID: {pool.id}
+
+
{pool.status}
+
+ Member Disks:
+ {pool.disks.map((d) => (
+
+ {d}
+
+ ))}
+
+
+
+
handleImport(pool.name)}
+ disabled={importingPoolName === pool.name}
+ className="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 text-white text-xs font-semibold shadow-sm transition shrink-0"
+ >
+ {importingPoolName === pool.name ? 'Importing Safely...' : 'Safe Import & Adopt'}
+
+
+ ))}
+
+ )}
+
+
+ )}
+
+ );
+};
diff --git a/src/views/WizardView.tsx b/src/views/WizardView.tsx
new file mode 100644
index 0000000..284998a
--- /dev/null
+++ b/src/views/WizardView.tsx
@@ -0,0 +1,234 @@
+import React, { useState } from 'react';
+import {
+ HardDrive,
+ Network,
+ UserCheck,
+ CheckCircle2,
+ ArrowRight,
+ ArrowLeft,
+ Database,
+ ShieldCheck,
+ Server,
+} from 'lucide-react';
+
+export const WizardView: React.FC = () => {
+ const [step, setStep] = useState(1);
+ const [storageMode, setStorageMode] = useState<'create' | 'import'>('import');
+ const [poolName, setPoolName] = useState('tank');
+ const [hostname, setHostname] = useState('naxos');
+ const [adminUser, setAdminUser] = useState('admin');
+ const [adminPassword, setAdminPassword] = useState('');
+ const [gitopsUrl, setGitopsUrl] = useState('ssh://git@git.lholz.de:2222/naxos/naxos-config.git');
+ const [isDone, setIsDone] = useState(false);
+
+ const handleFinish = () => {
+ setIsDone(true);
+ };
+
+ return (
+
+
+
NaxOS Appliance Setup Wizard
+
+ Step-by-step guided configuration for storage layout, network, and security
+
+
+
+ {/* Progress Steps */}
+
+ {[
+ { num: 1, title: 'Storage & Pools' },
+ { num: 2, title: 'Network' },
+ { num: 3, title: 'Credentials' },
+ { num: 4, title: 'GitOps & Finish' },
+ ].map((s) => (
+
+
s.num
+ ? 'bg-emerald-500/20 text-emerald-400'
+ : 'bg-slate-800 text-slate-500'
+ }`}
+ >
+ {step > s.num ? : s.num}
+
+
+ {s.title}
+
+
+ ))}
+
+
+ {/* Form Container */}
+
+ {step === 1 && (
+
+
+
+ Storage Architecture & ZFS Pool
+
+
+
+
setStorageMode('import')}
+ className={`p-4 rounded-xl border text-left transition ${
+ storageMode === 'import'
+ ? 'bg-emerald-500/10 border-emerald-500 text-white'
+ : 'bg-slate-950 border-slate-800 text-slate-400 hover:text-white'
+ }`}
+ >
+ Safe Import Existing Pool
+
+ Import TrueNAS or nixos-lukas pool without formatting. Preserves Immich photo library.
+
+
+
+
setStorageMode('create')}
+ className={`p-4 rounded-xl border text-left transition ${
+ storageMode === 'create'
+ ? 'bg-emerald-500/10 border-emerald-500 text-white'
+ : 'bg-slate-950 border-slate-800 text-slate-400 hover:text-white'
+ }`}
+ >
+ Create New Pool
+
+ Initialize fresh ZFS mirror or RAID-Z pool across raw disks with ashift=12.
+
+
+
+
+
+ Target Pool Name
+ setPoolName(e.target.value)}
+ className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white font-mono text-xs focus:outline-none focus:border-emerald-500"
+ />
+
+
+ )}
+
+ {step === 2 && (
+
+
+
+ Network Configuration
+
+
+
+ Appliance Hostname
+ setHostname(e.target.value)}
+ className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white font-mono text-xs focus:outline-none focus:border-emerald-500"
+ />
+
+
+
+ DHCP will automatically request an IP address and advertise mDNS name {hostname}.local via Avahi.
+
+
+ )}
+
+ {step === 3 && (
+
+
+
+ Administrator Credentials
+
+
+
+ Admin Username
+ setAdminUser(e.target.value)}
+ className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white font-mono text-xs focus:outline-none focus:border-emerald-500"
+ />
+
+
+
+ Admin Password
+ setAdminPassword(e.target.value)}
+ placeholder="••••••••••••"
+ className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white font-mono text-xs focus:outline-none focus:border-emerald-500"
+ />
+
+
+ )}
+
+ {step === 4 && (
+
+
+
+ GitOps Remote & Final Validation
+
+
+
+
Remote Git Repository (Gitea)
+
setGitopsUrl(e.target.value)}
+ className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white font-mono text-xs focus:outline-none focus:border-emerald-500"
+ />
+
+ All future web dashboard configuration adjustments will be committed here.
+
+
+
+ {isDone && (
+
+
+ NaxOS Appliance successfully initialized and ready for production!
+
+ )}
+
+ )}
+
+ {/* Buttons */}
+
+
setStep(step - 1)}
+ className="flex items-center gap-1.5 px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 disabled:opacity-30 text-slate-300 text-xs font-medium"
+ >
+
+ Back
+
+
+ {step < 4 ? (
+
setStep(step + 1)}
+ className="flex items-center gap-1.5 px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold shadow-sm"
+ >
+ Next Step
+
+
+ ) : (
+
+
+ Complete Setup
+
+ )}
+
+
+
+ );
+};
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..5a4f981
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,24 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
+ darkMode: 'class',
+ theme: {
+ extend: {
+ colors: {
+ naxos: {
+ 50: '#f0fdf4',
+ 100: '#dcfce7',
+ 500: '#22c55e',
+ 600: '#16a34a',
+ 700: '#15803d',
+ 900: '#14532d',
+ },
+ slate: {
+ 850: '#151e2e',
+ 950: '#0b1120',
+ },
+ },
+ },
+ },
+ plugins: [],
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..6bfa73a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..c027834
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ proxy: {
+ '/api': {
+ target: 'http://localhost:8088',
+ changeOrigin: true,
+ },
+ '/perses': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ },
+ },
+ },
+});