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 +[![Gitea Actions](https://git.lholz.de/naxos/naxos-ui/actions/workflows/ci.yaml/badge.svg)](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 + + )} +
+ +
+
+
+ ); +}; 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 ( + + ); +}; 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 ( +
+
+

+ {status?.hostname || 'naxos'} + + Gen {status?.nixosGeneration || 42} + +

+
+ + ARC Hit: {status?.arcHitRatioPercent || 98.6}% +
+
+ +
+ {gitops?.lastCommitSha && ( +
+ SHA: + {gitops.lastCommitSha.slice(0, 7)} +
+ )} + + + + +
+
+ ); +}; 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) => ( + + ))} +
+ + +
+
+ + {/* 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 +

+
+ +
+ + +
+
+ + {/* 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: + +
+ + {/* Action button */} + {isInstalled ? ( +
+ + Installed & Active +
+ ) : ( + + )} +
+
+ ); + })} +
+ )} + + {/* 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' ? ( + + ) : ( + + )} + + +
+
+ ))} +
+
+ )} +
+ ); +}; 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 +

+
+ + +
+ + {/* 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 +
+ +
+
+ + 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 && ( + + )} +
+ ))} +
+
+
+ ); +}; 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 +

+
+ +
+ + + +
+
+ + {/* 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) => ( + + ))} +
+
+ + {/* 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 +

+
+
+ + +
+
+ + {/* 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

+
+ +
+ +
+ {/* 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 +

+
+ +
+
+ + +
+ + +
+
+ + {/* SMB Shares Table */} + {activeTab === 'smb' && ( +
+
+

+ + Samba (SMB) Shares +

+ {Object.keys(smbShares).length} shares active +
+ +
+ + + + + + + + + + + + + {Object.entries(smbShares).map(([name, share]) => ( + + + + + + + + + ))} + +
Share NameStorage PathAccess ModeApple Time MachineValid UsersAction
[{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'} + + +
+
+
+ )} + + {/* 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

+
+
+ + setNewShareName(e.target.value)} + placeholder="e.g. photos, documents" + className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white focus:outline-none focus:border-emerald-500 font-mono" + required + /> +
+ +
+ + setNewSharePath(e.target.value)} + placeholder="/tank/..." + className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white focus:outline-none focus:border-emerald-500 font-mono" + required + /> +
+ +
+
+ Public Guest Access + Allow connecting without credentials +
+ setNewShareGuest(e.target.checked)} + className="h-4 w-4 rounded accent-emerald-500" + /> +
+ +
+
+
+ macOS Time Machine Target + Enables Apple vfs_fruit compatibility +
+ setNewShareTimeMachine(e.target.checked)} + className="h-4 w-4 rounded accent-emerald-500" + /> +
+ + {newShareTimeMachine && ( +
+ + setNewShareTmSize(e.target.value)} + placeholder="512G" + className="w-full px-3 py-1.5 bg-slate-900 border border-slate-700 rounded-lg text-white font-mono" + /> +
+ )} +
+ +
+ + +
+
+
+
+ )} +
+ ); +}; 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 +

+
+ +
+ + + + +
+
+ + {importSuccess && ( +
+ + {importSuccess} +
+ )} + + {/* Pools Tab */} + {activeSubTab === 'pools' && ( +
+ {pools.map((pool) => ( +
+
+
+
+ +
+
+

+ {pool.name} + + {pool.health} + +

+

+ OpenZFS Pool • ashift=12 • Compression=lz4/zstd +

+
+
+ +
+ +
+
+ + {/* 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 +
+ +
+ + + + + + + + + + + + + + {datasets.map((ds) => ( + + + + + + + + + + ))} + +
Dataset NameMountpointUsedAvailableCompressionRecord SizeQuota
+ + {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 +
+ +
+ + + + + + + + + + + + {snapshots.map((snap) => ( + + + + + + + + ))} + +
Snapshot NameDatasetCreatedUnique UsedActions
{snap.name}{snap.dataset}{snap.creationTime}{snap.usedBytes} + +
+
+
+ )} + + {/* 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

+
+ +
+ + {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} + + ))} +
+
+ + +
+ ))} +
+ )} +
+
+ )} +
+ ); +}; 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} +
+ +
+ ))} +
+ + {/* Form Container */} +
+ {step === 1 && ( +
+

+ + Storage Architecture & ZFS Pool +

+ +
+ + + +
+ +
+ + 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 +

+ +
+ + 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 +

+ +
+ + 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" + /> +
+ +
+ + 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 +

+ +
+ + 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 */} +
+ + + {step < 4 ? ( + + ) : ( + + )} +
+
+
+ ); +}; 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, + }, + }, + }, +});