diff --git a/experimental/app/.gitignore b/experimental/app/.gitignore
deleted file mode 100644
index a547bf3..0000000
--- a/experimental/app/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
diff --git a/experimental/app/.vite/deps/_metadata.json b/experimental/app/.vite/deps/_metadata.json
deleted file mode 100644
index 8cac017..0000000
--- a/experimental/app/.vite/deps/_metadata.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "hash": "0e2daab1",
- "configHash": "9fbff803",
- "lockfileHash": "e3b0c442",
- "browserHash": "04872398",
- "optimized": {},
- "chunks": {}
-}
\ No newline at end of file
diff --git a/experimental/app/.vite/deps/package.json b/experimental/app/.vite/deps/package.json
deleted file mode 100644
index 3dbc1ca..0000000
--- a/experimental/app/.vite/deps/package.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "type": "module"
-}
diff --git a/experimental/app/README.md b/experimental/app/README.md
deleted file mode 100644
index da98444..0000000
--- a/experimental/app/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# React + TypeScript + Vite
-
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
-
-Currently, two official plugins are available:
-
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-
-## Expanding the ESLint configuration
-
-If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
-
-```js
-export default tseslint.config({
- extends: [
- // Remove ...tseslint.configs.recommended and replace with this
- ...tseslint.configs.recommendedTypeChecked,
- // Alternatively, use this for stricter rules
- ...tseslint.configs.strictTypeChecked,
- // Optionally, add this for stylistic rules
- ...tseslint.configs.stylisticTypeChecked,
- ],
- languageOptions: {
- // other options...
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- },
-})
-```
-
-You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
-
-```js
-// eslint.config.js
-import reactX from 'eslint-plugin-react-x'
-import reactDom from 'eslint-plugin-react-dom'
-
-export default tseslint.config({
- plugins: {
- // Add the react-x and react-dom plugins
- 'react-x': reactX,
- 'react-dom': reactDom,
- },
- rules: {
- // other rules...
- // Enable its recommended typescript rules
- ...reactX.configs['recommended-typescript'].rules,
- ...reactDom.configs.recommended.rules,
- },
-})
-```
diff --git a/experimental/app/components.json b/experimental/app/components.json
deleted file mode 100644
index 73afbdb..0000000
--- a/experimental/app/components.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "$schema": "https://ui.shadcn.com/schema.json",
- "style": "new-york",
- "rsc": false,
- "tsx": true,
- "tailwind": {
- "config": "",
- "css": "src/index.css",
- "baseColor": "neutral",
- "cssVariables": true,
- "prefix": ""
- },
- "aliases": {
- "components": "@/components",
- "utils": "@/lib/utils",
- "ui": "@/components/ui",
- "lib": "@/lib",
- "hooks": "@/hooks"
- },
- "iconLibrary": "lucide"
-}
\ No newline at end of file
diff --git a/experimental/app/eslint.config.js b/experimental/app/eslint.config.js
deleted file mode 100644
index 092408a..0000000
--- a/experimental/app/eslint.config.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import tseslint from 'typescript-eslint'
-
-export default tseslint.config(
- { ignores: ['dist'] },
- {
- extends: [js.configs.recommended, ...tseslint.configs.recommended],
- files: ['**/*.{ts,tsx}'],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- },
- plugins: {
- 'react-hooks': reactHooks,
- 'react-refresh': reactRefresh,
- },
- rules: {
- ...reactHooks.configs.recommended.rules,
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- },
- },
-)
diff --git a/experimental/app/index.html b/experimental/app/index.html
deleted file mode 100644
index 58db59d..0000000
--- a/experimental/app/index.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Wild Cloud Central
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
\ No newline at end of file
diff --git a/experimental/app/package.json b/experimental/app/package.json
deleted file mode 100644
index 68e5d02..0000000
--- a/experimental/app/package.json
+++ /dev/null
@@ -1,53 +0,0 @@
-{
- "name": "wild-cloud-central",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc -b && vite build",
- "lint": "eslint .",
- "preview": "vite preview",
- "type-check": "tsc --noEmit",
- "test": "vitest",
- "test:ui": "vitest --ui",
- "test:coverage": "vitest --coverage",
- "build:css": "tailwindcss -i src/index.css -o ./output.css --config ./tailwind.config.js"
- },
- "dependencies": {
- "@hookform/resolvers": "^5.1.1",
- "@radix-ui/react-collapsible": "^1.1.11",
- "@radix-ui/react-dialog": "^1.1.14",
- "@radix-ui/react-label": "^2.1.7",
- "@radix-ui/react-separator": "^1.1.7",
- "@radix-ui/react-slot": "^1.2.3",
- "@radix-ui/react-tooltip": "^1.2.7",
- "@tailwindcss/vite": "^4.1.10",
- "@tanstack/react-query": "^5.62.10",
- "class-variance-authority": "^0.7.1",
- "clsx": "^2.1.1",
- "lucide-react": "^0.516.0",
- "react": "^19.1.0",
- "react-dom": "^19.1.0",
- "react-hook-form": "^7.58.1",
- "tailwind-merge": "^3.3.1",
- "tailwindcss": "^4.1.10",
- "zod": "^3.25.67"
- },
- "devDependencies": {
- "@eslint/js": "^9.25.0",
- "@types/node": "^24.0.3",
- "@types/react": "^19.1.2",
- "@types/react-dom": "^19.1.2",
- "@vitejs/plugin-react": "^4.4.1",
- "eslint": "^9.25.0",
- "eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.19",
- "globals": "^16.0.0",
- "tw-animate-css": "^1.3.4",
- "typescript": "~5.8.3",
- "typescript-eslint": "^8.30.1",
- "vite": "^6.3.5"
- },
- "packageManager": "pnpm@10.9.0+sha512.0486e394640d3c1fb3c9d43d49cf92879ff74f8516959c235308f5a8f62e2e19528a65cdc2a3058f587cde71eba3d5b56327c8c33a97e4c4051ca48a10ca2d5f"
-}
diff --git a/experimental/app/pnpm-lock.yaml b/experimental/app/pnpm-lock.yaml
deleted file mode 100644
index be90570..0000000
--- a/experimental/app/pnpm-lock.yaml
+++ /dev/null
@@ -1,3284 +0,0 @@
-lockfileVersion: '9.0'
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
-
-importers:
-
- .:
- dependencies:
- '@hookform/resolvers':
- specifier: ^5.1.1
- version: 5.1.1(react-hook-form@7.58.1(react@19.1.0))
- '@radix-ui/react-collapsible':
- specifier: ^1.1.11
- version: 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-dialog':
- specifier: ^1.1.14
- version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-label':
- specifier: ^2.1.7
- version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-separator':
- specifier: ^1.1.7
- version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-slot':
- specifier: ^1.2.3
- version: 1.2.3(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-tooltip':
- specifier: ^1.2.7
- version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@tailwindcss/vite':
- specifier: ^4.1.10
- version: 4.1.10(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1))
- '@tanstack/react-query':
- specifier: ^5.62.10
- version: 5.80.7(react@19.1.0)
- class-variance-authority:
- specifier: ^0.7.1
- version: 0.7.1
- clsx:
- specifier: ^2.1.1
- version: 2.1.1
- lucide-react:
- specifier: ^0.516.0
- version: 0.516.0(react@19.1.0)
- react:
- specifier: ^19.1.0
- version: 19.1.0
- react-dom:
- specifier: ^19.1.0
- version: 19.1.0(react@19.1.0)
- react-hook-form:
- specifier: ^7.58.1
- version: 7.58.1(react@19.1.0)
- tailwind-merge:
- specifier: ^3.3.1
- version: 3.3.1
- tailwindcss:
- specifier: ^4.1.10
- version: 4.1.10
- zod:
- specifier: ^3.25.67
- version: 3.25.67
- devDependencies:
- '@eslint/js':
- specifier: ^9.25.0
- version: 9.29.0
- '@types/node':
- specifier: ^24.0.3
- version: 24.0.3
- '@types/react':
- specifier: ^19.1.2
- version: 19.1.8
- '@types/react-dom':
- specifier: ^19.1.2
- version: 19.1.6(@types/react@19.1.8)
- '@vitejs/plugin-react':
- specifier: ^4.4.1
- version: 4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1))
- eslint:
- specifier: ^9.25.0
- version: 9.29.0(jiti@2.4.2)
- eslint-plugin-react-hooks:
- specifier: ^5.2.0
- version: 5.2.0(eslint@9.29.0(jiti@2.4.2))
- eslint-plugin-react-refresh:
- specifier: ^0.4.19
- version: 0.4.20(eslint@9.29.0(jiti@2.4.2))
- globals:
- specifier: ^16.0.0
- version: 16.2.0
- tw-animate-css:
- specifier: ^1.3.4
- version: 1.3.4
- typescript:
- specifier: ~5.8.3
- version: 5.8.3
- typescript-eslint:
- specifier: ^8.30.1
- version: 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- vite:
- specifier: ^6.3.5
- version: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)
-
-packages:
-
- '@ampproject/remapping@2.3.0':
- resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
- engines: {node: '>=6.0.0'}
-
- '@babel/code-frame@7.27.1':
- resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
- engines: {node: '>=6.9.0'}
-
- '@babel/compat-data@7.27.5':
- resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==}
- engines: {node: '>=6.9.0'}
-
- '@babel/core@7.27.4':
- resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==}
- engines: {node: '>=6.9.0'}
-
- '@babel/generator@7.27.5':
- resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-compilation-targets@7.27.2':
- resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-module-imports@7.27.1':
- resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-module-transforms@7.27.3':
- resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0
-
- '@babel/helper-plugin-utils@7.27.1':
- resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-string-parser@7.27.1':
- resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-validator-identifier@7.27.1':
- resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-validator-option@7.27.1':
- resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helpers@7.27.6':
- resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==}
- engines: {node: '>=6.9.0'}
-
- '@babel/parser@7.27.5':
- resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
- engines: {node: '>=6.0.0'}
- hasBin: true
-
- '@babel/plugin-transform-react-jsx-self@7.27.1':
- resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-transform-react-jsx-source@7.27.1':
- resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/template@7.27.2':
- resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/traverse@7.27.4':
- resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/types@7.27.6':
- resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
- engines: {node: '>=6.9.0'}
-
- '@esbuild/aix-ppc64@0.25.5':
- resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [aix]
-
- '@esbuild/android-arm64@0.25.5':
- resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [android]
-
- '@esbuild/android-arm@0.25.5':
- resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [android]
-
- '@esbuild/android-x64@0.25.5':
- resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [android]
-
- '@esbuild/darwin-arm64@0.25.5':
- resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [darwin]
-
- '@esbuild/darwin-x64@0.25.5':
- resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [darwin]
-
- '@esbuild/freebsd-arm64@0.25.5':
- resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [freebsd]
-
- '@esbuild/freebsd-x64@0.25.5':
- resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [freebsd]
-
- '@esbuild/linux-arm64@0.25.5':
- resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [linux]
-
- '@esbuild/linux-arm@0.25.5':
- resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [linux]
-
- '@esbuild/linux-ia32@0.25.5':
- resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [linux]
-
- '@esbuild/linux-loong64@0.25.5':
- resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==}
- engines: {node: '>=18'}
- cpu: [loong64]
- os: [linux]
-
- '@esbuild/linux-mips64el@0.25.5':
- resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==}
- engines: {node: '>=18'}
- cpu: [mips64el]
- os: [linux]
-
- '@esbuild/linux-ppc64@0.25.5':
- resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [linux]
-
- '@esbuild/linux-riscv64@0.25.5':
- resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==}
- engines: {node: '>=18'}
- cpu: [riscv64]
- os: [linux]
-
- '@esbuild/linux-s390x@0.25.5':
- resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==}
- engines: {node: '>=18'}
- cpu: [s390x]
- os: [linux]
-
- '@esbuild/linux-x64@0.25.5':
- resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [linux]
-
- '@esbuild/netbsd-arm64@0.25.5':
- resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [netbsd]
-
- '@esbuild/netbsd-x64@0.25.5':
- resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [netbsd]
-
- '@esbuild/openbsd-arm64@0.25.5':
- resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [openbsd]
-
- '@esbuild/openbsd-x64@0.25.5':
- resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [openbsd]
-
- '@esbuild/sunos-x64@0.25.5':
- resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [sunos]
-
- '@esbuild/win32-arm64@0.25.5':
- resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [win32]
-
- '@esbuild/win32-ia32@0.25.5':
- resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [win32]
-
- '@esbuild/win32-x64@0.25.5':
- resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [win32]
-
- '@eslint-community/eslint-utils@4.7.0':
- resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- peerDependencies:
- eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
-
- '@eslint-community/regexpp@4.12.1':
- resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
- engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
-
- '@eslint/config-array@0.20.1':
- resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/config-helpers@0.2.3':
- resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/core@0.14.0':
- resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/core@0.15.0':
- resolution: {integrity: sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/eslintrc@3.3.1':
- resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/js@9.29.0':
- resolution: {integrity: sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/object-schema@2.1.6':
- resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/plugin-kit@0.3.2':
- resolution: {integrity: sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@floating-ui/core@1.7.1':
- resolution: {integrity: sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==}
-
- '@floating-ui/dom@1.7.1':
- resolution: {integrity: sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==}
-
- '@floating-ui/react-dom@2.1.3':
- resolution: {integrity: sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==}
- peerDependencies:
- react: '>=16.8.0'
- react-dom: '>=16.8.0'
-
- '@floating-ui/utils@0.2.9':
- resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
-
- '@hookform/resolvers@5.1.1':
- resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==}
- peerDependencies:
- react-hook-form: ^7.55.0
-
- '@humanfs/core@0.19.1':
- resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
- engines: {node: '>=18.18.0'}
-
- '@humanfs/node@0.16.6':
- resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
- engines: {node: '>=18.18.0'}
-
- '@humanwhocodes/module-importer@1.0.1':
- resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
- engines: {node: '>=12.22'}
-
- '@humanwhocodes/retry@0.3.1':
- resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
- engines: {node: '>=18.18'}
-
- '@humanwhocodes/retry@0.4.3':
- resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
- engines: {node: '>=18.18'}
-
- '@isaacs/fs-minipass@4.0.1':
- resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
- engines: {node: '>=18.0.0'}
-
- '@jridgewell/gen-mapping@0.3.8':
- resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
- engines: {node: '>=6.0.0'}
-
- '@jridgewell/resolve-uri@3.1.2':
- resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
- engines: {node: '>=6.0.0'}
-
- '@jridgewell/set-array@1.2.1':
- resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
- engines: {node: '>=6.0.0'}
-
- '@jridgewell/sourcemap-codec@1.5.0':
- resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
-
- '@jridgewell/trace-mapping@0.3.25':
- resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
-
- '@nodelib/fs.scandir@2.1.5':
- resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
- engines: {node: '>= 8'}
-
- '@nodelib/fs.stat@2.0.5':
- resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
- engines: {node: '>= 8'}
-
- '@nodelib/fs.walk@1.2.8':
- resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
- engines: {node: '>= 8'}
-
- '@radix-ui/primitive@1.1.2':
- resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
-
- '@radix-ui/react-arrow@1.1.7':
- resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-collapsible@1.1.11':
- resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-compose-refs@1.1.2':
- resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-context@1.1.2':
- resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-dialog@1.1.14':
- resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-dismissable-layer@1.1.10':
- resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-focus-guards@1.1.2':
- resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-focus-scope@1.1.7':
- resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-id@1.1.1':
- resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-label@2.1.7':
- resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-popper@1.2.7':
- resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-portal@1.1.9':
- resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-presence@1.1.4':
- resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-primitive@2.1.3':
- resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-separator@1.1.7':
- resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-slot@1.2.3':
- resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-tooltip@1.2.7':
- resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-use-callback-ref@1.1.1':
- resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-use-controllable-state@1.2.2':
- resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-use-effect-event@0.0.2':
- resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-use-escape-keydown@1.1.1':
- resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-use-layout-effect@1.1.1':
- resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-use-rect@1.1.1':
- resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-use-size@1.1.1':
- resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-visually-hidden@1.2.3':
- resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/rect@1.1.1':
- resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
-
- '@rolldown/pluginutils@1.0.0-beta.11':
- resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
-
- '@rollup/rollup-android-arm-eabi@4.43.0':
- resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==}
- cpu: [arm]
- os: [android]
-
- '@rollup/rollup-android-arm64@4.43.0':
- resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==}
- cpu: [arm64]
- os: [android]
-
- '@rollup/rollup-darwin-arm64@4.43.0':
- resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==}
- cpu: [arm64]
- os: [darwin]
-
- '@rollup/rollup-darwin-x64@4.43.0':
- resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==}
- cpu: [x64]
- os: [darwin]
-
- '@rollup/rollup-freebsd-arm64@4.43.0':
- resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==}
- cpu: [arm64]
- os: [freebsd]
-
- '@rollup/rollup-freebsd-x64@4.43.0':
- resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==}
- cpu: [x64]
- os: [freebsd]
-
- '@rollup/rollup-linux-arm-gnueabihf@4.43.0':
- resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==}
- cpu: [arm]
- os: [linux]
-
- '@rollup/rollup-linux-arm-musleabihf@4.43.0':
- resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==}
- cpu: [arm]
- os: [linux]
-
- '@rollup/rollup-linux-arm64-gnu@4.43.0':
- resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==}
- cpu: [arm64]
- os: [linux]
-
- '@rollup/rollup-linux-arm64-musl@4.43.0':
- resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==}
- cpu: [arm64]
- os: [linux]
-
- '@rollup/rollup-linux-loongarch64-gnu@4.43.0':
- resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==}
- cpu: [loong64]
- os: [linux]
-
- '@rollup/rollup-linux-powerpc64le-gnu@4.43.0':
- resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==}
- cpu: [ppc64]
- os: [linux]
-
- '@rollup/rollup-linux-riscv64-gnu@4.43.0':
- resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==}
- cpu: [riscv64]
- os: [linux]
-
- '@rollup/rollup-linux-riscv64-musl@4.43.0':
- resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==}
- cpu: [riscv64]
- os: [linux]
-
- '@rollup/rollup-linux-s390x-gnu@4.43.0':
- resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==}
- cpu: [s390x]
- os: [linux]
-
- '@rollup/rollup-linux-x64-gnu@4.43.0':
- resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==}
- cpu: [x64]
- os: [linux]
-
- '@rollup/rollup-linux-x64-musl@4.43.0':
- resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==}
- cpu: [x64]
- os: [linux]
-
- '@rollup/rollup-win32-arm64-msvc@4.43.0':
- resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==}
- cpu: [arm64]
- os: [win32]
-
- '@rollup/rollup-win32-ia32-msvc@4.43.0':
- resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==}
- cpu: [ia32]
- os: [win32]
-
- '@rollup/rollup-win32-x64-msvc@4.43.0':
- resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==}
- cpu: [x64]
- os: [win32]
-
- '@standard-schema/utils@0.3.0':
- resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
-
- '@tailwindcss/node@4.1.10':
- resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==}
-
- '@tailwindcss/oxide-android-arm64@4.1.10':
- resolution: {integrity: sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [android]
-
- '@tailwindcss/oxide-darwin-arm64@4.1.10':
- resolution: {integrity: sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [darwin]
-
- '@tailwindcss/oxide-darwin-x64@4.1.10':
- resolution: {integrity: sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [darwin]
-
- '@tailwindcss/oxide-freebsd-x64@4.1.10':
- resolution: {integrity: sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [freebsd]
-
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10':
- resolution: {integrity: sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==}
- engines: {node: '>= 10'}
- cpu: [arm]
- os: [linux]
-
- '@tailwindcss/oxide-linux-arm64-gnu@4.1.10':
- resolution: {integrity: sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
-
- '@tailwindcss/oxide-linux-arm64-musl@4.1.10':
- resolution: {integrity: sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
-
- '@tailwindcss/oxide-linux-x64-gnu@4.1.10':
- resolution: {integrity: sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
-
- '@tailwindcss/oxide-linux-x64-musl@4.1.10':
- resolution: {integrity: sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
-
- '@tailwindcss/oxide-wasm32-wasi@4.1.10':
- resolution: {integrity: sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==}
- engines: {node: '>=14.0.0'}
- cpu: [wasm32]
- bundledDependencies:
- - '@napi-rs/wasm-runtime'
- - '@emnapi/core'
- - '@emnapi/runtime'
- - '@tybys/wasm-util'
- - '@emnapi/wasi-threads'
- - tslib
-
- '@tailwindcss/oxide-win32-arm64-msvc@4.1.10':
- resolution: {integrity: sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [win32]
-
- '@tailwindcss/oxide-win32-x64-msvc@4.1.10':
- resolution: {integrity: sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [win32]
-
- '@tailwindcss/oxide@4.1.10':
- resolution: {integrity: sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==}
- engines: {node: '>= 10'}
-
- '@tailwindcss/vite@4.1.10':
- resolution: {integrity: sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A==}
- peerDependencies:
- vite: ^5.2.0 || ^6
-
- '@tanstack/query-core@5.80.7':
- resolution: {integrity: sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==}
-
- '@tanstack/react-query@5.80.7':
- resolution: {integrity: sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==}
- peerDependencies:
- react: ^18 || ^19
-
- '@types/babel__core@7.20.5':
- resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
-
- '@types/babel__generator@7.27.0':
- resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
-
- '@types/babel__template@7.4.4':
- resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
-
- '@types/babel__traverse@7.20.7':
- resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
-
- '@types/estree@1.0.7':
- resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
-
- '@types/estree@1.0.8':
- resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
-
- '@types/json-schema@7.0.15':
- resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
-
- '@types/node@24.0.3':
- resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==}
-
- '@types/react-dom@19.1.6':
- resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==}
- peerDependencies:
- '@types/react': ^19.0.0
-
- '@types/react@19.1.8':
- resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==}
-
- '@typescript-eslint/eslint-plugin@8.34.1':
- resolution: {integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- '@typescript-eslint/parser': ^8.34.1
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
-
- '@typescript-eslint/parser@8.34.1':
- resolution: {integrity: sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
-
- '@typescript-eslint/project-service@8.34.1':
- resolution: {integrity: sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <5.9.0'
-
- '@typescript-eslint/scope-manager@8.34.1':
- resolution: {integrity: sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@typescript-eslint/tsconfig-utils@8.34.1':
- resolution: {integrity: sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <5.9.0'
-
- '@typescript-eslint/type-utils@8.34.1':
- resolution: {integrity: sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
-
- '@typescript-eslint/types@8.34.1':
- resolution: {integrity: sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@typescript-eslint/typescript-estree@8.34.1':
- resolution: {integrity: sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <5.9.0'
-
- '@typescript-eslint/utils@8.34.1':
- resolution: {integrity: sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
-
- '@typescript-eslint/visitor-keys@8.34.1':
- resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@vitejs/plugin-react@4.5.2':
- resolution: {integrity: sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==}
- engines: {node: ^14.18.0 || >=16.0.0}
- peerDependencies:
- vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
-
- acorn-jsx@5.3.2:
- resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
- peerDependencies:
- acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
-
- acorn@8.15.0:
- resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
- ajv@6.12.6:
- resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
-
- ansi-styles@4.3.0:
- resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
- engines: {node: '>=8'}
-
- argparse@2.0.1:
- resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
-
- aria-hidden@1.2.6:
- resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
- engines: {node: '>=10'}
-
- balanced-match@1.0.2:
- resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-
- brace-expansion@1.1.12:
- resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
-
- brace-expansion@2.0.2:
- resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
-
- braces@3.0.3:
- resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
- engines: {node: '>=8'}
-
- browserslist@4.25.0:
- resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==}
- engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
- hasBin: true
-
- callsites@3.1.0:
- resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
- engines: {node: '>=6'}
-
- caniuse-lite@1.0.30001723:
- resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
-
- chalk@4.1.2:
- resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
- engines: {node: '>=10'}
-
- chownr@3.0.0:
- resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
- engines: {node: '>=18'}
-
- class-variance-authority@0.7.1:
- resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
-
- clsx@2.1.1:
- resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
- engines: {node: '>=6'}
-
- color-convert@2.0.1:
- resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
- engines: {node: '>=7.0.0'}
-
- color-name@1.1.4:
- resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
-
- concat-map@0.0.1:
- resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
-
- convert-source-map@2.0.0:
- resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
-
- cross-spawn@7.0.6:
- resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
- engines: {node: '>= 8'}
-
- csstype@3.1.3:
- resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
-
- debug@4.4.1:
- resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
-
- deep-is@0.1.4:
- resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
-
- detect-libc@2.0.4:
- resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
- engines: {node: '>=8'}
-
- detect-node-es@1.1.0:
- resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
-
- electron-to-chromium@1.5.169:
- resolution: {integrity: sha512-q7SQx6mkLy0GTJK9K9OiWeaBMV4XQtBSdf6MJUzDB/H/5tFXfIiX38Lci1Kl6SsgiEhz1SQI1ejEOU5asWEhwQ==}
-
- enhanced-resolve@5.18.1:
- resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
- engines: {node: '>=10.13.0'}
-
- esbuild@0.25.5:
- resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==}
- engines: {node: '>=18'}
- hasBin: true
-
- escalade@3.2.0:
- resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
- engines: {node: '>=6'}
-
- escape-string-regexp@4.0.0:
- resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
- engines: {node: '>=10'}
-
- eslint-plugin-react-hooks@5.2.0:
- resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
- engines: {node: '>=10'}
- peerDependencies:
- eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
-
- eslint-plugin-react-refresh@0.4.20:
- resolution: {integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==}
- peerDependencies:
- eslint: '>=8.40'
-
- eslint-scope@8.4.0:
- resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- eslint-visitor-keys@3.4.3:
- resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
- eslint-visitor-keys@4.2.1:
- resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- eslint@9.29.0:
- resolution: {integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- hasBin: true
- peerDependencies:
- jiti: '*'
- peerDependenciesMeta:
- jiti:
- optional: true
-
- espree@10.4.0:
- resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- esquery@1.6.0:
- resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
- engines: {node: '>=0.10'}
-
- esrecurse@4.3.0:
- resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
- engines: {node: '>=4.0'}
-
- estraverse@5.3.0:
- resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
- engines: {node: '>=4.0'}
-
- esutils@2.0.3:
- resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
- engines: {node: '>=0.10.0'}
-
- fast-deep-equal@3.1.3:
- resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
-
- fast-glob@3.3.3:
- resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
- engines: {node: '>=8.6.0'}
-
- fast-json-stable-stringify@2.1.0:
- resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
-
- fast-levenshtein@2.0.6:
- resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
-
- fastq@1.19.1:
- resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
-
- fdir@6.4.6:
- resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
- peerDependencies:
- picomatch: ^3 || ^4
- peerDependenciesMeta:
- picomatch:
- optional: true
-
- file-entry-cache@8.0.0:
- resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
- engines: {node: '>=16.0.0'}
-
- fill-range@7.1.1:
- resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
- engines: {node: '>=8'}
-
- find-up@5.0.0:
- resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
- engines: {node: '>=10'}
-
- flat-cache@4.0.1:
- resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
- engines: {node: '>=16'}
-
- flatted@3.3.3:
- resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
-
- fsevents@2.3.3:
- resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
- engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
- os: [darwin]
-
- gensync@1.0.0-beta.2:
- resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
- engines: {node: '>=6.9.0'}
-
- get-nonce@1.0.1:
- resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
- engines: {node: '>=6'}
-
- glob-parent@5.1.2:
- resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
- engines: {node: '>= 6'}
-
- glob-parent@6.0.2:
- resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
- engines: {node: '>=10.13.0'}
-
- globals@11.12.0:
- resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
- engines: {node: '>=4'}
-
- globals@14.0.0:
- resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
- engines: {node: '>=18'}
-
- globals@16.2.0:
- resolution: {integrity: sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==}
- engines: {node: '>=18'}
-
- graceful-fs@4.2.11:
- resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
-
- graphemer@1.4.0:
- resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
-
- has-flag@4.0.0:
- resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
- engines: {node: '>=8'}
-
- ignore@5.3.2:
- resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
- engines: {node: '>= 4'}
-
- ignore@7.0.5:
- resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
- engines: {node: '>= 4'}
-
- import-fresh@3.3.1:
- resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
- engines: {node: '>=6'}
-
- imurmurhash@0.1.4:
- resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
- engines: {node: '>=0.8.19'}
-
- is-extglob@2.1.1:
- resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
- engines: {node: '>=0.10.0'}
-
- is-glob@4.0.3:
- resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
- engines: {node: '>=0.10.0'}
-
- is-number@7.0.0:
- resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
- engines: {node: '>=0.12.0'}
-
- isexe@2.0.0:
- resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
-
- jiti@2.4.2:
- resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
- hasBin: true
-
- js-tokens@4.0.0:
- resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
-
- js-yaml@4.1.0:
- resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
- hasBin: true
-
- jsesc@3.1.0:
- resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
- engines: {node: '>=6'}
- hasBin: true
-
- json-buffer@3.0.1:
- resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
-
- json-schema-traverse@0.4.1:
- resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
-
- json-stable-stringify-without-jsonify@1.0.1:
- resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
-
- json5@2.2.3:
- resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
- engines: {node: '>=6'}
- hasBin: true
-
- keyv@4.5.4:
- resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
-
- levn@0.4.1:
- resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
- engines: {node: '>= 0.8.0'}
-
- lightningcss-darwin-arm64@1.30.1:
- resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [darwin]
-
- lightningcss-darwin-x64@1.30.1:
- resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [darwin]
-
- lightningcss-freebsd-x64@1.30.1:
- resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [freebsd]
-
- lightningcss-linux-arm-gnueabihf@1.30.1:
- resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm]
- os: [linux]
-
- lightningcss-linux-arm64-gnu@1.30.1:
- resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [linux]
-
- lightningcss-linux-arm64-musl@1.30.1:
- resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [linux]
-
- lightningcss-linux-x64-gnu@1.30.1:
- resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [linux]
-
- lightningcss-linux-x64-musl@1.30.1:
- resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [linux]
-
- lightningcss-win32-arm64-msvc@1.30.1:
- resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [win32]
-
- lightningcss-win32-x64-msvc@1.30.1:
- resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [win32]
-
- lightningcss@1.30.1:
- resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
- engines: {node: '>= 12.0.0'}
-
- locate-path@6.0.0:
- resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
- engines: {node: '>=10'}
-
- lodash.merge@4.6.2:
- resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
-
- lru-cache@5.1.1:
- resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
-
- lucide-react@0.516.0:
- resolution: {integrity: sha512-aybBJzLHcw1CIn3rUcRkztB37dsJATtpffLNX+0/w+ws2p21nYIlOwX/B5fqxq8F/BjqVemnJX8chKwRidvROg==}
- peerDependencies:
- react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
-
- magic-string@0.30.17:
- resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
-
- merge2@1.4.1:
- resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
- engines: {node: '>= 8'}
-
- micromatch@4.0.8:
- resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
- engines: {node: '>=8.6'}
-
- minimatch@3.1.2:
- resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
-
- minimatch@9.0.5:
- resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
- engines: {node: '>=16 || 14 >=14.17'}
-
- minipass@7.1.2:
- resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
- engines: {node: '>=16 || 14 >=14.17'}
-
- minizlib@3.0.2:
- resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
- engines: {node: '>= 18'}
-
- mkdirp@3.0.1:
- resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
- engines: {node: '>=10'}
- hasBin: true
-
- ms@2.1.3:
- resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
-
- nanoid@3.3.11:
- resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
-
- natural-compare@1.4.0:
- resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
-
- node-releases@2.0.19:
- resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
-
- optionator@0.9.4:
- resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
- engines: {node: '>= 0.8.0'}
-
- p-limit@3.1.0:
- resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
- engines: {node: '>=10'}
-
- p-locate@5.0.0:
- resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
- engines: {node: '>=10'}
-
- parent-module@1.0.1:
- resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
- engines: {node: '>=6'}
-
- path-exists@4.0.0:
- resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
- engines: {node: '>=8'}
-
- path-key@3.1.1:
- resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
- engines: {node: '>=8'}
-
- picocolors@1.1.1:
- resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
-
- picomatch@2.3.1:
- resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
- engines: {node: '>=8.6'}
-
- picomatch@4.0.2:
- resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
- engines: {node: '>=12'}
-
- postcss@8.5.6:
- resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
- engines: {node: ^10 || ^12 || >=14}
-
- prelude-ls@1.2.1:
- resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
- engines: {node: '>= 0.8.0'}
-
- punycode@2.3.1:
- resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
- engines: {node: '>=6'}
-
- queue-microtask@1.2.3:
- resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
-
- react-dom@19.1.0:
- resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
- peerDependencies:
- react: ^19.1.0
-
- react-hook-form@7.58.1:
- resolution: {integrity: sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==}
- engines: {node: '>=18.0.0'}
- peerDependencies:
- react: ^16.8.0 || ^17 || ^18 || ^19
-
- react-refresh@0.17.0:
- resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
- engines: {node: '>=0.10.0'}
-
- react-remove-scroll-bar@2.3.8:
- resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
- engines: {node: '>=10'}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- react-remove-scroll@2.7.1:
- resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
- engines: {node: '>=10'}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- react-style-singleton@2.2.3:
- resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
- engines: {node: '>=10'}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- react@19.1.0:
- resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
- engines: {node: '>=0.10.0'}
-
- resolve-from@4.0.0:
- resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
- engines: {node: '>=4'}
-
- reusify@1.1.0:
- resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
- engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
-
- rollup@4.43.0:
- resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==}
- engines: {node: '>=18.0.0', npm: '>=8.0.0'}
- hasBin: true
-
- run-parallel@1.2.0:
- resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
-
- scheduler@0.26.0:
- resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
-
- semver@6.3.1:
- resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
- hasBin: true
-
- semver@7.7.2:
- resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
- engines: {node: '>=10'}
- hasBin: true
-
- shebang-command@2.0.0:
- resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
- engines: {node: '>=8'}
-
- shebang-regex@3.0.0:
- resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
- engines: {node: '>=8'}
-
- source-map-js@1.2.1:
- resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
- engines: {node: '>=0.10.0'}
-
- strip-json-comments@3.1.1:
- resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
- engines: {node: '>=8'}
-
- supports-color@7.2.0:
- resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
- engines: {node: '>=8'}
-
- tailwind-merge@3.3.1:
- resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
-
- tailwindcss@4.1.10:
- resolution: {integrity: sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==}
-
- tapable@2.2.2:
- resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==}
- engines: {node: '>=6'}
-
- tar@7.4.3:
- resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
- engines: {node: '>=18'}
-
- tinyglobby@0.2.14:
- resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
- engines: {node: '>=12.0.0'}
-
- to-regex-range@5.0.1:
- resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
- engines: {node: '>=8.0'}
-
- ts-api-utils@2.1.0:
- resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
- engines: {node: '>=18.12'}
- peerDependencies:
- typescript: '>=4.8.4'
-
- tslib@2.8.1:
- resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
-
- tw-animate-css@1.3.4:
- resolution: {integrity: sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==}
-
- type-check@0.4.0:
- resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
- engines: {node: '>= 0.8.0'}
-
- typescript-eslint@8.34.1:
- resolution: {integrity: sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
-
- typescript@5.8.3:
- resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
- engines: {node: '>=14.17'}
- hasBin: true
-
- undici-types@7.8.0:
- resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
-
- update-browserslist-db@1.1.3:
- resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
- hasBin: true
- peerDependencies:
- browserslist: '>= 4.21.0'
-
- uri-js@4.4.1:
- resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
-
- use-callback-ref@1.3.3:
- resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
- engines: {node: '>=10'}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- use-sidecar@1.1.3:
- resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
- engines: {node: '>=10'}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- vite@6.3.5:
- resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
- engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
- hasBin: true
- peerDependencies:
- '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
- jiti: '>=1.21.0'
- less: '*'
- lightningcss: ^1.21.0
- sass: '*'
- sass-embedded: '*'
- stylus: '*'
- sugarss: '*'
- terser: ^5.16.0
- tsx: ^4.8.1
- yaml: ^2.4.2
- peerDependenciesMeta:
- '@types/node':
- optional: true
- jiti:
- optional: true
- less:
- optional: true
- lightningcss:
- optional: true
- sass:
- optional: true
- sass-embedded:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- terser:
- optional: true
- tsx:
- optional: true
- yaml:
- optional: true
-
- which@2.0.2:
- resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
- engines: {node: '>= 8'}
- hasBin: true
-
- word-wrap@1.2.5:
- resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
- engines: {node: '>=0.10.0'}
-
- yallist@3.1.1:
- resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
-
- yallist@5.0.0:
- resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
- engines: {node: '>=18'}
-
- yocto-queue@0.1.0:
- resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
- engines: {node: '>=10'}
-
- zod@3.25.67:
- resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
-
-snapshots:
-
- '@ampproject/remapping@2.3.0':
- dependencies:
- '@jridgewell/gen-mapping': 0.3.8
- '@jridgewell/trace-mapping': 0.3.25
-
- '@babel/code-frame@7.27.1':
- dependencies:
- '@babel/helper-validator-identifier': 7.27.1
- js-tokens: 4.0.0
- picocolors: 1.1.1
-
- '@babel/compat-data@7.27.5': {}
-
- '@babel/core@7.27.4':
- dependencies:
- '@ampproject/remapping': 2.3.0
- '@babel/code-frame': 7.27.1
- '@babel/generator': 7.27.5
- '@babel/helper-compilation-targets': 7.27.2
- '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
- '@babel/helpers': 7.27.6
- '@babel/parser': 7.27.5
- '@babel/template': 7.27.2
- '@babel/traverse': 7.27.4
- '@babel/types': 7.27.6
- convert-source-map: 2.0.0
- debug: 4.4.1
- gensync: 1.0.0-beta.2
- json5: 2.2.3
- semver: 6.3.1
- transitivePeerDependencies:
- - supports-color
-
- '@babel/generator@7.27.5':
- dependencies:
- '@babel/parser': 7.27.5
- '@babel/types': 7.27.6
- '@jridgewell/gen-mapping': 0.3.8
- '@jridgewell/trace-mapping': 0.3.25
- jsesc: 3.1.0
-
- '@babel/helper-compilation-targets@7.27.2':
- dependencies:
- '@babel/compat-data': 7.27.5
- '@babel/helper-validator-option': 7.27.1
- browserslist: 4.25.0
- lru-cache: 5.1.1
- semver: 6.3.1
-
- '@babel/helper-module-imports@7.27.1':
- dependencies:
- '@babel/traverse': 7.27.4
- '@babel/types': 7.27.6
- transitivePeerDependencies:
- - supports-color
-
- '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)':
- dependencies:
- '@babel/core': 7.27.4
- '@babel/helper-module-imports': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
- '@babel/traverse': 7.27.4
- transitivePeerDependencies:
- - supports-color
-
- '@babel/helper-plugin-utils@7.27.1': {}
-
- '@babel/helper-string-parser@7.27.1': {}
-
- '@babel/helper-validator-identifier@7.27.1': {}
-
- '@babel/helper-validator-option@7.27.1': {}
-
- '@babel/helpers@7.27.6':
- dependencies:
- '@babel/template': 7.27.2
- '@babel/types': 7.27.6
-
- '@babel/parser@7.27.5':
- dependencies:
- '@babel/types': 7.27.6
-
- '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)':
- dependencies:
- '@babel/core': 7.27.4
- '@babel/helper-plugin-utils': 7.27.1
-
- '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.4)':
- dependencies:
- '@babel/core': 7.27.4
- '@babel/helper-plugin-utils': 7.27.1
-
- '@babel/template@7.27.2':
- dependencies:
- '@babel/code-frame': 7.27.1
- '@babel/parser': 7.27.5
- '@babel/types': 7.27.6
-
- '@babel/traverse@7.27.4':
- dependencies:
- '@babel/code-frame': 7.27.1
- '@babel/generator': 7.27.5
- '@babel/parser': 7.27.5
- '@babel/template': 7.27.2
- '@babel/types': 7.27.6
- debug: 4.4.1
- globals: 11.12.0
- transitivePeerDependencies:
- - supports-color
-
- '@babel/types@7.27.6':
- dependencies:
- '@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
-
- '@esbuild/aix-ppc64@0.25.5':
- optional: true
-
- '@esbuild/android-arm64@0.25.5':
- optional: true
-
- '@esbuild/android-arm@0.25.5':
- optional: true
-
- '@esbuild/android-x64@0.25.5':
- optional: true
-
- '@esbuild/darwin-arm64@0.25.5':
- optional: true
-
- '@esbuild/darwin-x64@0.25.5':
- optional: true
-
- '@esbuild/freebsd-arm64@0.25.5':
- optional: true
-
- '@esbuild/freebsd-x64@0.25.5':
- optional: true
-
- '@esbuild/linux-arm64@0.25.5':
- optional: true
-
- '@esbuild/linux-arm@0.25.5':
- optional: true
-
- '@esbuild/linux-ia32@0.25.5':
- optional: true
-
- '@esbuild/linux-loong64@0.25.5':
- optional: true
-
- '@esbuild/linux-mips64el@0.25.5':
- optional: true
-
- '@esbuild/linux-ppc64@0.25.5':
- optional: true
-
- '@esbuild/linux-riscv64@0.25.5':
- optional: true
-
- '@esbuild/linux-s390x@0.25.5':
- optional: true
-
- '@esbuild/linux-x64@0.25.5':
- optional: true
-
- '@esbuild/netbsd-arm64@0.25.5':
- optional: true
-
- '@esbuild/netbsd-x64@0.25.5':
- optional: true
-
- '@esbuild/openbsd-arm64@0.25.5':
- optional: true
-
- '@esbuild/openbsd-x64@0.25.5':
- optional: true
-
- '@esbuild/sunos-x64@0.25.5':
- optional: true
-
- '@esbuild/win32-arm64@0.25.5':
- optional: true
-
- '@esbuild/win32-ia32@0.25.5':
- optional: true
-
- '@esbuild/win32-x64@0.25.5':
- optional: true
-
- '@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.4.2))':
- dependencies:
- eslint: 9.29.0(jiti@2.4.2)
- eslint-visitor-keys: 3.4.3
-
- '@eslint-community/regexpp@4.12.1': {}
-
- '@eslint/config-array@0.20.1':
- dependencies:
- '@eslint/object-schema': 2.1.6
- debug: 4.4.1
- minimatch: 3.1.2
- transitivePeerDependencies:
- - supports-color
-
- '@eslint/config-helpers@0.2.3': {}
-
- '@eslint/core@0.14.0':
- dependencies:
- '@types/json-schema': 7.0.15
-
- '@eslint/core@0.15.0':
- dependencies:
- '@types/json-schema': 7.0.15
-
- '@eslint/eslintrc@3.3.1':
- dependencies:
- ajv: 6.12.6
- debug: 4.4.1
- espree: 10.4.0
- globals: 14.0.0
- ignore: 5.3.2
- import-fresh: 3.3.1
- js-yaml: 4.1.0
- minimatch: 3.1.2
- strip-json-comments: 3.1.1
- transitivePeerDependencies:
- - supports-color
-
- '@eslint/js@9.29.0': {}
-
- '@eslint/object-schema@2.1.6': {}
-
- '@eslint/plugin-kit@0.3.2':
- dependencies:
- '@eslint/core': 0.15.0
- levn: 0.4.1
-
- '@floating-ui/core@1.7.1':
- dependencies:
- '@floating-ui/utils': 0.2.9
-
- '@floating-ui/dom@1.7.1':
- dependencies:
- '@floating-ui/core': 1.7.1
- '@floating-ui/utils': 0.2.9
-
- '@floating-ui/react-dom@2.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@floating-ui/dom': 1.7.1
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
-
- '@floating-ui/utils@0.2.9': {}
-
- '@hookform/resolvers@5.1.1(react-hook-form@7.58.1(react@19.1.0))':
- dependencies:
- '@standard-schema/utils': 0.3.0
- react-hook-form: 7.58.1(react@19.1.0)
-
- '@humanfs/core@0.19.1': {}
-
- '@humanfs/node@0.16.6':
- dependencies:
- '@humanfs/core': 0.19.1
- '@humanwhocodes/retry': 0.3.1
-
- '@humanwhocodes/module-importer@1.0.1': {}
-
- '@humanwhocodes/retry@0.3.1': {}
-
- '@humanwhocodes/retry@0.4.3': {}
-
- '@isaacs/fs-minipass@4.0.1':
- dependencies:
- minipass: 7.1.2
-
- '@jridgewell/gen-mapping@0.3.8':
- dependencies:
- '@jridgewell/set-array': 1.2.1
- '@jridgewell/sourcemap-codec': 1.5.0
- '@jridgewell/trace-mapping': 0.3.25
-
- '@jridgewell/resolve-uri@3.1.2': {}
-
- '@jridgewell/set-array@1.2.1': {}
-
- '@jridgewell/sourcemap-codec@1.5.0': {}
-
- '@jridgewell/trace-mapping@0.3.25':
- dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.0
-
- '@nodelib/fs.scandir@2.1.5':
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- run-parallel: 1.2.0
-
- '@nodelib/fs.stat@2.0.5': {}
-
- '@nodelib/fs.walk@1.2.8':
- dependencies:
- '@nodelib/fs.scandir': 2.1.5
- fastq: 1.19.1
-
- '@radix-ui/primitive@1.1.2': {}
-
- '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/primitive': 1.1.2
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/primitive': 1.1.2
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
- aria-hidden: 1.2.6
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/primitive': 1.1.2
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@floating-ui/react-dom': 2.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/rect': 1.1.1
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/primitive': 1.1.2
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- '@radix-ui/rect': 1.1.1
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)':
- dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.8
-
- '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
- '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
- '@radix-ui/rect@1.1.1': {}
-
- '@rolldown/pluginutils@1.0.0-beta.11': {}
-
- '@rollup/rollup-android-arm-eabi@4.43.0':
- optional: true
-
- '@rollup/rollup-android-arm64@4.43.0':
- optional: true
-
- '@rollup/rollup-darwin-arm64@4.43.0':
- optional: true
-
- '@rollup/rollup-darwin-x64@4.43.0':
- optional: true
-
- '@rollup/rollup-freebsd-arm64@4.43.0':
- optional: true
-
- '@rollup/rollup-freebsd-x64@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-arm-gnueabihf@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-arm-musleabihf@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-arm64-gnu@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-arm64-musl@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-loongarch64-gnu@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-powerpc64le-gnu@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-riscv64-gnu@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-riscv64-musl@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-s390x-gnu@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-x64-gnu@4.43.0':
- optional: true
-
- '@rollup/rollup-linux-x64-musl@4.43.0':
- optional: true
-
- '@rollup/rollup-win32-arm64-msvc@4.43.0':
- optional: true
-
- '@rollup/rollup-win32-ia32-msvc@4.43.0':
- optional: true
-
- '@rollup/rollup-win32-x64-msvc@4.43.0':
- optional: true
-
- '@standard-schema/utils@0.3.0': {}
-
- '@tailwindcss/node@4.1.10':
- dependencies:
- '@ampproject/remapping': 2.3.0
- enhanced-resolve: 5.18.1
- jiti: 2.4.2
- lightningcss: 1.30.1
- magic-string: 0.30.17
- source-map-js: 1.2.1
- tailwindcss: 4.1.10
-
- '@tailwindcss/oxide-android-arm64@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-darwin-arm64@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-darwin-x64@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-freebsd-x64@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-linux-arm64-gnu@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-linux-arm64-musl@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-linux-x64-gnu@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-linux-x64-musl@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-wasm32-wasi@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-win32-arm64-msvc@4.1.10':
- optional: true
-
- '@tailwindcss/oxide-win32-x64-msvc@4.1.10':
- optional: true
-
- '@tailwindcss/oxide@4.1.10':
- dependencies:
- detect-libc: 2.0.4
- tar: 7.4.3
- optionalDependencies:
- '@tailwindcss/oxide-android-arm64': 4.1.10
- '@tailwindcss/oxide-darwin-arm64': 4.1.10
- '@tailwindcss/oxide-darwin-x64': 4.1.10
- '@tailwindcss/oxide-freebsd-x64': 4.1.10
- '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.10
- '@tailwindcss/oxide-linux-arm64-gnu': 4.1.10
- '@tailwindcss/oxide-linux-arm64-musl': 4.1.10
- '@tailwindcss/oxide-linux-x64-gnu': 4.1.10
- '@tailwindcss/oxide-linux-x64-musl': 4.1.10
- '@tailwindcss/oxide-wasm32-wasi': 4.1.10
- '@tailwindcss/oxide-win32-arm64-msvc': 4.1.10
- '@tailwindcss/oxide-win32-x64-msvc': 4.1.10
-
- '@tailwindcss/vite@4.1.10(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1))':
- dependencies:
- '@tailwindcss/node': 4.1.10
- '@tailwindcss/oxide': 4.1.10
- tailwindcss: 4.1.10
- vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)
-
- '@tanstack/query-core@5.80.7': {}
-
- '@tanstack/react-query@5.80.7(react@19.1.0)':
- dependencies:
- '@tanstack/query-core': 5.80.7
- react: 19.1.0
-
- '@types/babel__core@7.20.5':
- dependencies:
- '@babel/parser': 7.27.5
- '@babel/types': 7.27.6
- '@types/babel__generator': 7.27.0
- '@types/babel__template': 7.4.4
- '@types/babel__traverse': 7.20.7
-
- '@types/babel__generator@7.27.0':
- dependencies:
- '@babel/types': 7.27.6
-
- '@types/babel__template@7.4.4':
- dependencies:
- '@babel/parser': 7.27.5
- '@babel/types': 7.27.6
-
- '@types/babel__traverse@7.20.7':
- dependencies:
- '@babel/types': 7.27.6
-
- '@types/estree@1.0.7': {}
-
- '@types/estree@1.0.8': {}
-
- '@types/json-schema@7.0.15': {}
-
- '@types/node@24.0.3':
- dependencies:
- undici-types: 7.8.0
-
- '@types/react-dom@19.1.6(@types/react@19.1.8)':
- dependencies:
- '@types/react': 19.1.8
-
- '@types/react@19.1.8':
- dependencies:
- csstype: 3.1.3
-
- '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)':
- dependencies:
- '@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- '@typescript-eslint/scope-manager': 8.34.1
- '@typescript-eslint/type-utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- '@typescript-eslint/visitor-keys': 8.34.1
- eslint: 9.29.0(jiti@2.4.2)
- graphemer: 1.4.0
- ignore: 7.0.5
- natural-compare: 1.4.0
- ts-api-utils: 2.1.0(typescript@5.8.3)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)':
- dependencies:
- '@typescript-eslint/scope-manager': 8.34.1
- '@typescript-eslint/types': 8.34.1
- '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3)
- '@typescript-eslint/visitor-keys': 8.34.1
- debug: 4.4.1
- eslint: 9.29.0(jiti@2.4.2)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/project-service@8.34.1(typescript@5.8.3)':
- dependencies:
- '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3)
- '@typescript-eslint/types': 8.34.1
- debug: 4.4.1
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/scope-manager@8.34.1':
- dependencies:
- '@typescript-eslint/types': 8.34.1
- '@typescript-eslint/visitor-keys': 8.34.1
-
- '@typescript-eslint/tsconfig-utils@8.34.1(typescript@5.8.3)':
- dependencies:
- typescript: 5.8.3
-
- '@typescript-eslint/type-utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)':
- dependencies:
- '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3)
- '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- debug: 4.4.1
- eslint: 9.29.0(jiti@2.4.2)
- ts-api-utils: 2.1.0(typescript@5.8.3)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/types@8.34.1': {}
-
- '@typescript-eslint/typescript-estree@8.34.1(typescript@5.8.3)':
- dependencies:
- '@typescript-eslint/project-service': 8.34.1(typescript@5.8.3)
- '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3)
- '@typescript-eslint/types': 8.34.1
- '@typescript-eslint/visitor-keys': 8.34.1
- debug: 4.4.1
- fast-glob: 3.3.3
- is-glob: 4.0.3
- minimatch: 9.0.5
- semver: 7.7.2
- ts-api-utils: 2.1.0(typescript@5.8.3)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)':
- dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2))
- '@typescript-eslint/scope-manager': 8.34.1
- '@typescript-eslint/types': 8.34.1
- '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3)
- eslint: 9.29.0(jiti@2.4.2)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/visitor-keys@8.34.1':
- dependencies:
- '@typescript-eslint/types': 8.34.1
- eslint-visitor-keys: 4.2.1
-
- '@vitejs/plugin-react@4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1))':
- dependencies:
- '@babel/core': 7.27.4
- '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4)
- '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.4)
- '@rolldown/pluginutils': 1.0.0-beta.11
- '@types/babel__core': 7.20.5
- react-refresh: 0.17.0
- vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)
- transitivePeerDependencies:
- - supports-color
-
- acorn-jsx@5.3.2(acorn@8.15.0):
- dependencies:
- acorn: 8.15.0
-
- acorn@8.15.0: {}
-
- ajv@6.12.6:
- dependencies:
- fast-deep-equal: 3.1.3
- fast-json-stable-stringify: 2.1.0
- json-schema-traverse: 0.4.1
- uri-js: 4.4.1
-
- ansi-styles@4.3.0:
- dependencies:
- color-convert: 2.0.1
-
- argparse@2.0.1: {}
-
- aria-hidden@1.2.6:
- dependencies:
- tslib: 2.8.1
-
- balanced-match@1.0.2: {}
-
- brace-expansion@1.1.12:
- dependencies:
- balanced-match: 1.0.2
- concat-map: 0.0.1
-
- brace-expansion@2.0.2:
- dependencies:
- balanced-match: 1.0.2
-
- braces@3.0.3:
- dependencies:
- fill-range: 7.1.1
-
- browserslist@4.25.0:
- dependencies:
- caniuse-lite: 1.0.30001723
- electron-to-chromium: 1.5.169
- node-releases: 2.0.19
- update-browserslist-db: 1.1.3(browserslist@4.25.0)
-
- callsites@3.1.0: {}
-
- caniuse-lite@1.0.30001723: {}
-
- chalk@4.1.2:
- dependencies:
- ansi-styles: 4.3.0
- supports-color: 7.2.0
-
- chownr@3.0.0: {}
-
- class-variance-authority@0.7.1:
- dependencies:
- clsx: 2.1.1
-
- clsx@2.1.1: {}
-
- color-convert@2.0.1:
- dependencies:
- color-name: 1.1.4
-
- color-name@1.1.4: {}
-
- concat-map@0.0.1: {}
-
- convert-source-map@2.0.0: {}
-
- cross-spawn@7.0.6:
- dependencies:
- path-key: 3.1.1
- shebang-command: 2.0.0
- which: 2.0.2
-
- csstype@3.1.3: {}
-
- debug@4.4.1:
- dependencies:
- ms: 2.1.3
-
- deep-is@0.1.4: {}
-
- detect-libc@2.0.4: {}
-
- detect-node-es@1.1.0: {}
-
- electron-to-chromium@1.5.169: {}
-
- enhanced-resolve@5.18.1:
- dependencies:
- graceful-fs: 4.2.11
- tapable: 2.2.2
-
- esbuild@0.25.5:
- optionalDependencies:
- '@esbuild/aix-ppc64': 0.25.5
- '@esbuild/android-arm': 0.25.5
- '@esbuild/android-arm64': 0.25.5
- '@esbuild/android-x64': 0.25.5
- '@esbuild/darwin-arm64': 0.25.5
- '@esbuild/darwin-x64': 0.25.5
- '@esbuild/freebsd-arm64': 0.25.5
- '@esbuild/freebsd-x64': 0.25.5
- '@esbuild/linux-arm': 0.25.5
- '@esbuild/linux-arm64': 0.25.5
- '@esbuild/linux-ia32': 0.25.5
- '@esbuild/linux-loong64': 0.25.5
- '@esbuild/linux-mips64el': 0.25.5
- '@esbuild/linux-ppc64': 0.25.5
- '@esbuild/linux-riscv64': 0.25.5
- '@esbuild/linux-s390x': 0.25.5
- '@esbuild/linux-x64': 0.25.5
- '@esbuild/netbsd-arm64': 0.25.5
- '@esbuild/netbsd-x64': 0.25.5
- '@esbuild/openbsd-arm64': 0.25.5
- '@esbuild/openbsd-x64': 0.25.5
- '@esbuild/sunos-x64': 0.25.5
- '@esbuild/win32-arm64': 0.25.5
- '@esbuild/win32-ia32': 0.25.5
- '@esbuild/win32-x64': 0.25.5
-
- escalade@3.2.0: {}
-
- escape-string-regexp@4.0.0: {}
-
- eslint-plugin-react-hooks@5.2.0(eslint@9.29.0(jiti@2.4.2)):
- dependencies:
- eslint: 9.29.0(jiti@2.4.2)
-
- eslint-plugin-react-refresh@0.4.20(eslint@9.29.0(jiti@2.4.2)):
- dependencies:
- eslint: 9.29.0(jiti@2.4.2)
-
- eslint-scope@8.4.0:
- dependencies:
- esrecurse: 4.3.0
- estraverse: 5.3.0
-
- eslint-visitor-keys@3.4.3: {}
-
- eslint-visitor-keys@4.2.1: {}
-
- eslint@9.29.0(jiti@2.4.2):
- dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2))
- '@eslint-community/regexpp': 4.12.1
- '@eslint/config-array': 0.20.1
- '@eslint/config-helpers': 0.2.3
- '@eslint/core': 0.14.0
- '@eslint/eslintrc': 3.3.1
- '@eslint/js': 9.29.0
- '@eslint/plugin-kit': 0.3.2
- '@humanfs/node': 0.16.6
- '@humanwhocodes/module-importer': 1.0.1
- '@humanwhocodes/retry': 0.4.3
- '@types/estree': 1.0.8
- '@types/json-schema': 7.0.15
- ajv: 6.12.6
- chalk: 4.1.2
- cross-spawn: 7.0.6
- debug: 4.4.1
- escape-string-regexp: 4.0.0
- eslint-scope: 8.4.0
- eslint-visitor-keys: 4.2.1
- espree: 10.4.0
- esquery: 1.6.0
- esutils: 2.0.3
- fast-deep-equal: 3.1.3
- file-entry-cache: 8.0.0
- find-up: 5.0.0
- glob-parent: 6.0.2
- ignore: 5.3.2
- imurmurhash: 0.1.4
- is-glob: 4.0.3
- json-stable-stringify-without-jsonify: 1.0.1
- lodash.merge: 4.6.2
- minimatch: 3.1.2
- natural-compare: 1.4.0
- optionator: 0.9.4
- optionalDependencies:
- jiti: 2.4.2
- transitivePeerDependencies:
- - supports-color
-
- espree@10.4.0:
- dependencies:
- acorn: 8.15.0
- acorn-jsx: 5.3.2(acorn@8.15.0)
- eslint-visitor-keys: 4.2.1
-
- esquery@1.6.0:
- dependencies:
- estraverse: 5.3.0
-
- esrecurse@4.3.0:
- dependencies:
- estraverse: 5.3.0
-
- estraverse@5.3.0: {}
-
- esutils@2.0.3: {}
-
- fast-deep-equal@3.1.3: {}
-
- fast-glob@3.3.3:
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- '@nodelib/fs.walk': 1.2.8
- glob-parent: 5.1.2
- merge2: 1.4.1
- micromatch: 4.0.8
-
- fast-json-stable-stringify@2.1.0: {}
-
- fast-levenshtein@2.0.6: {}
-
- fastq@1.19.1:
- dependencies:
- reusify: 1.1.0
-
- fdir@6.4.6(picomatch@4.0.2):
- optionalDependencies:
- picomatch: 4.0.2
-
- file-entry-cache@8.0.0:
- dependencies:
- flat-cache: 4.0.1
-
- fill-range@7.1.1:
- dependencies:
- to-regex-range: 5.0.1
-
- find-up@5.0.0:
- dependencies:
- locate-path: 6.0.0
- path-exists: 4.0.0
-
- flat-cache@4.0.1:
- dependencies:
- flatted: 3.3.3
- keyv: 4.5.4
-
- flatted@3.3.3: {}
-
- fsevents@2.3.3:
- optional: true
-
- gensync@1.0.0-beta.2: {}
-
- get-nonce@1.0.1: {}
-
- glob-parent@5.1.2:
- dependencies:
- is-glob: 4.0.3
-
- glob-parent@6.0.2:
- dependencies:
- is-glob: 4.0.3
-
- globals@11.12.0: {}
-
- globals@14.0.0: {}
-
- globals@16.2.0: {}
-
- graceful-fs@4.2.11: {}
-
- graphemer@1.4.0: {}
-
- has-flag@4.0.0: {}
-
- ignore@5.3.2: {}
-
- ignore@7.0.5: {}
-
- import-fresh@3.3.1:
- dependencies:
- parent-module: 1.0.1
- resolve-from: 4.0.0
-
- imurmurhash@0.1.4: {}
-
- is-extglob@2.1.1: {}
-
- is-glob@4.0.3:
- dependencies:
- is-extglob: 2.1.1
-
- is-number@7.0.0: {}
-
- isexe@2.0.0: {}
-
- jiti@2.4.2: {}
-
- js-tokens@4.0.0: {}
-
- js-yaml@4.1.0:
- dependencies:
- argparse: 2.0.1
-
- jsesc@3.1.0: {}
-
- json-buffer@3.0.1: {}
-
- json-schema-traverse@0.4.1: {}
-
- json-stable-stringify-without-jsonify@1.0.1: {}
-
- json5@2.2.3: {}
-
- keyv@4.5.4:
- dependencies:
- json-buffer: 3.0.1
-
- levn@0.4.1:
- dependencies:
- prelude-ls: 1.2.1
- type-check: 0.4.0
-
- lightningcss-darwin-arm64@1.30.1:
- optional: true
-
- lightningcss-darwin-x64@1.30.1:
- optional: true
-
- lightningcss-freebsd-x64@1.30.1:
- optional: true
-
- lightningcss-linux-arm-gnueabihf@1.30.1:
- optional: true
-
- lightningcss-linux-arm64-gnu@1.30.1:
- optional: true
-
- lightningcss-linux-arm64-musl@1.30.1:
- optional: true
-
- lightningcss-linux-x64-gnu@1.30.1:
- optional: true
-
- lightningcss-linux-x64-musl@1.30.1:
- optional: true
-
- lightningcss-win32-arm64-msvc@1.30.1:
- optional: true
-
- lightningcss-win32-x64-msvc@1.30.1:
- optional: true
-
- lightningcss@1.30.1:
- dependencies:
- detect-libc: 2.0.4
- optionalDependencies:
- lightningcss-darwin-arm64: 1.30.1
- lightningcss-darwin-x64: 1.30.1
- lightningcss-freebsd-x64: 1.30.1
- lightningcss-linux-arm-gnueabihf: 1.30.1
- lightningcss-linux-arm64-gnu: 1.30.1
- lightningcss-linux-arm64-musl: 1.30.1
- lightningcss-linux-x64-gnu: 1.30.1
- lightningcss-linux-x64-musl: 1.30.1
- lightningcss-win32-arm64-msvc: 1.30.1
- lightningcss-win32-x64-msvc: 1.30.1
-
- locate-path@6.0.0:
- dependencies:
- p-locate: 5.0.0
-
- lodash.merge@4.6.2: {}
-
- lru-cache@5.1.1:
- dependencies:
- yallist: 3.1.1
-
- lucide-react@0.516.0(react@19.1.0):
- dependencies:
- react: 19.1.0
-
- magic-string@0.30.17:
- dependencies:
- '@jridgewell/sourcemap-codec': 1.5.0
-
- merge2@1.4.1: {}
-
- micromatch@4.0.8:
- dependencies:
- braces: 3.0.3
- picomatch: 2.3.1
-
- minimatch@3.1.2:
- dependencies:
- brace-expansion: 1.1.12
-
- minimatch@9.0.5:
- dependencies:
- brace-expansion: 2.0.2
-
- minipass@7.1.2: {}
-
- minizlib@3.0.2:
- dependencies:
- minipass: 7.1.2
-
- mkdirp@3.0.1: {}
-
- ms@2.1.3: {}
-
- nanoid@3.3.11: {}
-
- natural-compare@1.4.0: {}
-
- node-releases@2.0.19: {}
-
- optionator@0.9.4:
- dependencies:
- deep-is: 0.1.4
- fast-levenshtein: 2.0.6
- levn: 0.4.1
- prelude-ls: 1.2.1
- type-check: 0.4.0
- word-wrap: 1.2.5
-
- p-limit@3.1.0:
- dependencies:
- yocto-queue: 0.1.0
-
- p-locate@5.0.0:
- dependencies:
- p-limit: 3.1.0
-
- parent-module@1.0.1:
- dependencies:
- callsites: 3.1.0
-
- path-exists@4.0.0: {}
-
- path-key@3.1.1: {}
-
- picocolors@1.1.1: {}
-
- picomatch@2.3.1: {}
-
- picomatch@4.0.2: {}
-
- postcss@8.5.6:
- dependencies:
- nanoid: 3.3.11
- picocolors: 1.1.1
- source-map-js: 1.2.1
-
- prelude-ls@1.2.1: {}
-
- punycode@2.3.1: {}
-
- queue-microtask@1.2.3: {}
-
- react-dom@19.1.0(react@19.1.0):
- dependencies:
- react: 19.1.0
- scheduler: 0.26.0
-
- react-hook-form@7.58.1(react@19.1.0):
- dependencies:
- react: 19.1.0
-
- react-refresh@0.17.0: {}
-
- react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0):
- dependencies:
- react: 19.1.0
- react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0)
- tslib: 2.8.1
- optionalDependencies:
- '@types/react': 19.1.8
-
- react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0):
- dependencies:
- react: 19.1.0
- react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.0)
- react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0)
- tslib: 2.8.1
- use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0)
- use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0)
- optionalDependencies:
- '@types/react': 19.1.8
-
- react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0):
- dependencies:
- get-nonce: 1.0.1
- react: 19.1.0
- tslib: 2.8.1
- optionalDependencies:
- '@types/react': 19.1.8
-
- react@19.1.0: {}
-
- resolve-from@4.0.0: {}
-
- reusify@1.1.0: {}
-
- rollup@4.43.0:
- dependencies:
- '@types/estree': 1.0.7
- optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.43.0
- '@rollup/rollup-android-arm64': 4.43.0
- '@rollup/rollup-darwin-arm64': 4.43.0
- '@rollup/rollup-darwin-x64': 4.43.0
- '@rollup/rollup-freebsd-arm64': 4.43.0
- '@rollup/rollup-freebsd-x64': 4.43.0
- '@rollup/rollup-linux-arm-gnueabihf': 4.43.0
- '@rollup/rollup-linux-arm-musleabihf': 4.43.0
- '@rollup/rollup-linux-arm64-gnu': 4.43.0
- '@rollup/rollup-linux-arm64-musl': 4.43.0
- '@rollup/rollup-linux-loongarch64-gnu': 4.43.0
- '@rollup/rollup-linux-powerpc64le-gnu': 4.43.0
- '@rollup/rollup-linux-riscv64-gnu': 4.43.0
- '@rollup/rollup-linux-riscv64-musl': 4.43.0
- '@rollup/rollup-linux-s390x-gnu': 4.43.0
- '@rollup/rollup-linux-x64-gnu': 4.43.0
- '@rollup/rollup-linux-x64-musl': 4.43.0
- '@rollup/rollup-win32-arm64-msvc': 4.43.0
- '@rollup/rollup-win32-ia32-msvc': 4.43.0
- '@rollup/rollup-win32-x64-msvc': 4.43.0
- fsevents: 2.3.3
-
- run-parallel@1.2.0:
- dependencies:
- queue-microtask: 1.2.3
-
- scheduler@0.26.0: {}
-
- semver@6.3.1: {}
-
- semver@7.7.2: {}
-
- shebang-command@2.0.0:
- dependencies:
- shebang-regex: 3.0.0
-
- shebang-regex@3.0.0: {}
-
- source-map-js@1.2.1: {}
-
- strip-json-comments@3.1.1: {}
-
- supports-color@7.2.0:
- dependencies:
- has-flag: 4.0.0
-
- tailwind-merge@3.3.1: {}
-
- tailwindcss@4.1.10: {}
-
- tapable@2.2.2: {}
-
- tar@7.4.3:
- dependencies:
- '@isaacs/fs-minipass': 4.0.1
- chownr: 3.0.0
- minipass: 7.1.2
- minizlib: 3.0.2
- mkdirp: 3.0.1
- yallist: 5.0.0
-
- tinyglobby@0.2.14:
- dependencies:
- fdir: 6.4.6(picomatch@4.0.2)
- picomatch: 4.0.2
-
- to-regex-range@5.0.1:
- dependencies:
- is-number: 7.0.0
-
- ts-api-utils@2.1.0(typescript@5.8.3):
- dependencies:
- typescript: 5.8.3
-
- tslib@2.8.1: {}
-
- tw-animate-css@1.3.4: {}
-
- type-check@0.4.0:
- dependencies:
- prelude-ls: 1.2.1
-
- typescript-eslint@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3):
- dependencies:
- '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
- eslint: 9.29.0(jiti@2.4.2)
- typescript: 5.8.3
- transitivePeerDependencies:
- - supports-color
-
- typescript@5.8.3: {}
-
- undici-types@7.8.0: {}
-
- update-browserslist-db@1.1.3(browserslist@4.25.0):
- dependencies:
- browserslist: 4.25.0
- escalade: 3.2.0
- picocolors: 1.1.1
-
- uri-js@4.4.1:
- dependencies:
- punycode: 2.3.1
-
- use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0):
- dependencies:
- react: 19.1.0
- tslib: 2.8.1
- optionalDependencies:
- '@types/react': 19.1.8
-
- use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0):
- dependencies:
- detect-node-es: 1.1.0
- react: 19.1.0
- tslib: 2.8.1
- optionalDependencies:
- '@types/react': 19.1.8
-
- vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1):
- dependencies:
- esbuild: 0.25.5
- fdir: 6.4.6(picomatch@4.0.2)
- picomatch: 4.0.2
- postcss: 8.5.6
- rollup: 4.43.0
- tinyglobby: 0.2.14
- optionalDependencies:
- '@types/node': 24.0.3
- fsevents: 2.3.3
- jiti: 2.4.2
- lightningcss: 1.30.1
-
- which@2.0.2:
- dependencies:
- isexe: 2.0.0
-
- word-wrap@1.2.5: {}
-
- yallist@3.1.1: {}
-
- yallist@5.0.0: {}
-
- yocto-queue@0.1.0: {}
-
- zod@3.25.67: {}
diff --git a/experimental/app/public/vite.svg b/experimental/app/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/experimental/app/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/experimental/app/src/App.css b/experimental/app/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/experimental/app/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/experimental/app/src/App.tsx b/experimental/app/src/App.tsx
deleted file mode 100644
index 4879a4b..0000000
--- a/experimental/app/src/App.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useConfig } from './hooks';
-import {
- Advanced,
- ErrorBoundary
-} from './components';
-import { CloudComponent } from './components/CloudComponent';
-import { CentralComponent } from './components/CentralComponent';
-import { DnsComponent } from './components/DnsComponent';
-import { DhcpComponent } from './components/DhcpComponent';
-import { PxeComponent } from './components/PxeComponent';
-import { ClusterNodesComponent } from './components/ClusterNodesComponent';
-import { ClusterServicesComponent } from './components/ClusterServicesComponent';
-import { AppsComponent } from './components/AppsComponent';
-import { AppSidebar } from './components/AppSidebar';
-import { SidebarProvider, SidebarInset, SidebarTrigger } from './components/ui/sidebar';
-import type { Phase, Tab } from './components/AppSidebar';
-
-function App() {
- const [currentTab, setCurrentTab] = useState('cloud');
- const [completedPhases, setCompletedPhases] = useState([]);
-
- const { config } = useConfig();
-
- // Update phase state from config when it changes
- useEffect(() => {
- console.log('Config changed:', config);
- console.log('config?.wildcloud:', config?.wildcloud);
- if (config?.wildcloud?.currentPhase) {
- console.log('Setting currentTab to:', config.wildcloud.currentPhase);
- setCurrentTab(config.wildcloud.currentPhase as Phase);
- }
- if (config?.wildcloud?.completedPhases) {
- console.log('Setting completedPhases to:', config.wildcloud.completedPhases);
- setCompletedPhases(config.wildcloud.completedPhases as Phase[]);
- }
- }, [config]);
-
- const handlePhaseComplete = (phase: Phase) => {
- if (!completedPhases.includes(phase)) {
- setCompletedPhases(prev => [...prev, phase]);
- }
-
- // Auto-advance to next phase (excluding advanced)
- const phases: Phase[] = ['setup', 'infrastructure', 'cluster', 'apps'];
- const currentIndex = phases.indexOf(phase);
- if (currentIndex < phases.length - 1) {
- setCurrentTab(phases[currentIndex + 1]);
- }
- };
-
- const renderCurrentTab = () => {
- switch (currentTab) {
- case 'cloud':
- return (
-
-
-
- );
- case 'central':
- return (
-
-
-
- );
- case 'dns':
- return (
-
-
-
- );
- case 'dhcp':
- return (
-
-
-
- );
- case 'pxe':
- return (
-
-
-
- );
- case 'setup':
- case 'infrastructure':
- return (
-
- handlePhaseComplete('infrastructure')} />
-
- );
- case 'cluster':
- return (
-
- handlePhaseComplete('cluster')} />
-
- );
- case 'apps':
- return (
-
- handlePhaseComplete('apps')} />
-
- );
- case 'advanced':
- return (
-
-
-
- );
- default:
- return (
-
-
-
- );
- }
- };
-
- return (
-
-
-
-
-
- {renderCurrentTab()}
-
-
-
- );
-}
-
-export default App;
\ No newline at end of file
diff --git a/experimental/app/src/assets/react.svg b/experimental/app/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/experimental/app/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/experimental/app/src/components/Advanced.tsx b/experimental/app/src/components/Advanced.tsx
deleted file mode 100644
index 583a85d..0000000
--- a/experimental/app/src/components/Advanced.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { useState } from "react";
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "./ui/card";
-import { ConfigEditor } from "./ConfigEditor";
-import { Button, Input, Label } from "./ui";
-import { Check, Edit2, HelpCircle, X } from "lucide-react";
-
-export function Advanced() {
- const [upstreamValue, setUpstreamValue] = useState("https://mywildcloud.org");
- const [editingUpstream, setEditingUpstream] = useState(false);
- const [tempUpstream, setTempUpstream] = useState(upstreamValue);
- const handleUpstreamEdit = () => {
- setTempUpstream(upstreamValue);
- setEditingUpstream(true);
- };
-
- const handleUpstreamSave = () => {
- setUpstreamValue(tempUpstream);
- setEditingUpstream(false);
- };
-
- const handleUpstreamCancel = () => {
- setTempUpstream(upstreamValue);
- setEditingUpstream(false);
- };
-
- return (
-
-
-
- Advanced Configuration
-
- Advanced settings and system configuration options
-
-
-
-
-
- Configuration Management
-
-
- Edit the raw YAML configuration file directly. This provides full
- access to all configuration options.
-
-
-
-
-
- {/* Upstream Section */}
-
-
-
-
Upstream Configuration
-
- External service endpoint
-
-
-
-
-
-
- {!editingUpstream && (
-
-
- Edit
-
- )}
-
-
-
- {editingUpstream ? (
-
-
- Upstream URL
- setTempUpstream(e.target.value)}
- placeholder="https://example.com"
- className="mt-1"
- />
-
-
-
-
- Save
-
-
-
- Cancel
-
-
-
- ) : (
-
-
Upstream URL
-
- {upstreamValue}
-
-
- )}
-
-
- );
-}
diff --git a/experimental/app/src/components/AppSidebar.tsx b/experimental/app/src/components/AppSidebar.tsx
deleted file mode 100644
index d2ab6e5..0000000
--- a/experimental/app/src/components/AppSidebar.tsx
+++ /dev/null
@@ -1,416 +0,0 @@
-import { CheckCircle, Lock, Server, Play, Container, AppWindow, Settings, CloudLightning, Sun, Moon, Monitor, ChevronDown, Globe, Wifi, HardDrive } from 'lucide-react';
-import { cn } from '../lib/utils';
-import {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarHeader,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarRail,
-} from './ui/sidebar';
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
-import { useTheme } from '../contexts/ThemeContext';
-
-export type Phase = 'setup' | 'infrastructure' | 'cluster' | 'apps';
-export type Tab = Phase | 'advanced' | 'cloud' | 'central' | 'dns' | 'dhcp' | 'pxe';
-
-interface AppSidebarProps {
- currentTab: Tab;
- onTabChange: (tab: Tab) => void;
- completedPhases: Phase[];
-}
-
-
-export function AppSidebar({ currentTab, onTabChange, completedPhases }: AppSidebarProps) {
- const { theme, setTheme } = useTheme();
-
- const cycleTheme = () => {
- if (theme === 'light') {
- setTheme('dark');
- } else if (theme === 'dark') {
- setTheme('system');
- } else {
- setTheme('light');
- }
- };
-
- const getThemeIcon = () => {
- switch (theme) {
- case 'light':
- return ;
- case 'dark':
- return ;
- default:
- return ;
- }
- };
-
- const getThemeLabel = () => {
- switch (theme) {
- case 'light':
- return 'Light mode';
- case 'dark':
- return 'Dark mode';
- default:
- return 'System theme';
- }
- };
-
- const getTabStatus = (tab: Tab) => {
- // Non-phase tabs (like advanced and cloud) are always available
- if (tab === 'advanced' || tab === 'cloud') {
- return 'available';
- }
-
- // Central sub-tabs are available if setup phase is available or completed
- if (tab === 'central' || tab === 'dns' || tab === 'dhcp' || tab === 'pxe') {
- if (completedPhases.includes('setup')) {
- return 'completed';
- }
- return 'available';
- }
-
- // For phase tabs, check completion status
- if (completedPhases.includes(tab as Phase)) {
- return 'completed';
- }
-
- // Allow access to the first phase always
- if (tab === 'setup') {
- return 'available';
- }
-
- // Allow access to the next phase if the previous phase is completed
- if (tab === 'infrastructure' && completedPhases.includes('setup')) {
- return 'available';
- }
-
- if (tab === 'cluster' && completedPhases.includes('infrastructure')) {
- return 'available';
- }
-
- if (tab === 'apps' && completedPhases.includes('cluster')) {
- return 'available';
- }
-
- return 'locked';
- };
-
- return (
-
-
-
-
-
-
-
-
- {
- const status = getTabStatus('cloud');
- if (status !== 'locked') onTabChange('cloud');
- }}
- disabled={getTabStatus('cloud') === 'locked'}
- tooltip="Configure cloud settings and domains"
- className={cn(
- "transition-colors",
- getTabStatus('cloud') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
-
-
- Cloud
-
-
-
-
-
-
-
-
- Central
-
-
-
-
-
-
- {
- const status = getTabStatus('central');
- if (status !== 'locked') onTabChange('central');
- }}
- className={cn(
- "transition-colors",
- getTabStatus('central') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
-
-
- Central
-
-
-
-
- {
- const status = getTabStatus('dns');
- if (status !== 'locked') onTabChange('dns');
- }}
- className={cn(
- "transition-colors",
- getTabStatus('dns') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
-
-
- DNS
-
-
-
-
- {
- const status = getTabStatus('dhcp');
- if (status !== 'locked') onTabChange('dhcp');
- }}
- className={cn(
- "transition-colors",
- getTabStatus('dhcp') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
-
-
- DHCP
-
-
-
-
- {
- const status = getTabStatus('pxe');
- if (status !== 'locked') onTabChange('pxe');
- }}
- className={cn(
- "transition-colors",
- getTabStatus('pxe') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
-
-
- PXE
-
-
-
-
-
-
-
-
-
-
-
-
- Cluster
-
-
-
-
-
-
- {
- const status = getTabStatus('infrastructure');
- if (status !== 'locked') onTabChange('infrastructure');
- }}
- className={cn(
- "transition-colors",
- getTabStatus('infrastructure') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
- Cluster Nodes
-
-
-
-
- {
- const status = getTabStatus('cluster');
- if (status !== 'locked') onTabChange('cluster');
- }}
- className={cn(
- "transition-colors",
- getTabStatus('cluster') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
-
-
- Cluster Services
-
-
-
-
-
-
-
-
- {
- const status = getTabStatus('apps');
- if (status !== 'locked') onTabChange('apps');
- }}
- disabled={getTabStatus('apps') === 'locked'}
- tooltip="Install and manage applications"
- className={cn(
- "transition-colors",
- getTabStatus('apps') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
- Apps
-
-
-
-
- {
- const status = getTabStatus('advanced');
- if (status !== 'locked') onTabChange('advanced');
- }}
- disabled={getTabStatus('advanced') === 'locked'}
- tooltip="Advanced settings and system configuration"
- className={cn(
- "transition-colors",
- getTabStatus('advanced') === 'locked' && "opacity-50 cursor-not-allowed"
- )}
- >
-
-
-
- Advanced
-
-
-
-
-
-
-
-
- {getThemeIcon()}
- {getThemeLabel()}
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/AppsComponent.tsx b/experimental/app/src/components/AppsComponent.tsx
deleted file mode 100644
index 3002f9a..0000000
--- a/experimental/app/src/components/AppsComponent.tsx
+++ /dev/null
@@ -1,394 +0,0 @@
-import { useState } from 'react';
-import { Card } from './ui/card';
-import { Button } from './ui/button';
-import { Badge } from './ui/badge';
-import {
- AppWindow,
- Database,
- Globe,
- Shield,
- BarChart3,
- MessageSquare,
- Plus,
- Search,
- Settings,
- ExternalLink,
- CheckCircle,
- AlertCircle,
- Clock,
- Download,
- Trash2,
- BookOpen
-} from 'lucide-react';
-
-interface AppsComponentProps {
- onComplete?: () => void;
-}
-
-interface Application {
- id: string;
- name: string;
- description: string;
- category: 'database' | 'web' | 'security' | 'monitoring' | 'communication' | 'storage';
- status: 'available' | 'installing' | 'running' | 'error' | 'stopped';
- version?: string;
- namespace?: string;
- replicas?: number;
- resources?: {
- cpu: string;
- memory: string;
- };
- urls?: string[];
-}
-
-export function AppsComponent({ onComplete }: AppsComponentProps) {
- const [applications, setApplications] = useState([
- {
- id: 'postgres',
- name: 'PostgreSQL',
- description: 'Reliable, high-performance SQL database',
- category: 'database',
- status: 'running',
- version: 'v15.4',
- namespace: 'default',
- replicas: 1,
- resources: { cpu: '500m', memory: '1Gi' },
- urls: ['postgres://postgres.wildcloud.local:5432'],
- },
- {
- id: 'redis',
- name: 'Redis',
- description: 'In-memory data structure store',
- category: 'database',
- status: 'running',
- version: 'v7.2',
- namespace: 'default',
- replicas: 1,
- resources: { cpu: '250m', memory: '512Mi' },
- },
- {
- id: 'traefik-dashboard',
- name: 'Traefik Dashboard',
- description: 'Load balancer and reverse proxy dashboard',
- category: 'web',
- status: 'running',
- version: 'v3.0',
- namespace: 'kube-system',
- urls: ['https://traefik.wildcloud.local'],
- },
- {
- id: 'grafana',
- name: 'Grafana',
- description: 'Monitoring and observability dashboards',
- category: 'monitoring',
- status: 'installing',
- version: 'v10.2',
- namespace: 'monitoring',
- },
- {
- id: 'prometheus',
- name: 'Prometheus',
- description: 'Time-series monitoring and alerting',
- category: 'monitoring',
- status: 'running',
- version: 'v2.45',
- namespace: 'monitoring',
- replicas: 1,
- resources: { cpu: '1000m', memory: '2Gi' },
- },
- {
- id: 'vault',
- name: 'HashiCorp Vault',
- description: 'Secrets management and encryption',
- category: 'security',
- status: 'available',
- version: 'v1.15',
- },
- {
- id: 'minio',
- name: 'MinIO',
- description: 'High-performance object storage',
- category: 'storage',
- status: 'available',
- version: 'RELEASE.2023-12-07',
- },
- ]);
-
- const [searchTerm, setSearchTerm] = useState('');
- const [selectedCategory, setSelectedCategory] = useState('all');
-
- const getStatusIcon = (status: Application['status']) => {
- switch (status) {
- case 'running':
- return ;
- case 'error':
- return ;
- case 'installing':
- return ;
- case 'stopped':
- return ;
- default:
- return ;
- }
- };
-
- const getStatusBadge = (status: Application['status']) => {
- const variants = {
- available: 'secondary',
- installing: 'default',
- running: 'success',
- error: 'destructive',
- stopped: 'warning',
- } as const;
-
- const labels = {
- available: 'Available',
- installing: 'Installing',
- running: 'Running',
- error: 'Error',
- stopped: 'Stopped',
- };
-
- return (
-
- {labels[status]}
-
- );
- };
-
- const getCategoryIcon = (category: Application['category']) => {
- switch (category) {
- case 'database':
- return ;
- case 'web':
- return ;
- case 'security':
- return ;
- case 'monitoring':
- return ;
- case 'communication':
- return ;
- case 'storage':
- return ;
- default:
- return ;
- }
- };
-
- const handleAppAction = (appId: string, action: 'install' | 'start' | 'stop' | 'delete' | 'configure') => {
- console.log(`${action} app: ${appId}`);
- };
-
- const categories = ['all', 'database', 'web', 'security', 'monitoring', 'communication', 'storage'];
-
- const filteredApps = applications.filter(app => {
- const matchesSearch = app.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
- app.description.toLowerCase().includes(searchTerm.toLowerCase());
- const matchesCategory = selectedCategory === 'all' || app.category === selectedCategory;
- return matchesSearch && matchesCategory;
- });
-
- const runningApps = applications.filter(app => app.status === 'running').length;
-
- return (
-
- {/* Educational Intro Section */}
-
-
-
-
-
-
-
- What are Apps in your Personal Cloud?
-
-
- Apps are the useful programs that make your personal cloud valuable - like having a personal Netflix
- (media server), Google Drive (file storage), or Gmail (email server) running on your own hardware.
- Instead of relying on big tech companies, you control your data and services.
-
-
- Your cluster can run databases, web servers, photo galleries, password managers, backup services, and much more.
- Each app runs in its own secure container, so they don't interfere with each other and can be easily managed.
-
-
-
- Learn more about self-hosted applications
-
-
-
-
-
-
-
-
-
-
App Management
-
- Install and manage applications on your Kubernetes cluster
-
-
-
-
-
-
-
- setSearchTerm(e.target.value)}
- className="w-full pl-10 pr-4 py-2 border rounded-lg bg-background"
- />
-
-
- {categories.map(category => (
- setSelectedCategory(category)}
- className="capitalize"
- >
- {category}
-
- ))}
-
-
-
-
-
- {runningApps} applications running โข {applications.length} total available
-
-
-
- Add App
-
-
-
-
-
- {filteredApps.map((app) => (
-
-
-
- {getCategoryIcon(app.category)}
-
-
-
-
{app.name}
- {app.version && (
-
- {app.version}
-
- )}
- {getStatusIcon(app.status)}
-
-
{app.description}
-
- {app.status === 'running' && (
-
- {app.namespace && (
-
Namespace: {app.namespace}
- )}
- {app.replicas && (
-
Replicas: {app.replicas}
- )}
- {app.resources && (
-
Resources: {app.resources.cpu} CPU, {app.resources.memory} RAM
- )}
- {app.urls && app.urls.length > 0 && (
-
- URLs:
- {app.urls.map((url, index) => (
- window.open(url, '_blank')}
- >
-
- Access
-
- ))}
-
- )}
-
- )}
-
-
-
- {getStatusBadge(app.status)}
-
- {app.status === 'available' && (
- handleAppAction(app.id, 'install')}
- >
- Install
-
- )}
- {app.status === 'running' && (
- <>
- handleAppAction(app.id, 'configure')}
- >
-
-
- handleAppAction(app.id, 'stop')}
- >
- Stop
-
- >
- )}
- {app.status === 'stopped' && (
- handleAppAction(app.id, 'start')}
- >
- Start
-
- )}
- {(app.status === 'running' || app.status === 'stopped') && (
- handleAppAction(app.id, 'delete')}
- >
-
-
- )}
-
-
-
-
- ))}
-
-
- {filteredApps.length === 0 && (
-
-
- No applications found
-
- {searchTerm || selectedCategory !== 'all'
- ? 'Try adjusting your search or category filter'
- : 'Install your first application to get started'
- }
-
-
-
- Browse App Catalog
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/CentralComponent.tsx b/experimental/app/src/components/CentralComponent.tsx
deleted file mode 100644
index 85c78ee..0000000
--- a/experimental/app/src/components/CentralComponent.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { Card } from './ui/card';
-import { Button } from './ui/button';
-import { Server, Network, Settings, Clock, HelpCircle, CheckCircle, BookOpen, ExternalLink } from 'lucide-react';
-import { Input, Label } from './ui';
-
-export function CentralComponent() {
- return (
-
- {/* Educational Intro Section */}
-
-
-
-
-
-
-
- What is the Central Service?
-
-
- The Central Service is the "brain" of your personal cloud. It acts as the main coordinator that manages
- all the different services running on your network. Think of it like the control tower at an airport -
- it keeps track of what's happening, routes traffic between services, and ensures everything works together smoothly.
-
-
- This service handles configuration management, service discovery, and provides the web interface you're using right now.
-
-
-
- Learn more about service orchestration
-
-
-
-
-
-
-
-
-
-
-
-
Central Service
-
- Monitor and manage the central server service
-
-
-
-
-
-
Service Status
-
-
-
-
- IP Address: 192.168.8.50
-
-
-
- Network: 192.168.8.0/24
-
-
-
- Version: 1.0.0 (update available)
-
-
-
- Age: 12s
-
-
-
- Platform: ARM
-
-
-
- File permissions: Good
-
-
-
-
-
-
-
Interface
-
-
-
-
-
-
-
-
-
-
- console.log('Update service')}>
- Update
-
- console.log('Restart service')}>
- Restart
-
- console.log('View log')}>
- View log
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/CloudComponent.tsx b/experimental/app/src/components/CloudComponent.tsx
deleted file mode 100644
index bdd32e9..0000000
--- a/experimental/app/src/components/CloudComponent.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import { useState } from "react";
-import { Card } from "./ui/card";
-import { Button } from "./ui/button";
-import { Cloud, HelpCircle, Edit2, Check, X } from "lucide-react";
-import { Input, Label } from "./ui";
-
-export function CloudComponent() {
- const [domainValue, setDomainValue] = useState("cloud.payne.io");
- const [internalDomainValue, setInternalDomainValue] = useState(
- "internal.cloud.payne.io"
- );
-
- const [editingDomains, setEditingDomains] = useState(false);
-
- const [tempDomain, setTempDomain] = useState(domainValue);
- const [tempInternalDomain, setTempInternalDomain] =
- useState(internalDomainValue);
-
- const handleDomainsEdit = () => {
- setTempDomain(domainValue);
- setTempInternalDomain(internalDomainValue);
- setEditingDomains(true);
- };
-
- const handleDomainsSave = () => {
- setDomainValue(tempDomain);
- setInternalDomainValue(tempInternalDomain);
- setEditingDomains(false);
- };
-
- const handleDomainsCancel = () => {
- setTempDomain(domainValue);
- setTempInternalDomain(internalDomainValue);
- setEditingDomains(false);
- };
-
- return (
-
-
-
-
-
-
-
-
Cloud Configuration
-
- Configure top-level cloud settings and domains
-
-
-
-
-
- {/* Domains Section */}
-
-
-
-
Domain Configuration
-
- Public and internal domain settings
-
-
-
-
-
-
- {!editingDomains && (
-
-
- Edit
-
- )}
-
-
-
- {editingDomains ? (
-
-
- Public Domain
- setTempDomain(e.target.value)}
- placeholder="example.com"
- className="mt-1"
- />
-
-
- Internal Domain
- setTempInternalDomain(e.target.value)}
- placeholder="internal.example.com"
- className="mt-1"
- />
-
-
-
-
- Save
-
-
-
- Cancel
-
-
-
- ) : (
-
-
-
Public Domain
-
- {domainValue}
-
-
-
-
Internal Domain
-
- {internalDomainValue}
-
-
-
- )}
-
-
-
-
- );
-}
diff --git a/experimental/app/src/components/ClusterNodesComponent.tsx b/experimental/app/src/components/ClusterNodesComponent.tsx
deleted file mode 100644
index 2d7165d..0000000
--- a/experimental/app/src/components/ClusterNodesComponent.tsx
+++ /dev/null
@@ -1,378 +0,0 @@
-import { useState } from 'react';
-import { Card } from './ui/card';
-import { Button } from './ui/button';
-import { Badge } from './ui/badge';
-import { Cpu, HardDrive, Network, Monitor, Plus, CheckCircle, AlertCircle, Clock, BookOpen, ExternalLink } from 'lucide-react';
-
-interface ClusterNodesComponentProps {
- onComplete?: () => void;
-}
-
-interface Node {
- id: string;
- name: string;
- type: 'controller' | 'worker' | 'unassigned';
- status: 'pending' | 'connecting' | 'connected' | 'healthy' | 'error';
- ipAddress?: string;
- macAddress: string;
- osVersion?: string;
- specs: {
- cpu: string;
- memory: string;
- storage: string;
- };
-}
-
-export function ClusterNodesComponent({ onComplete }: ClusterNodesComponentProps) {
- const [currentOsVersion, setCurrentOsVersion] = useState('v13.0.5');
- const [nodes, setNodes] = useState([
- {
- id: 'controller-1',
- name: 'Controller Node 1',
- type: 'controller',
- status: 'healthy',
- macAddress: '00:1A:2B:3C:4D:5E',
- osVersion: 'v13.0.4',
- specs: {
- cpu: '4 cores',
- memory: '8GB RAM',
- storage: '120GB SSD',
- },
- },
- {
- id: 'worker-1',
- name: 'Worker Node 1',
- type: 'worker',
- status: 'healthy',
- macAddress: '00:1A:2B:3C:4D:5F',
- osVersion: 'v13.0.5',
- specs: {
- cpu: '8 cores',
- memory: '16GB RAM',
- storage: '500GB SSD',
- },
- },
- {
- id: 'worker-2',
- name: 'Worker Node 2',
- type: 'worker',
- status: 'healthy',
- macAddress: '00:1A:2B:3C:4D:60',
- osVersion: 'v13.0.4',
- specs: {
- cpu: '8 cores',
- memory: '16GB RAM',
- storage: '500GB SSD',
- },
- },
- {
- id: 'node-1',
- name: 'Node 1',
- type: 'unassigned',
- status: 'pending',
- macAddress: '00:1A:2B:3C:4D:5E',
- osVersion: 'v13.0.5',
- specs: {
- cpu: '4 cores',
- memory: '8GB RAM',
- storage: '120GB SSD',
- },
- },
- {
- id: 'node-2',
- name: 'Node 2',
- type: 'unassigned',
- status: 'pending',
- macAddress: '00:1A:2B:3C:4D:5F',
- osVersion: 'v13.0.5',
- specs: {
- cpu: '8 cores',
- memory: '16GB RAM',
- storage: '500GB SSD',
- },
- },
- ]);
-
- const getStatusIcon = (status: Node['status']) => {
- switch (status) {
- case 'connected':
- return ;
- case 'error':
- return ;
- case 'connecting':
- return ;
- default:
- return ;
- }
- };
-
- const getStatusBadge = (status: Node['status']) => {
- const variants = {
- pending: 'secondary',
- connecting: 'default',
- connected: 'success',
- healthy: 'success',
- error: 'destructive',
- } as const;
-
- const labels = {
- pending: 'Pending',
- connecting: 'Connecting',
- connected: 'Connected',
- healthy: 'Healthy',
- error: 'Error',
- };
-
- return (
-
- {labels[status]}
-
- );
- };
-
- const getTypeIcon = (type: Node['type']) => {
- return type === 'controller' ? (
-
- ) : (
-
- );
- };
-
- const handleNodeAction = (nodeId: string, action: 'connect' | 'retry' | 'upgrade_node') => {
- console.log(`${action} node: ${nodeId}`);
- };
-
- const connectedNodes = nodes.filter(node => node.status === 'connected').length;
- const assignedNodes = nodes.filter(node => node.type !== 'unassigned');
- const unassignedNodes = nodes.filter(node => node.type === 'unassigned');
- const totalNodes = nodes.length;
- const isComplete = connectedNodes === totalNodes;
-
- return (
-
- {/* Educational Intro Section */}
-
-
-
-
-
-
-
- What are Cluster Nodes?
-
-
- Think of cluster nodes as the "workers" in your personal cloud factory. Each node is a separate computer
- that contributes its processing power, memory, and storage to the overall cluster. Some nodes are "controllers"
- (like managers) that coordinate the work, while others are "workers" that do the heavy lifting.
-
-
- By connecting multiple computers together as nodes, you create a powerful, resilient system where if one
- computer fails, the others can pick up the work. This is how you scale your personal cloud from one machine to many.
-
-
-
- Learn more about distributed computing
-
-
-
-
-
-
-
-
-
-
-
-
Cluster Nodes
-
- Connect machines to your wild-cloud
-
-
-
-
-
-
Assigned Nodes ({assignedNodes.length}/{totalNodes})
- {assignedNodes.map((node) => (
-
-
-
- {getTypeIcon(node.type)}
-
-
-
-
{node.name}
-
- {node.type}
-
- {getStatusIcon(node.status)}
-
-
- MAC: {node.macAddress}
- {node.ipAddress && ` โข IP: ${node.ipAddress}`}
-
-
-
-
- {node.specs.cpu}
-
-
-
- {node.specs.memory}
-
-
-
- {node.specs.storage}
-
- {node.osVersion && (
-
-
- OS: {node.osVersion}
-
-
- )}
-
-
-
- {getStatusBadge(node.status)}
- {node.osVersion !== currentOsVersion && (
- handleNodeAction(node.id, 'upgrade_node')}
- >
- Upgrade OS
-
- )}
- {node.status === 'error' && (
- handleNodeAction(node.id, 'retry')}
- >
- Retry
-
- )}
-
-
-
- ))}
-
-
- Unassigned Nodes ({unassignedNodes.length}/{totalNodes})
-
- {unassignedNodes.map((node) => (
-
-
-
- {getTypeIcon(node.type)}
-
-
-
-
{node.name}
-
- {node.type}
-
- {getStatusIcon(node.status)}
-
-
- MAC: {node.macAddress}
- {node.ipAddress && ` โข IP: ${node.ipAddress}`}
-
-
-
-
- {node.specs.cpu}
-
-
-
- {node.specs.memory}
-
-
-
- {node.specs.storage}
-
-
-
-
- {getStatusBadge(node.status)}
- {node.status === 'pending' && (
- handleNodeAction(node.id, 'connect')}
- >
- Assign
-
- )}
- {node.status === 'error' && (
- handleNodeAction(node.id, 'retry')}
- >
- Retry
-
- )}
-
-
-
- ))}
-
-
- {isComplete && (
-
-
-
-
- Infrastructure Ready!
-
-
-
- All nodes are connected and ready for Kubernetes installation.
-
-
- Continue to Kubernetes Installation
-
-
- )}
-
-
-
- PXE Boot Instructions
-
-
-
- 1
-
-
-
Power on your nodes
-
- Ensure network boot (PXE) is enabled in BIOS/UEFI settings
-
-
-
-
-
- 2
-
-
-
Connect to the wild-cloud network
-
- Nodes will automatically receive IP addresses via DHCP
-
-
-
-
-
- 3
-
-
-
Boot Talos Linux
-
- Nodes will automatically download and boot Talos Linux via PXE
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/ClusterServicesComponent.tsx b/experimental/app/src/components/ClusterServicesComponent.tsx
deleted file mode 100644
index 786cb8b..0000000
--- a/experimental/app/src/components/ClusterServicesComponent.tsx
+++ /dev/null
@@ -1,299 +0,0 @@
-import { useState } from 'react';
-import { Card } from './ui/card';
-import { Button } from './ui/button';
-import { Badge } from './ui/badge';
-import { Container, Shield, Network, Database, CheckCircle, AlertCircle, Clock, Terminal, FileText, BookOpen, ExternalLink } from 'lucide-react';
-
-interface ClusterServicesComponentProps {
- onComplete?: () => void;
-}
-
-interface ClusterComponent {
- id: string;
- name: string;
- description: string;
- status: 'pending' | 'installing' | 'ready' | 'error';
- version?: string;
- logs?: string[];
-}
-
-export function ClusterServicesComponent({ onComplete }: ClusterServicesComponentProps) {
- const [components, setComponents] = useState([
- {
- id: 'talos-config',
- name: 'Talos Configuration',
- description: 'Generate and apply Talos cluster configuration',
- status: 'pending',
- },
- {
- id: 'kubernetes-bootstrap',
- name: 'Kubernetes Bootstrap',
- description: 'Initialize Kubernetes control plane',
- status: 'pending',
- version: 'v1.29.0',
- },
- {
- id: 'cni-plugin',
- name: 'Container Network Interface',
- description: 'Install and configure Cilium CNI',
- status: 'pending',
- version: 'v1.14.5',
- },
- {
- id: 'storage-class',
- name: 'Storage Classes',
- description: 'Configure persistent volume storage',
- status: 'pending',
- },
- {
- id: 'ingress-controller',
- name: 'Ingress Controller',
- description: 'Install Traefik ingress controller',
- status: 'pending',
- version: 'v3.0.0',
- },
- {
- id: 'monitoring',
- name: 'Cluster Monitoring',
- description: 'Deploy Prometheus and Grafana stack',
- status: 'pending',
- },
- ]);
-
- const [showLogs, setShowLogs] = useState(null);
-
- const getStatusIcon = (status: ClusterComponent['status']) => {
- switch (status) {
- case 'ready':
- return ;
- case 'error':
- return ;
- case 'installing':
- return ;
- default:
- return null;
- }
- };
-
- const getStatusBadge = (status: ClusterComponent['status']) => {
- const variants = {
- pending: 'secondary',
- installing: 'default',
- ready: 'success',
- error: 'destructive',
- } as const;
-
- const labels = {
- pending: 'Pending',
- installing: 'Installing',
- ready: 'Ready',
- error: 'Error',
- };
-
- return (
-
- {labels[status]}
-
- );
- };
-
- const getComponentIcon = (id: string) => {
- switch (id) {
- case 'talos-config':
- return ;
- case 'kubernetes-bootstrap':
- return ;
- case 'cni-plugin':
- return ;
- case 'storage-class':
- return ;
- case 'ingress-controller':
- return ;
- case 'monitoring':
- return ;
- default:
- return ;
- }
- };
-
- const handleComponentAction = (componentId: string, action: 'install' | 'retry') => {
- console.log(`${action} component: ${componentId}`);
- };
-
- const readyComponents = components.filter(component => component.status === 'ready').length;
- const totalComponents = components.length;
- const isComplete = readyComponents === totalComponents;
-
- return (
-
- {/* Educational Intro Section */}
-
-
-
-
-
-
-
- What are Cluster Services?
-
-
- Cluster services are like the "essential utilities" that make your personal cloud actually work. Just like a city
- needs electricity, water, and roads, your cluster needs networking, storage, monitoring, and security services.
- These services run automatically in the background to keep everything functioning smoothly.
-
-
- Services like Kubernetes orchestration, container networking, ingress routing, and monitoring work together to
- create a robust platform where you can easily deploy and manage your applications.
-
-
-
- Learn more about Kubernetes services
-
-
-
-
-
-
-
-
-
-
-
-
Cluster Services
-
- Install and configure essential cluster services
-
-
-
-
-
-
- endpoint: civil
- endpointIp: 192.168.8.240
- kubernetes:
- config: /home/payne/.kube/config
- context: default
- loadBalancerRange: 192.168.8.240-192.168.8.250
- dashboard:
- adminUsername: admin
- certManager:
- namespace: cert-manager
- cloudflare:
- domain: payne.io
- ownerId: cloud-payne-io-cluster
-
-
-
-
-
- {components.map((component) => (
-
-
-
- {getComponentIcon(component.id)}
-
-
-
-
{component.name}
- {component.version && (
-
- {component.version}
-
- )}
- {getStatusIcon(component.status)}
-
-
{component.description}
-
-
- {getStatusBadge(component.status)}
- {(component.status === 'installing' || component.status === 'error') && (
- setShowLogs(showLogs === component.id ? null : component.id)}
- >
-
- Logs
-
- )}
- {component.status === 'pending' && (
- handleComponentAction(component.id, 'install')}
- >
- Install
-
- )}
- {component.status === 'error' && (
- handleComponentAction(component.id, 'retry')}
- >
- Retry
-
- )}
-
-
-
- {showLogs === component.id && (
-
-
-
Installing {component.name}...
-
โ Checking prerequisites
-
โ Downloading manifests
- {component.status === 'installing' && (
-
โณ Applying configuration...
- )}
- {component.status === 'error' && (
-
โ Installation failed: timeout waiting for pods
- )}
-
-
- )}
-
- ))}
-
-
- {isComplete && (
-
-
-
-
- Kubernetes Cluster Ready!
-
-
-
- Your Kubernetes cluster is fully configured and ready for application deployment.
-
-
- Continue to App Management
-
-
- )}
-
-
-
- Cluster Information
-
-
-
Control Plane
-
-
โข API Server: https://cluster.wildcloud.local:6443
-
โข Nodes: 1 controller, 2 workers
-
โข Version: Kubernetes v1.29.0
-
-
-
-
Network Configuration
-
-
โข Pod CIDR: 10.244.0.0/16
-
โข Service CIDR: 10.96.0.0/12
-
โข CNI: Cilium v1.14.5
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/ConfigEditor.tsx b/experimental/app/src/components/ConfigEditor.tsx
deleted file mode 100644
index 1f41877..0000000
--- a/experimental/app/src/components/ConfigEditor.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { useState, useEffect } from 'react';
-import { Settings, Save, X } from 'lucide-react';
-import { useConfigYaml } from '../hooks';
-import { Button, Textarea } from './ui';
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger} from '@/components/ui/dialog';
-
-export function ConfigEditor() {
- const { yamlContent, isLoading, error, isEndpointMissing, updateYaml, refetch } = useConfigYaml();
-
- const [editedContent, setEditedContent] = useState('');
- const [hasChanges, setHasChanges] = useState(false);
-
- // Update edited content when YAML content changes
- useEffect(() => {
- if (yamlContent) {
- setEditedContent(yamlContent);
- setHasChanges(false);
- }
- }, [yamlContent]);
-
- // Track changes
- useEffect(() => {
- setHasChanges(editedContent !== yamlContent);
- }, [editedContent, yamlContent]);
-
- const handleSave = () => {
- if (!hasChanges) return;
-
- updateYaml(editedContent, {
- onSuccess: () => {
- setHasChanges(false);
- },
- onError: (err) => {
- console.error('Failed to update config:', err);
- }
- });
- };
-
- const handleOpenChange = (open: boolean) => {
- if (!open && hasChanges) {
- if (!window.confirm('You have unsaved changes. Close anyway?')) {
- return;
- }
- }
- if (open) {
- refetch();
- }
- };
-
- return (
-
-
-
-
- Config
-
-
-
-
-
-
- Configuration Editor
-
-
- Edit the raw YAML configuration file. This provides direct access to all configuration options.
-
-
-
-
- {error && error instanceof Error && error.message && (
-
-
- Error: {error.message}
-
-
- )}
-
- {isEndpointMissing && (
-
-
- Backend endpoints missing. Raw YAML editing not available.
-
-
- )}
-
-
-
-
-
-
- Cancel
-
-
-
- Update Config
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/ConfigurationForm.tsx b/experimental/app/src/components/ConfigurationForm.tsx
deleted file mode 100644
index 6df8f53..0000000
--- a/experimental/app/src/components/ConfigurationForm.tsx
+++ /dev/null
@@ -1,297 +0,0 @@
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { FileText, Check, AlertCircle, Loader2 } from 'lucide-react';
-import { useConfig, useMessages } from '../hooks';
-import { configFormSchema, defaultConfigValues, type ConfigFormData } from '../schemas/config';
-import {
- Card,
- CardHeader,
- CardTitle,
- CardContent,
- Button,
- Form,
- FormField,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- Input,
-} from './ui';
-
-export const ConfigurationForm = () => {
- const {
- config,
- isConfigured,
- showConfigSetup,
- isLoading,
- isCreating,
- error,
- createConfig,
- refetch
- } = useConfig();
-
- const form = useForm({
- resolver: zodResolver(configFormSchema),
- defaultValues: defaultConfigValues,
- });
-
- const onSubmit = (data: ConfigFormData) => {
- createConfig(data);
- };
-
- return (
-
-
- Configuration (With Form Validation)
-
-
- refetch()} disabled={isLoading} variant="outline">
-
- {isLoading ? 'Loading...' : 'Reload Configuration'}
-
-
- {error && (
-
-
-
-
Configuration Error
-
{error.message}
-
-
- )}
-
- {showConfigSetup && (
-
-
-
Initial Configuration Setup
-
Configure your wild-cloud central server settings with real-time validation.
-
-
-
-
-
- )}
-
- {config && isConfigured && (
-
-
-
- โ Configuration loaded successfully
-
-
-
- {JSON.stringify(config, null, 2)}
-
-
- )}
-
-
- Form Validation Status: {form.formState.isValid ? 'โ Valid' : 'โ Has Errors'} |
- Errors: {Object.keys(form.formState.errors).length}
-
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/ConfigurationSection.tsx b/experimental/app/src/components/ConfigurationSection.tsx
deleted file mode 100644
index f9c51f2..0000000
--- a/experimental/app/src/components/ConfigurationSection.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { FileText, Check, AlertCircle, Loader2 } from 'lucide-react';
-import { useConfig, useMessages } from '../hooks';
-import { configFormSchema, defaultConfigValues, type ConfigFormData } from '../schemas/config';
-import { Message } from './Message';
-import { Card, CardHeader, CardTitle, CardContent, Button, Form, FormField, FormItem, FormLabel, FormControl, FormMessage, Input } from './ui';
-
-export const ConfigurationSection = () => {
- const {
- config,
- isConfigured,
- showConfigSetup,
- isLoading,
- isCreating,
- error,
- createConfig,
- refetch
- } = useConfig();
- const { messages } = useMessages();
-
- const form = useForm({
- resolver: zodResolver(configFormSchema),
- defaultValues: defaultConfigValues,
- });
-
- const onSubmit = (data: ConfigFormData) => {
- createConfig(data);
- };
-
- return (
-
-
- Configuration
-
-
- refetch()} disabled={isLoading} variant="outline">
-
- {isLoading ? 'Loading...' : 'Reload Configuration'}
-
-
- {error && (
-
-
-
-
Configuration Error
-
{error.message}
-
-
- )}
-
-
-
- {showConfigSetup && (
-
-
-
Initial Configuration Setup
-
Configure key settings for your wild-cloud central server:
-
-
-
-
-
- )}
-
- {config && isConfigured && (
-
-
-
- โ Configuration loaded successfully
-
-
-
- {JSON.stringify(config, null, 2)}
-
-
- )}
-
- {/* Debug info */}
-
- React Query Status: isLoading={isLoading.toString()}, isConfigured={isConfigured.toString()}, showSetup={showConfigSetup.toString()}
-
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/DhcpComponent.tsx b/experimental/app/src/components/DhcpComponent.tsx
deleted file mode 100644
index 2e10cd0..0000000
--- a/experimental/app/src/components/DhcpComponent.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { Card } from './ui/card';
-import { Button } from './ui/button';
-import { Wifi, HelpCircle, BookOpen, ExternalLink } from 'lucide-react';
-import { Input, Label } from './ui';
-
-export function DhcpComponent() {
- return (
-
- {/* Educational Intro Section */}
-
-
-
-
-
-
-
- What is DHCP?
-
-
- DHCP (Dynamic Host Configuration Protocol) is like an automatic "address assignment system" for your network.
- When a device joins your network, DHCP automatically gives it an IP address, tells it how to connect to the internet,
- and provides other network settings - no manual configuration needed!
-
-
- Without DHCP, you'd need to manually assign IP addresses to every device. DHCP makes it so you can just connect
- a phone, laptop, or smart device and it automatically gets everything it needs to work on your network.
-
-
-
- Learn more about DHCP
-
-
-
-
-
-
-
-
-
-
-
-
DHCP Configuration
-
- Manage DHCP settings and IP address allocation
-
-
-
-
-
-
- Status:
- Active
-
-
-
-
-
- console.log('View DHCP clients')}>
- View Clients
-
- console.log('Configure DHCP')}>
- Configure
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/DnsComponent.tsx b/experimental/app/src/components/DnsComponent.tsx
deleted file mode 100644
index 9915df7..0000000
--- a/experimental/app/src/components/DnsComponent.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Card } from './ui/card';
-import { Button } from './ui/button';
-import { Globe, CheckCircle, BookOpen, ExternalLink } from 'lucide-react';
-
-export function DnsComponent() {
- return (
-
- {/* Educational Intro Section */}
-
-
-
-
-
-
-
- What is DNS?
-
-
- DNS (Domain Name System) is like the "phone book" of the internet. Instead of remembering complex IP addresses
- like "192.168.1.100", you can use friendly names like "my-server.local". When you type a name, DNS translates
- it to the correct IP address so your devices can find each other.
-
-
- Your personal cloud runs its own DNS service so devices can easily find services like "photos.home" or "media.local"
- without needing to remember numbers.
-
-
-
- Learn more about DNS
-
-
-
-
-
-
-
-
-
-
-
-
DNS Configuration
-
- Manage DNS settings and domain resolution
-
-
-
-
-
-
-
- Local resolution: Active
-
-
-
-
DNS Status
-
- DNS service is running and resolving domains correctly.
-
-
-
-
- console.log('Test DNS')}>
- Test DNS
-
- console.log('Configure DNS')}>
- Configure
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/DnsmasqSection.tsx b/experimental/app/src/components/DnsmasqSection.tsx
deleted file mode 100644
index 2a51edf..0000000
--- a/experimental/app/src/components/DnsmasqSection.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Settings, RotateCcw, AlertCircle } from 'lucide-react';
-import { useDnsmasq, useMessages } from '../hooks';
-import { Message } from './Message';
-import { Card, CardHeader, CardTitle, CardContent, Button } from './ui';
-
-export const DnsmasqSection = () => {
- const {
- dnsmasqConfig,
- generateConfig,
- isGenerating,
- generateError,
- restart,
- isRestarting,
- restartError,
- restartData
- } = useDnsmasq();
- const { messages, setMessage } = useMessages();
-
- // Handle success/error messaging
- if (generateError) {
- setMessage('dnsmasq', `Failed to generate dnsmasq config: ${generateError.message}`, 'error');
- } else if (dnsmasqConfig) {
- setMessage('dnsmasq', 'Dnsmasq config generated successfully', 'success');
- }
-
- if (restartError) {
- setMessage('dnsmasq', `Failed to restart dnsmasq: ${restartError.message}`, 'error');
- } else if (restartData) {
- setMessage('dnsmasq', `Dnsmasq restart: ${restartData.status}`, 'success');
- }
- return (
-
-
- DNS/DHCP Management
-
-
-
- generateConfig()} disabled={isGenerating} variant="outline">
-
- {isGenerating ? 'Generating...' : 'Generate Dnsmasq Config'}
-
- restart()} disabled={isRestarting} variant="outline">
-
- {isRestarting ? 'Restarting...' : 'Restart Dnsmasq'}
-
-
-
- {generateError && (
-
-
-
-
Generation Error
-
{generateError.message}
-
-
- )}
-
- {restartError && (
-
-
-
-
Restart Error
-
{restartError.message}
-
-
- )}
-
- {restartData && (
-
-
- โ Dnsmasq restart: {restartData.status}
-
-
- )}
-
-
-
- {dnsmasqConfig && (
-
- {dnsmasqConfig}
-
- )}
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/ErrorBoundary.tsx b/experimental/app/src/components/ErrorBoundary.tsx
deleted file mode 100644
index 47c3702..0000000
--- a/experimental/app/src/components/ErrorBoundary.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import React, { Component as ReactComponent, ErrorInfo, ReactNode } from 'react';
-import { AlertTriangle, RefreshCw, Home } from 'lucide-react';
-import { Button } from './ui/button';
-import { Card, CardHeader, CardTitle, CardContent } from './ui/card';
-
-interface Props {
- children?: ReactNode;
- fallback?: ReactNode;
- onError?: (error: Error, errorInfo: ErrorInfo) => void;
-}
-
-interface State {
- hasError: boolean;
- error?: Error;
- errorInfo?: ErrorInfo;
-}
-
-export class ErrorBoundary extends ReactComponent {
- public state: State = {
- hasError: false,
- };
-
- public static getDerivedStateFromError(error: Error): State {
- // Update state so the next render will show the fallback UI
- return { hasError: true, error };
- }
-
- public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
- console.error('ErrorBoundary caught an error:', error, errorInfo);
-
- this.setState({
- error,
- errorInfo,
- });
-
- // Call optional error handler
- if (this.props.onError) {
- this.props.onError(error, errorInfo);
- }
- }
-
- private handleReset = () => {
- this.setState({ hasError: false, error: undefined, errorInfo: undefined });
- };
-
- private handleReload = () => {
- window.location.reload();
- };
-
- public render() {
- if (this.state.hasError) {
- // If a custom fallback is provided, use it
- if (this.props.fallback) {
- return this.props.fallback;
- }
-
- // Default error UI
- return ;
- }
-
- return this.props.children;
- }
-}
-
-interface ErrorFallbackProps {
- error?: Error;
- errorInfo?: ErrorInfo;
- onReset: () => void;
- onReload: () => void;
-}
-
-export const ErrorFallback: React.FC = ({
- error,
- errorInfo,
- onReset,
- onReload
-}) => {
- const isDev = process.env.NODE_ENV === 'development';
-
- return (
-
-
-
-
-
-
-
- Something went wrong
-
-
- The application encountered an unexpected error
-
-
-
-
-
-
-
- Don't worry, your data is safe. You can try the following options:
-
-
-
-
- Try Again
-
-
-
- Reload Page
-
-
-
-
- {isDev && error && (
-
-
- Error Details (Development Mode)
-
-
-
-
-
Error Message:
-
- {error.message}
-
-
-
- {error.stack && (
-
-
Stack Trace:
-
- {error.stack}
-
-
- )}
-
- {errorInfo?.componentStack && (
-
-
Component Stack:
-
- {errorInfo.componentStack}
-
-
- )}
-
-
- )}
-
- {!isDev && (
-
-
- If this problem persists, please contact support with details about what you were doing when the error occurred.
-
-
- )}
-
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/ErrorTester.tsx b/experimental/app/src/components/ErrorTester.tsx
deleted file mode 100644
index cf5109d..0000000
--- a/experimental/app/src/components/ErrorTester.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useState } from 'react';
-import { Button } from './ui/button';
-import { Card, CardHeader, CardTitle, CardContent } from './ui/card';
-import { AlertTriangle } from 'lucide-react';
-
-// Component that can trigger errors for testing
-export const ErrorTester = () => {
- const [shouldThrow, setShouldThrow] = useState(false);
-
- if (shouldThrow) {
- throw new Error('Test error: This is a simulated component crash for testing the error boundary.');
- }
-
- return (
-
-
-
-
- Error Boundary Tester
-
-
-
-
- This component can be used to test the error boundary functionality in development.
-
-
-
-
- โ ๏ธ Warning: Clicking the button below will intentionally crash this component to test error handling.
-
-
-
- setShouldThrow(true)}
- variant="destructive"
- size="sm"
- >
- Trigger Error
-
-
-
- Development tool - remove from production builds
-
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/Message.tsx b/experimental/app/src/components/Message.tsx
deleted file mode 100644
index 90adf53..0000000
--- a/experimental/app/src/components/Message.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { AlertCircle, CheckCircle, Info } from 'lucide-react';
-import type { Message as MessageType } from '../types';
-import { cn } from '@/lib/utils';
-
-interface MessageProps {
- message?: MessageType;
-}
-
-export const Message = ({ message }: MessageProps) => {
- if (!message) return null;
-
- const getIcon = () => {
- switch (message.type) {
- case 'error':
- return ;
- case 'success':
- return ;
- default:
- return ;
- }
- };
-
- const getVariantStyles = () => {
- switch (message.type) {
- case 'error':
- return 'border-destructive/50 text-destructive bg-destructive/10';
- case 'success':
- return 'border-green-500/50 text-green-700 bg-green-50 dark:bg-green-950 dark:text-green-400';
- default:
- return 'border-blue-500/50 text-blue-700 bg-blue-50 dark:bg-blue-950 dark:text-blue-400';
- }
- };
-
- return (
-
- {getIcon()}
- {message.message}
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/PxeAssetsSection.tsx b/experimental/app/src/components/PxeAssetsSection.tsx
deleted file mode 100644
index be35d16..0000000
--- a/experimental/app/src/components/PxeAssetsSection.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Download, AlertCircle } from 'lucide-react';
-import { useAssets, useMessages } from '../hooks';
-import { Message } from './Message';
-import { Card, CardHeader, CardTitle, CardContent, Button } from './ui';
-
-export const PxeAssetsSection = () => {
- const { downloadAssets, isDownloading, error, data } = useAssets();
- const { messages, setMessage } = useMessages();
-
- // Handle success/error messaging
- if (error) {
- setMessage('assets', `Failed to download assets: ${error.message}`, 'error');
- } else if (data) {
- setMessage('assets', `PXE Assets: ${data.status}`, 'success');
- }
- return (
-
-
- PXE Boot Assets
-
-
- downloadAssets()} disabled={isDownloading} variant="outline">
-
- {isDownloading ? 'Downloading...' : 'Download/Update PXE Assets'}
-
-
- {error && (
-
-
-
-
Download Error
-
{error.message}
-
-
- )}
-
- {data && (
-
-
- โ PXE Assets: {data.status}
-
-
- )}
-
-
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/PxeComponent.tsx b/experimental/app/src/components/PxeComponent.tsx
deleted file mode 100644
index 3a544f8..0000000
--- a/experimental/app/src/components/PxeComponent.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Card } from './ui/card';
-import { Button } from './ui/button';
-import { HardDrive, BookOpen, ExternalLink } from 'lucide-react';
-
-export function PxeComponent() {
- return (
-
- {/* Educational Intro Section */}
-
-
-
-
-
-
-
- What is PXE Boot?
-
-
- PXE (Preboot Execution Environment) is like having a "network installer" that can set up computers without
- needing USB drives or DVDs. When you turn on a computer, instead of booting from its hard drive, it can boot
- from the network and automatically install an operating system or run diagnostics.
-
-
- This is especially useful for setting up multiple computers in your cloud infrastructure. PXE can automatically
- install and configure the same operating system on many machines, making it easy to expand your personal cloud.
-
-
-
- Learn more about network booting
-
-
-
-
-
-
-
-
-
-
-
-
PXE Configuration
-
- Manage PXE boot assets and network boot configuration
-
-
-
-
-
-
- Status:
- Active
-
-
-
-
Boot Assets
-
- Manage Talos Linux boot images and iPXE configurations for network booting.
-
-
-
-
- console.log('View assets')}>
- View Assets
-
- console.log('Download PXE assets')}>
- Download Assets
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/StatusSection.tsx b/experimental/app/src/components/StatusSection.tsx
deleted file mode 100644
index b59eb10..0000000
--- a/experimental/app/src/components/StatusSection.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Server, RefreshCw } from 'lucide-react';
-import { useStatus } from '../hooks';
-import { Card, CardHeader, CardTitle, CardContent, Button } from './ui';
-
-export const StatusSection = () => {
- const { data: status, isLoading, error, refetch } = useStatus();
-
- return (
-
-
-
-
- Server Status
-
-
-
-
- Current Status
- refetch()}
- disabled={isLoading}
- variant="outline"
- size="sm"
- >
-
- {isLoading ? 'Refreshing...' : 'Refresh'}
-
-
-
- {error && (
-
-
- Failed to fetch status: {error.message}
-
-
- )}
-
- {status && (
-
-
-
-
Status
-
{status.status}
-
-
-
Version
-
{status.version}
-
-
-
- {status.uptime && (
-
-
Uptime
-
{status.uptime}
-
- )}
-
-
- {JSON.stringify(status, null, 2)}
-
-
- )}
-
- {isLoading && !status && (
-
-
-
- )}
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/SystemStatus.tsx b/experimental/app/src/components/SystemStatus.tsx
deleted file mode 100644
index d68f764..0000000
--- a/experimental/app/src/components/SystemStatus.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { RefreshCw, Activity, AlertCircle } from 'lucide-react';
-import { useStatus, useHealth, useMessages } from '../hooks';
-import { formatTimestamp } from '../utils/formatters';
-import { Message } from './Message';
-import { Card, CardHeader, CardTitle, CardContent, Button, Badge } from './ui';
-
-export const SystemStatus = () => {
- const { data: status, isLoading: statusLoading, error: statusError, refetch } = useStatus();
- const { mutate: checkHealth, isPending: healthLoading, error: healthError, data: healthData } = useHealth();
- const { messages, setMessage } = useMessages();
-
- // Handle health check messaging
- if (healthError) {
- setMessage('health', `Health check failed: ${healthError.message}`, 'error');
- } else if (healthData) {
- setMessage('health', `Service: ${healthData.service} - Status: ${healthData.status}`, 'success');
- }
- return (
-
-
- System Status
-
-
-
-
refetch()} disabled={statusLoading} variant="outline">
-
- {statusLoading ? 'Checking...' : 'Refresh Status'}
-
-
checkHealth()} disabled={healthLoading} variant="outline">
-
- {healthLoading ? 'Checking...' : 'Check Health'}
-
-
-
- {statusError && (
-
-
-
-
Status Error
-
{statusError.message}
-
-
- )}
-
- {healthError && (
-
-
-
-
Health Check Error
-
{healthError.message}
-
-
- )}
-
- {healthData && (
-
-
- โ Service: {healthData.service} - Status: {healthData.status}
-
-
- )}
-
-
-
- {status && (
-
-
-
Status
-
-
- {status.status}
-
-
-
-
Version
-
{status.version}
-
-
-
Uptime
-
{status.uptime}
-
-
-
Last Updated
-
{formatTimestamp(status.timestamp)}
-
-
- )}
-
-
- );
-};
\ No newline at end of file
diff --git a/experimental/app/src/components/ThemeToggle.tsx b/experimental/app/src/components/ThemeToggle.tsx
deleted file mode 100644
index 054d7cc..0000000
--- a/experimental/app/src/components/ThemeToggle.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Moon, Sun, Monitor } from 'lucide-react';
-import { Button } from './ui/button';
-import { useTheme } from '../contexts/ThemeContext';
-
-export function ThemeToggle() {
- const { theme, setTheme } = useTheme();
-
- const cycleTheme = () => {
- if (theme === 'light') {
- setTheme('dark');
- } else if (theme === 'dark') {
- setTheme('system');
- } else {
- setTheme('light');
- }
- };
-
- const getIcon = () => {
- switch (theme) {
- case 'light':
- return ;
- case 'dark':
- return ;
- default:
- return ;
- }
- };
-
- const getLabel = () => {
- switch (theme) {
- case 'light':
- return 'Light mode';
- case 'dark':
- return 'Dark mode';
- default:
- return 'System theme';
- }
- };
-
- return (
-
- {getIcon()}
- {getLabel()}
-
- );
-}
\ No newline at end of file
diff --git a/experimental/app/src/components/index.ts b/experimental/app/src/components/index.ts
deleted file mode 100644
index 448d02d..0000000
--- a/experimental/app/src/components/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export { Message } from './Message';
-export { SystemStatus } from './SystemStatus';
-export { ConfigurationSection } from './ConfigurationSection';
-export { ConfigurationForm } from './ConfigurationForm';
-export { StatusSection } from './StatusSection';
-export { DnsmasqSection } from './DnsmasqSection';
-export { PxeAssetsSection } from './PxeAssetsSection';
-export { AppSidebar } from './AppSidebar';
-export { Advanced } from './Advanced';
-export { ConfigEditor } from './ConfigEditor';
-export { ErrorBoundary, ErrorFallback } from './ErrorBoundary';
-export { CloudComponent } from './CloudComponent';
-export { CentralComponent } from './CentralComponent';
-export { DnsComponent } from './DnsComponent';
-export { DhcpComponent } from './DhcpComponent';
-export { PxeComponent } from './PxeComponent';
-export { ClusterNodesComponent } from './ClusterNodesComponent';
-export { ClusterServicesComponent } from './ClusterServicesComponent';
-export { AppsComponent } from './AppsComponent';
\ No newline at end of file
diff --git a/experimental/app/src/components/ui/badge.tsx b/experimental/app/src/components/ui/badge.tsx
deleted file mode 100644
index 0205413..0000000
--- a/experimental/app/src/components/ui/badge.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const badgeVariants = cva(
- "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
- {
- variants: {
- variant: {
- default:
- "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
- secondary:
- "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
- destructive:
- "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
- outline:
- "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- }
-)
-
-function Badge({
- className,
- variant,
- asChild = false,
- ...props
-}: React.ComponentProps<"span"> &
- VariantProps & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "span"
-
- return (
-
- )
-}
-
-export { Badge, badgeVariants }
diff --git a/experimental/app/src/components/ui/button.tsx b/experimental/app/src/components/ui/button.tsx
deleted file mode 100644
index a2df8dc..0000000
--- a/experimental/app/src/components/ui/button.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
- {
- variants: {
- variant: {
- default:
- "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
- destructive:
- "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
- outline:
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
- secondary:
- "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
- ghost:
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
- icon: "size-9",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
-)
-
-function Button({
- className,
- variant,
- size,
- asChild = false,
- ...props
-}: React.ComponentProps<"button"> &
- VariantProps & {
- asChild?: boolean
- }) {
- const Comp = asChild ? Slot : "button"
-
- return (
-
- )
-}
-
-export { Button, buttonVariants }
diff --git a/experimental/app/src/components/ui/card.tsx b/experimental/app/src/components/ui/card.tsx
deleted file mode 100644
index d05bbc6..0000000
--- a/experimental/app/src/components/ui/card.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-function Card({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardAction,
- CardDescription,
- CardContent,
-}
diff --git a/experimental/app/src/components/ui/collapsible.tsx b/experimental/app/src/components/ui/collapsible.tsx
deleted file mode 100644
index 77f86be..0000000
--- a/experimental/app/src/components/ui/collapsible.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
-
-function Collapsible({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function CollapsibleTrigger({
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function CollapsibleContent({
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/experimental/app/src/components/ui/dialog.tsx b/experimental/app/src/components/ui/dialog.tsx
deleted file mode 100644
index 6cb123b..0000000
--- a/experimental/app/src/components/ui/dialog.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-function Dialog({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function DialogTrigger({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function DialogPortal({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function DialogClose({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function DialogOverlay({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function DialogContent({
- className,
- children,
- showCloseButton = true,
- ...props
-}: React.ComponentProps & {
- showCloseButton?: boolean
-}) {
- return (
-
-
-
- {children}
- {showCloseButton && (
-
-
- Close
-
- )}
-
-
- )
-}
-
-function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function DialogTitle({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function DialogDescription({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogOverlay,
- DialogPortal,
- DialogTitle,
- DialogTrigger,
-}
diff --git a/experimental/app/src/components/ui/form.tsx b/experimental/app/src/components/ui/form.tsx
deleted file mode 100644
index 524b986..0000000
--- a/experimental/app/src/components/ui/form.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { Slot } from "@radix-ui/react-slot"
-import {
- Controller,
- FormProvider,
- useFormContext,
- useFormState,
- type ControllerProps,
- type FieldPath,
- type FieldValues,
-} from "react-hook-form"
-
-import { cn } from "@/lib/utils"
-import { Label } from "@/components/ui/label"
-
-const Form = FormProvider
-
-type FormFieldContextValue<
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath,
-> = {
- name: TName
-}
-
-const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
-)
-
-const FormField = <
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath,
->({
- ...props
-}: ControllerProps) => {
- return (
-
-
-
- )
-}
-
-const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext)
- const itemContext = React.useContext(FormItemContext)
- const { getFieldState } = useFormContext()
- const formState = useFormState({ name: fieldContext.name })
- const fieldState = getFieldState(fieldContext.name, formState)
-
- if (!fieldContext) {
- throw new Error("useFormField should be used within ")
- }
-
- const { id } = itemContext
-
- return {
- id,
- name: fieldContext.name,
- formItemId: `${id}-form-item`,
- formDescriptionId: `${id}-form-item-description`,
- formMessageId: `${id}-form-item-message`,
- ...fieldState,
- }
-}
-
-type FormItemContextValue = {
- id: string
-}
-
-const FormItemContext = React.createContext(
- {} as FormItemContextValue
-)
-
-function FormItem({ className, ...props }: React.ComponentProps<"div">) {
- const id = React.useId()
-
- return (
-
-
-
- )
-}
-
-function FormLabel({
- className,
- ...props
-}: React.ComponentProps) {
- const { error, formItemId } = useFormField()
-
- return (
-
- )
-}
-
-function FormControl({ ...props }: React.ComponentProps) {
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
-
- return (
-
- )
-}
-
-function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
- const { formDescriptionId } = useFormField()
-
- return (
-
- )
-}
-
-function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
- const { error, formMessageId } = useFormField()
- const body = error ? String(error?.message ?? "") : props.children
-
- if (!body) {
- return null
- }
-
- return (
-
- {body}
-
- )
-}
-
-export {
- useFormField,
- Form,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- FormField,
-}
diff --git a/experimental/app/src/components/ui/index.ts b/experimental/app/src/components/ui/index.ts
deleted file mode 100644
index 92c0b04..0000000
--- a/experimental/app/src/components/ui/index.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-export { Button, buttonVariants } from './button';
-export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './card';
-export { Badge, badgeVariants } from './badge';
-export { Input } from './input';
-export { Label } from './label';
-export { Textarea } from './textarea';
-export {
- Dialog,
- DialogPortal,
- DialogOverlay,
- DialogClose,
- DialogTrigger,
- DialogContent,
- DialogHeader,
- DialogFooter,
- DialogTitle,
- DialogDescription,
-} from './dialog';
-export {
- Form,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- FormField,
-} from './form';
\ No newline at end of file
diff --git a/experimental/app/src/components/ui/input.tsx b/experimental/app/src/components/ui/input.tsx
deleted file mode 100644
index 03295ca..0000000
--- a/experimental/app/src/components/ui/input.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-function Input({ className, type, ...props }: React.ComponentProps<"input">) {
- return (
-
- )
-}
-
-export { Input }
diff --git a/experimental/app/src/components/ui/label.tsx b/experimental/app/src/components/ui/label.tsx
deleted file mode 100644
index ef7133a..0000000
--- a/experimental/app/src/components/ui/label.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-
-import { cn } from "@/lib/utils"
-
-function Label({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export { Label }
diff --git a/experimental/app/src/components/ui/separator.tsx b/experimental/app/src/components/ui/separator.tsx
deleted file mode 100644
index 275381c..0000000
--- a/experimental/app/src/components/ui/separator.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SeparatorPrimitive from "@radix-ui/react-separator"
-
-import { cn } from "@/lib/utils"
-
-function Separator({
- className,
- orientation = "horizontal",
- decorative = true,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export { Separator }
diff --git a/experimental/app/src/components/ui/sheet.tsx b/experimental/app/src/components/ui/sheet.tsx
deleted file mode 100644
index 6906f5b..0000000
--- a/experimental/app/src/components/ui/sheet.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as React from "react"
-import * as SheetPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-function Sheet({ ...props }: React.ComponentProps) {
- return
-}
-
-function SheetTrigger({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function SheetClose({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function SheetPortal({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function SheetOverlay({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function SheetContent({
- className,
- children,
- side = "right",
- ...props
-}: React.ComponentProps & {
- side?: "top" | "right" | "bottom" | "left"
-}) {
- return (
-
-
-
- {children}
-
-
- Close
-
-
-
- )
-}
-
-function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SheetTitle({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function SheetDescription({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export {
- Sheet,
- SheetTrigger,
- SheetClose,
- SheetContent,
- SheetHeader,
- SheetFooter,
- SheetTitle,
- SheetDescription,
-}
diff --git a/experimental/app/src/components/ui/sidebar.tsx b/experimental/app/src/components/ui/sidebar.tsx
deleted file mode 100644
index c948d1f..0000000
--- a/experimental/app/src/components/ui/sidebar.tsx
+++ /dev/null
@@ -1,727 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva } from "class-variance-authority"
-import type { VariantProps } from "class-variance-authority"
-import { PanelLeftIcon } from "lucide-react"
-
-import { useIsMobile } from "@/hooks/use-mobile"
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Separator } from "@/components/ui/separator"
-import {
- Sheet,
- SheetContent,
- SheetDescription,
- SheetHeader,
- SheetTitle,
-} from "@/components/ui/sheet"
-import { Skeleton } from "@/components/ui/skeleton"
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip"
-
-const SIDEBAR_COOKIE_NAME = "sidebar_state"
-const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
-const SIDEBAR_WIDTH = "16rem"
-const SIDEBAR_WIDTH_MOBILE = "18rem"
-const SIDEBAR_WIDTH_ICON = "3rem"
-const SIDEBAR_KEYBOARD_SHORTCUT = "b"
-
-type SidebarContextProps = {
- state: "expanded" | "collapsed"
- open: boolean
- setOpen: (open: boolean) => void
- openMobile: boolean
- setOpenMobile: (open: boolean) => void
- isMobile: boolean
- toggleSidebar: () => void
-}
-
-const SidebarContext = React.createContext(null)
-
-function useSidebar() {
- const context = React.useContext(SidebarContext)
- if (!context) {
- throw new Error("useSidebar must be used within a SidebarProvider.")
- }
-
- return context
-}
-
-function SidebarProvider({
- defaultOpen = true,
- open: openProp,
- onOpenChange: setOpenProp,
- className,
- style,
- children,
- ...props
-}: React.ComponentProps<"div"> & {
- defaultOpen?: boolean
- open?: boolean
- onOpenChange?: (open: boolean) => void
-}) {
- const isMobile = useIsMobile()
- const [openMobile, setOpenMobile] = React.useState(false)
-
- // This is the internal state of the sidebar.
- // We use openProp and setOpenProp for control from outside the component.
- const [_open, _setOpen] = React.useState(defaultOpen)
- const open = openProp ?? _open
- const setOpen = React.useCallback(
- (value: boolean | ((value: boolean) => boolean)) => {
- const openState = typeof value === "function" ? value(open) : value
- if (setOpenProp) {
- setOpenProp(openState)
- } else {
- _setOpen(openState)
- }
-
- // This sets the cookie to keep the sidebar state.
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
- },
- [setOpenProp, open]
- )
-
- // Helper to toggle the sidebar.
- const toggleSidebar = React.useCallback(() => {
- return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
- }, [isMobile, setOpen, setOpenMobile])
-
- // Adds a keyboard shortcut to toggle the sidebar.
- React.useEffect(() => {
- const handleKeyDown = (event: KeyboardEvent) => {
- if (
- event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
- (event.metaKey || event.ctrlKey)
- ) {
- event.preventDefault()
- toggleSidebar()
- }
- }
-
- window.addEventListener("keydown", handleKeyDown)
- return () => window.removeEventListener("keydown", handleKeyDown)
- }, [toggleSidebar])
-
- // We add a state so that we can do data-state="expanded" or "collapsed".
- // This makes it easier to style the sidebar with Tailwind classes.
- const state = open ? "expanded" : "collapsed"
-
- const contextValue = React.useMemo(
- () => ({
- state,
- open,
- setOpen,
- isMobile,
- openMobile,
- setOpenMobile,
- toggleSidebar,
- }),
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
- )
-
- return (
-
-
-
- {children}
-
-
-
- )
-}
-
-function Sidebar({
- side = "left",
- variant = "sidebar",
- collapsible = "offcanvas",
- className,
- children,
- ...props
-}: React.ComponentProps<"div"> & {
- side?: "left" | "right"
- variant?: "sidebar" | "floating" | "inset"
- collapsible?: "offcanvas" | "icon" | "none"
-}) {
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
-
- if (collapsible === "none") {
- return (
-
- {children}
-
- )
- }
-
- if (isMobile) {
- return (
-
-
-
- Sidebar
- Displays the mobile sidebar.
-
- {children}
-
-
- )
- }
-
- return (
-
- {/* This is what handles the sidebar gap on desktop */}
-
-
-
- )
-}
-
-function SidebarTrigger({
- className,
- onClick,
- ...props
-}: React.ComponentProps) {
- const { toggleSidebar } = useSidebar()
-
- return (
- {
- onClick?.(event)
- toggleSidebar()
- }}
- {...props}
- >
-
- Toggle Sidebar
-
- )
-}
-
-function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
- const { toggleSidebar } = useSidebar()
-
- return (
-
- )
-}
-
-function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
- return (
-
- )
-}
-
-function SidebarInput({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SidebarSeparator({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SidebarGroupLabel({
- className,
- asChild = false,
- ...props
-}: React.ComponentProps<"div"> & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "div"
-
- return (
- svg]:size-4 [&>svg]:shrink-0",
- "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
- className
- )}
- {...props}
- />
- )
-}
-
-function SidebarGroupAction({
- className,
- asChild = false,
- ...props
-}: React.ComponentProps<"button"> & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "button"
-
- return (
- svg]:size-4 [&>svg]:shrink-0",
- // Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 md:after:hidden",
- "group-data-[collapsible=icon]:hidden",
- className
- )}
- {...props}
- />
- )
-}
-
-function SidebarGroupContent({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
- return (
-
- )
-}
-
-function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
- return (
-
- )
-}
-
-const sidebarMenuButtonVariants = cva(
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
- {
- variants: {
- variant: {
- default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
- outline:
- "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
- },
- size: {
- default: "h-8 text-sm",
- sm: "h-7 text-xs",
- lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
-)
-
-function SidebarMenuButton({
- asChild = false,
- isActive = false,
- variant = "default",
- size = "default",
- tooltip,
- className,
- ...props
-}: React.ComponentProps<"button"> & {
- asChild?: boolean
- isActive?: boolean
- tooltip?: string | React.ComponentProps
-} & VariantProps) {
- const Comp = asChild ? Slot : "button"
- const { isMobile, state } = useSidebar()
-
- const button = (
-
- )
-
- if (!tooltip) {
- return button
- }
-
- if (typeof tooltip === "string") {
- tooltip = {
- children: tooltip,
- }
- }
-
- return (
-
- {button}
-
-
- )
-}
-
-function SidebarMenuAction({
- className,
- asChild = false,
- showOnHover = false,
- ...props
-}: React.ComponentProps<"button"> & {
- asChild?: boolean
- showOnHover?: boolean
-}) {
- const Comp = asChild ? Slot : "button"
-
- return (
- svg]:size-4 [&>svg]:shrink-0",
- // Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 md:after:hidden",
- "peer-data-[size=sm]/menu-button:top-1",
- "peer-data-[size=default]/menu-button:top-1.5",
- "peer-data-[size=lg]/menu-button:top-2.5",
- "group-data-[collapsible=icon]:hidden",
- showOnHover &&
- "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
- className
- )}
- {...props}
- />
- )
-}
-
-function SidebarMenuBadge({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function SidebarMenuSkeleton({
- className,
- showIcon = false,
- ...props
-}: React.ComponentProps<"div"> & {
- showIcon?: boolean
-}) {
- // Random width between 50 to 90%.
- const width = React.useMemo(() => {
- return `${Math.floor(Math.random() * 40) + 50}%`
- }, [])
-
- return (
-
- {showIcon && (
-
- )}
-
-
- )
-}
-
-function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
- return (
-
- )
-}
-
-function SidebarMenuSubItem({
- className,
- ...props
-}: React.ComponentProps<"li">) {
- return (
-
- )
-}
-
-function SidebarMenuSubButton({
- asChild = false,
- size = "md",
- isActive = false,
- className,
- ...props
-}: React.ComponentProps<"a"> & {
- asChild?: boolean
- size?: "sm" | "md"
- isActive?: boolean
-}) {
- const Comp = asChild ? Slot : "a"
-
- return (
- svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
- "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
- size === "sm" && "text-xs",
- size === "md" && "text-sm",
- "group-data-[collapsible=icon]:hidden",
- className
- )}
- {...props}
- />
- )
-}
-
-export {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarGroup,
- SidebarGroupAction,
- SidebarGroupContent,
- SidebarGroupLabel,
- SidebarHeader,
- SidebarInput,
- SidebarInset,
- SidebarMenu,
- SidebarMenuAction,
- SidebarMenuBadge,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSkeleton,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarProvider,
- SidebarRail,
- SidebarSeparator,
- SidebarTrigger,
- useSidebar,
-}
diff --git a/experimental/app/src/components/ui/skeleton.tsx b/experimental/app/src/components/ui/skeleton.tsx
deleted file mode 100644
index 32ea0ef..0000000
--- a/experimental/app/src/components/ui/skeleton.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { cn } from "@/lib/utils"
-
-function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-export { Skeleton }
diff --git a/experimental/app/src/components/ui/textarea.tsx b/experimental/app/src/components/ui/textarea.tsx
deleted file mode 100644
index 7f21b5e..0000000
--- a/experimental/app/src/components/ui/textarea.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
- return (
-
- )
-}
-
-export { Textarea }
diff --git a/experimental/app/src/components/ui/tooltip.tsx b/experimental/app/src/components/ui/tooltip.tsx
deleted file mode 100644
index 71ee0fe..0000000
--- a/experimental/app/src/components/ui/tooltip.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as React from "react"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
-
-import { cn } from "@/lib/utils"
-
-function TooltipProvider({
- delayDuration = 0,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function Tooltip({
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- )
-}
-
-function TooltipTrigger({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function TooltipContent({
- className,
- sideOffset = 0,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
- {children}
-
-
-
- )
-}
-
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/experimental/app/src/contexts/ThemeContext.tsx b/experimental/app/src/contexts/ThemeContext.tsx
deleted file mode 100644
index be42357..0000000
--- a/experimental/app/src/contexts/ThemeContext.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { createContext, useContext, useEffect, useState } from 'react';
-
-type Theme = 'dark' | 'light' | 'system';
-
-type ThemeProviderProps = {
- children: React.ReactNode;
- defaultTheme?: Theme;
- storageKey?: string;
-};
-
-type ThemeProviderState = {
- theme: Theme;
- setTheme: (theme: Theme) => void;
-};
-
-const initialState: ThemeProviderState = {
- theme: 'system',
- setTheme: () => null,
-};
-
-const ThemeProviderContext = createContext(initialState);
-
-export function ThemeProvider({
- children,
- defaultTheme = 'system',
- storageKey = 'wild-central-theme',
- ...props
-}: ThemeProviderProps) {
- const [theme, setTheme] = useState(
- () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
- );
-
- useEffect(() => {
- const root = window.document.documentElement;
-
- root.classList.remove('light', 'dark');
-
- if (theme === 'system') {
- const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
- .matches
- ? 'dark'
- : 'light';
-
- root.classList.add(systemTheme);
- return;
- }
-
- root.classList.add(theme);
- }, [theme]);
-
- const value = {
- theme,
- setTheme: (theme: Theme) => {
- localStorage.setItem(storageKey, theme);
- setTheme(theme);
- },
- };
-
- return (
-
- {children}
-
- );
-}
-
-export const useTheme = () => {
- const context = useContext(ThemeProviderContext);
-
- if (context === undefined)
- throw new Error('useTheme must be used within a ThemeProvider');
-
- return context;
-};
\ No newline at end of file
diff --git a/experimental/app/src/hooks/__tests__/useConfig.test.ts b/experimental/app/src/hooks/__tests__/useConfig.test.ts
deleted file mode 100644
index 186b9c8..0000000
--- a/experimental/app/src/hooks/__tests__/useConfig.test.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { renderHook, waitFor, act } from '@testing-library/react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import React from 'react';
-import { useConfig } from '../useConfig';
-import { apiService } from '../../services/api';
-
-// Mock the API service
-vi.mock('../../services/api', () => ({
- apiService: {
- getConfig: vi.fn(),
- createConfig: vi.fn(),
- },
-}));
-
-const createWrapper = () => {
- const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- gcTime: 0,
- },
- mutations: {
- retry: false,
- },
- },
- });
-
- return ({ children }: { children: React.ReactNode }) => (
- React.createElement(QueryClientProvider, { client: queryClient }, children)
- );
-};
-
-describe('useConfig', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- it('should fetch config successfully when configured', async () => {
- const mockConfigResponse = {
- configured: true,
- config: {
- server: { host: '0.0.0.0', port: 5055 },
- cloud: {
- domain: 'wildcloud.local',
- internalDomain: 'cluster.local',
- dhcpRange: '192.168.8.100,192.168.8.200',
- dns: { ip: '192.168.8.50' },
- router: { ip: '192.168.8.1' },
- dnsmasq: { interface: 'eth0' },
- },
- cluster: {
- endpointIp: '192.168.8.60',
- nodes: { talos: { version: 'v1.8.0' } },
- },
- },
- };
-
- vi.mocked(apiService.getConfig).mockResolvedValue(mockConfigResponse);
-
- const { result } = renderHook(() => useConfig(), {
- wrapper: createWrapper(),
- });
-
- expect(result.current.isLoading).toBe(true);
- expect(result.current.showConfigSetup).toBe(false);
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.config).toEqual(mockConfigResponse.config);
- expect(result.current.isConfigured).toBe(true);
- expect(result.current.showConfigSetup).toBe(false);
- expect(result.current.error).toBeNull();
- });
-
- it('should show config setup when not configured', async () => {
- const mockConfigResponse = {
- configured: false,
- message: 'No configuration found',
- };
-
- vi.mocked(apiService.getConfig).mockResolvedValue(mockConfigResponse);
-
- const { result } = renderHook(() => useConfig(), {
- wrapper: createWrapper(),
- });
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.config).toBeNull();
- expect(result.current.isConfigured).toBe(false);
- expect(result.current.showConfigSetup).toBe(true);
- });
-
- it('should create config successfully', async () => {
- const mockConfigResponse = {
- configured: false,
- message: 'No configuration found',
- };
-
- const mockCreateResponse = {
- status: 'Configuration created successfully',
- };
-
- const newConfig = {
- server: { host: '0.0.0.0', port: 5055 },
- cloud: {
- domain: 'wildcloud.local',
- internalDomain: 'cluster.local',
- dhcpRange: '192.168.8.100,192.168.8.200',
- dns: { ip: '192.168.8.50' },
- router: { ip: '192.168.8.1' },
- dnsmasq: { interface: 'eth0' },
- },
- cluster: {
- endpointIp: '192.168.8.60',
- nodes: { talos: { version: 'v1.8.0' } },
- },
- };
-
- vi.mocked(apiService.getConfig).mockResolvedValue(mockConfigResponse);
- vi.mocked(apiService.createConfig).mockResolvedValue(mockCreateResponse);
-
- const { result } = renderHook(() => useConfig(), {
- wrapper: createWrapper(),
- });
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.showConfigSetup).toBe(true);
-
- // Create config
- await act(async () => {
- result.current.createConfig(newConfig);
- });
-
- await waitFor(() => {
- expect(result.current.isCreating).toBe(false);
- });
-
- expect(apiService.createConfig).toHaveBeenCalledWith(newConfig);
- });
-
- it('should handle error when fetching config fails', async () => {
- const mockError = new Error('Network error');
- vi.mocked(apiService.getConfig).mockRejectedValue(mockError);
-
- const { result } = renderHook(() => useConfig(), {
- wrapper: createWrapper(),
- });
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.error).toEqual(mockError);
- expect(result.current.config).toBeNull();
- });
-});
\ No newline at end of file
diff --git a/experimental/app/src/hooks/__tests__/useMessages.test.ts b/experimental/app/src/hooks/__tests__/useMessages.test.ts
deleted file mode 100644
index 8c39c82..0000000
--- a/experimental/app/src/hooks/__tests__/useMessages.test.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { renderHook, act } from '@testing-library/react';
-import { useMessages } from '../useMessages';
-
-describe('useMessages', () => {
- it('should initialize with empty messages', () => {
- const { result } = renderHook(() => useMessages());
-
- expect(result.current.messages).toEqual({});
- });
-
- it('should set a message', () => {
- const { result } = renderHook(() => useMessages());
-
- act(() => {
- result.current.setMessage('test', 'Test message', 'success');
- });
-
- expect(result.current.messages).toEqual({
- test: { message: 'Test message', type: 'success' }
- });
- });
-
- it('should set multiple messages', () => {
- const { result } = renderHook(() => useMessages());
-
- act(() => {
- result.current.setMessage('success', 'Success message', 'success');
- result.current.setMessage('error', 'Error message', 'error');
- result.current.setMessage('info', 'Info message', 'info');
- });
-
- expect(result.current.messages).toEqual({
- success: { message: 'Success message', type: 'success' },
- error: { message: 'Error message', type: 'error' },
- info: { message: 'Info message', type: 'info' },
- });
- });
-
- it('should update existing message', () => {
- const { result } = renderHook(() => useMessages());
-
- act(() => {
- result.current.setMessage('test', 'First message', 'info');
- });
-
- expect(result.current.messages.test).toEqual({
- message: 'First message',
- type: 'info'
- });
-
- act(() => {
- result.current.setMessage('test', 'Updated message', 'error');
- });
-
- expect(result.current.messages.test).toEqual({
- message: 'Updated message',
- type: 'error'
- });
- });
-
- it('should clear a specific message', () => {
- const { result } = renderHook(() => useMessages());
-
- act(() => {
- result.current.setMessage('test1', 'Message 1', 'info');
- result.current.setMessage('test2', 'Message 2', 'success');
- });
-
- expect(Object.keys(result.current.messages)).toHaveLength(2);
-
- act(() => {
- result.current.clearMessage('test1');
- });
-
- expect(result.current.messages).toEqual({
- test2: { message: 'Message 2', type: 'success' }
- });
- });
-
- it('should clear message by setting to null', () => {
- const { result } = renderHook(() => useMessages());
-
- act(() => {
- result.current.setMessage('test', 'Test message', 'info');
- });
-
- expect(result.current.messages.test).toBeDefined();
-
- act(() => {
- result.current.setMessage('test', null);
- });
-
- expect(result.current.messages.test).toBeUndefined();
- });
-
- it('should clear all messages', () => {
- const { result } = renderHook(() => useMessages());
-
- act(() => {
- result.current.setMessage('test1', 'Message 1', 'info');
- result.current.setMessage('test2', 'Message 2', 'success');
- result.current.setMessage('test3', 'Message 3', 'error');
- });
-
- expect(Object.keys(result.current.messages)).toHaveLength(3);
-
- act(() => {
- result.current.clearAllMessages();
- });
-
- expect(result.current.messages).toEqual({});
- });
-
- it('should default to info type when type not specified', () => {
- const { result } = renderHook(() => useMessages());
-
- act(() => {
- result.current.setMessage('test', 'Test message');
- });
-
- expect(result.current.messages.test).toEqual({
- message: 'Test message',
- type: 'info'
- });
- });
-});
\ No newline at end of file
diff --git a/experimental/app/src/hooks/__tests__/useStatus.test.ts b/experimental/app/src/hooks/__tests__/useStatus.test.ts
deleted file mode 100644
index 1564c06..0000000
--- a/experimental/app/src/hooks/__tests__/useStatus.test.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { renderHook, waitFor } from '@testing-library/react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import React from 'react';
-import { useStatus } from '../useStatus';
-import { apiService } from '../../services/api';
-
-// Mock the API service
-vi.mock('../../services/api', () => ({
- apiService: {
- getStatus: vi.fn(),
- },
-}));
-
-const createWrapper = () => {
- const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- gcTime: 0,
- },
- },
- });
-
- return ({ children }: { children: React.ReactNode }) => (
- React.createElement(QueryClientProvider, { client: queryClient }, children)
- );
-};
-
-describe('useStatus', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- it('should fetch status successfully', async () => {
- const mockStatus = {
- status: 'running',
- version: '1.0.0',
- uptime: '2 hours',
- timestamp: '2024-01-01T00:00:00Z',
- };
-
- vi.mocked(apiService.getStatus).mockResolvedValue(mockStatus);
-
- const { result } = renderHook(() => useStatus(), {
- wrapper: createWrapper(),
- });
-
- expect(result.current.isLoading).toBe(true);
- expect(result.current.data).toBeUndefined();
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockStatus);
- expect(result.current.error).toBeNull();
- expect(apiService.getStatus).toHaveBeenCalledTimes(1);
- });
-
- it('should handle error when fetching status fails', async () => {
- const mockError = new Error('Network error');
- vi.mocked(apiService.getStatus).mockRejectedValue(mockError);
-
- const { result } = renderHook(() => useStatus(), {
- wrapper: createWrapper(),
- });
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toBeUndefined();
- expect(result.current.error).toEqual(mockError);
- });
-
- it('should refetch data when refetch is called', async () => {
- const mockStatus = {
- status: 'running',
- version: '1.0.0',
- uptime: '2 hours',
- timestamp: '2024-01-01T00:00:00Z',
- };
-
- vi.mocked(apiService.getStatus).mockResolvedValue(mockStatus);
-
- const { result } = renderHook(() => useStatus(), {
- wrapper: createWrapper(),
- });
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(apiService.getStatus).toHaveBeenCalledTimes(1);
-
- // Trigger refetch
- result.current.refetch();
-
- await waitFor(() => {
- expect(apiService.getStatus).toHaveBeenCalledTimes(2);
- });
- });
-});
\ No newline at end of file
diff --git a/experimental/app/src/hooks/index.ts b/experimental/app/src/hooks/index.ts
deleted file mode 100644
index 31d8dd5..0000000
--- a/experimental/app/src/hooks/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export { useMessages } from './useMessages';
-export { useStatus } from './useStatus';
-export { useHealth } from './useHealth';
-export { useConfig } from './useConfig';
-export { useConfigYaml } from './useConfigYaml';
-export { useDnsmasq } from './useDnsmasq';
-export { useAssets } from './useAssets';
\ No newline at end of file
diff --git a/experimental/app/src/hooks/use-mobile.ts b/experimental/app/src/hooks/use-mobile.ts
deleted file mode 100644
index 2b0fe1d..0000000
--- a/experimental/app/src/hooks/use-mobile.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as React from "react"
-
-const MOBILE_BREAKPOINT = 768
-
-export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
-
- React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
- const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
-
- return !!isMobile
-}
diff --git a/experimental/app/src/hooks/useAssets.ts b/experimental/app/src/hooks/useAssets.ts
deleted file mode 100644
index f3c3e5e..0000000
--- a/experimental/app/src/hooks/useAssets.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useMutation } from '@tanstack/react-query';
-import { apiService } from '../services/api';
-
-interface AssetsResponse {
- status: string;
-}
-
-export const useAssets = () => {
- const downloadMutation = useMutation({
- mutationFn: apiService.downloadPXEAssets,
- });
-
- return {
- downloadAssets: downloadMutation.mutate,
- isDownloading: downloadMutation.isPending,
- error: downloadMutation.error,
- data: downloadMutation.data,
- };
-};
\ No newline at end of file
diff --git a/experimental/app/src/hooks/useConfig.ts b/experimental/app/src/hooks/useConfig.ts
deleted file mode 100644
index 01db5ed..0000000
--- a/experimental/app/src/hooks/useConfig.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { useState, useEffect } from 'react';
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { apiService } from '../services/api';
-import type { Config } from '../types';
-
-interface ConfigResponse {
- configured: boolean;
- config?: Config;
- message?: string;
-}
-
-interface CreateConfigResponse {
- status: string;
-}
-
-export const useConfig = () => {
- const queryClient = useQueryClient();
- const [showConfigSetup, setShowConfigSetup] = useState(false);
-
- const configQuery = useQuery({
- queryKey: ['config'],
- queryFn: () => apiService.getConfig(),
- });
-
- // Update showConfigSetup based on query data
- useEffect(() => {
- if (configQuery.data) {
- setShowConfigSetup(configQuery.data.configured === false);
- }
- }, [configQuery.data]);
-
- const createConfigMutation = useMutation({
- mutationFn: apiService.createConfig,
- onSuccess: () => {
- // Invalidate and refetch config after successful creation
- queryClient.invalidateQueries({ queryKey: ['config'] });
- setShowConfigSetup(false);
- },
- });
-
- return {
- config: configQuery.data?.config || null,
- isConfigured: configQuery.data?.configured || false,
- showConfigSetup,
- setShowConfigSetup,
- isLoading: configQuery.isLoading,
- isCreating: createConfigMutation.isPending,
- error: configQuery.error || createConfigMutation.error,
- createConfig: createConfigMutation.mutate,
- refetch: configQuery.refetch,
- };
-};
\ No newline at end of file
diff --git a/experimental/app/src/hooks/useConfigYaml.ts b/experimental/app/src/hooks/useConfigYaml.ts
deleted file mode 100644
index 002e181..0000000
--- a/experimental/app/src/hooks/useConfigYaml.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { apiService } from '../services/api';
-
-export const useConfigYaml = () => {
- const queryClient = useQueryClient();
-
- const configYamlQuery = useQuery({
- queryKey: ['config', 'yaml'],
- queryFn: () => apiService.getConfigYaml(),
- staleTime: 30000, // Consider data fresh for 30 seconds
- retry: true,
- });
-
- const updateConfigYamlMutation = useMutation({
- mutationFn: (data: string) => apiService.updateConfigYaml(data),
- onSuccess: () => {
- // Invalidate both YAML and JSON config queries
- queryClient.invalidateQueries({ queryKey: ['config'] });
- },
- });
-
- // Check if error is 404 (endpoint doesn't exist)
- const isEndpointMissing = configYamlQuery.error &&
- configYamlQuery.error instanceof Error &&
- configYamlQuery.error.message.includes('404');
-
- // Only pass through real errors
- const actualError = (configYamlQuery.error instanceof Error ? configYamlQuery.error : null) ||
- (updateConfigYamlMutation.error instanceof Error ? updateConfigYamlMutation.error : null);
-
- return {
- yamlContent: configYamlQuery.data || '',
- isLoading: configYamlQuery.isLoading,
- error: actualError,
- isEndpointMissing,
- isUpdating: updateConfigYamlMutation.isPending,
- updateYaml: updateConfigYamlMutation.mutate,
- refetch: configYamlQuery.refetch,
- };
-};
\ No newline at end of file
diff --git a/experimental/app/src/hooks/useDnsmasq.ts b/experimental/app/src/hooks/useDnsmasq.ts
deleted file mode 100644
index 56fbb9e..0000000
--- a/experimental/app/src/hooks/useDnsmasq.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useState } from 'react';
-import { useMutation } from '@tanstack/react-query';
-import { apiService } from '../services/api';
-
-interface DnsmasqResponse {
- status: string;
-}
-
-export const useDnsmasq = () => {
- const [dnsmasqConfig, setDnsmasqConfig] = useState('');
-
- const generateConfigMutation = useMutation({
- mutationFn: apiService.getDnsmasqConfig,
- onSuccess: (data) => {
- setDnsmasqConfig(data);
- },
- });
-
- const restartMutation = useMutation({
- mutationFn: apiService.restartDnsmasq,
- });
-
- return {
- dnsmasqConfig,
- generateConfig: generateConfigMutation.mutate,
- isGenerating: generateConfigMutation.isPending,
- generateError: generateConfigMutation.error,
- restart: restartMutation.mutate,
- isRestarting: restartMutation.isPending,
- restartError: restartMutation.error,
- restartData: restartMutation.data,
- };
-};
\ No newline at end of file
diff --git a/experimental/app/src/hooks/useHealth.ts b/experimental/app/src/hooks/useHealth.ts
deleted file mode 100644
index 6037107..0000000
--- a/experimental/app/src/hooks/useHealth.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useMutation } from '@tanstack/react-query';
-import { apiService } from '../services/api';
-
-interface HealthResponse {
- service: string;
- status: string;
-}
-
-export const useHealth = () => {
- return useMutation({
- mutationFn: apiService.getHealth,
- });
-};
\ No newline at end of file
diff --git a/experimental/app/src/hooks/useMessages.ts b/experimental/app/src/hooks/useMessages.ts
deleted file mode 100644
index a7bef13..0000000
--- a/experimental/app/src/hooks/useMessages.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useState } from 'react';
-import type { Messages } from '../types';
-
-export const useMessages = () => {
- const [messages, setMessages] = useState({});
-
- const setMessage = (key: string, message: string | null, type: 'info' | 'success' | 'error' = 'info') => {
- if (message === null) {
- setMessages(prev => {
- const newMessages = { ...prev };
- delete newMessages[key];
- return newMessages;
- });
- } else {
- setMessages(prev => ({ ...prev, [key]: { message, type } }));
- }
- };
-
- const clearMessage = (key: string) => {
- setMessage(key, null);
- };
-
- const clearAllMessages = () => {
- setMessages({});
- };
-
- return {
- messages,
- setMessage,
- clearMessage,
- clearAllMessages,
- };
-};
\ No newline at end of file
diff --git a/experimental/app/src/hooks/useStatus.ts b/experimental/app/src/hooks/useStatus.ts
deleted file mode 100644
index fd5a3b0..0000000
--- a/experimental/app/src/hooks/useStatus.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { useQuery } from '@tanstack/react-query';
-import { apiService } from '../services/api';
-import type { Status } from '../types';
-
-export const useStatus = () => {
- return useQuery({
- queryKey: ['status'],
- queryFn: apiService.getStatus,
- refetchInterval: 30000, // Refetch every 30 seconds
- });
-};
\ No newline at end of file
diff --git a/experimental/app/src/index.css b/experimental/app/src/index.css
deleted file mode 100644
index 98de84c..0000000
--- a/experimental/app/src/index.css
+++ /dev/null
@@ -1,120 +0,0 @@
-@import "tailwindcss";
-@import "tw-animate-css";
-
-@custom-variant dark (&:is(.dark *));
-
-@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
-}
-
-:root {
- --radius: 0.625rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(50.59% 0.12582 244.557);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
-}
-
-.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.205 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
-}
-
-@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
diff --git a/experimental/app/src/lib/queryClient.ts b/experimental/app/src/lib/queryClient.ts
deleted file mode 100644
index ee1fcd8..0000000
--- a/experimental/app/src/lib/queryClient.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { QueryClient } from '@tanstack/react-query';
-
-export const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: 1,
- staleTime: 5 * 60 * 1000, // 5 minutes
- gcTime: 10 * 60 * 1000, // 10 minutes
- refetchOnWindowFocus: false,
- },
- mutations: {
- retry: 1,
- },
- },
-});
\ No newline at end of file
diff --git a/experimental/app/src/lib/utils.ts b/experimental/app/src/lib/utils.ts
deleted file mode 100644
index bd0c391..0000000
--- a/experimental/app/src/lib/utils.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
-
-export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
-}
diff --git a/experimental/app/src/main.tsx b/experimental/app/src/main.tsx
deleted file mode 100644
index 7ba9fd1..0000000
--- a/experimental/app/src/main.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { StrictMode } from 'react';
-import ReactDOM from 'react-dom/client';
-import { QueryClientProvider } from '@tanstack/react-query';
-import './index.css';
-import App from './App';
-import { ThemeProvider } from './contexts/ThemeContext';
-import { queryClient } from './lib/queryClient';
-import { ErrorBoundary } from './components/ErrorBoundary';
-
-const root = ReactDOM.createRoot(
- document.getElementById('root') as HTMLElement
-);
-
-root.render(
-
-
-
-
-
-
-
-
-
-);
\ No newline at end of file
diff --git a/experimental/app/src/schemas/__tests__/config.test.ts b/experimental/app/src/schemas/__tests__/config.test.ts
deleted file mode 100644
index 1b49c6e..0000000
--- a/experimental/app/src/schemas/__tests__/config.test.ts
+++ /dev/null
@@ -1,330 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { configFormSchema, defaultConfigValues } from '../config';
-
-describe('config schema validation', () => {
- describe('valid configurations', () => {
- it('should validate default configuration', () => {
- const result = configFormSchema.safeParse(defaultConfigValues);
- expect(result.success).toBe(true);
- });
-
- it('should validate complete configuration', () => {
- const validConfig = {
- server: {
- host: '0.0.0.0',
- port: 5055,
- },
- cloud: {
- domain: 'wildcloud.local',
- internalDomain: 'cluster.local',
- dhcpRange: '192.168.8.100,192.168.8.200',
- dns: { ip: '192.168.8.50' },
- router: { ip: '192.168.8.1' },
- dnsmasq: { interface: 'eth0' },
- },
- cluster: {
- endpointIp: '192.168.8.60',
- nodes: { talos: { version: 'v1.8.0' } },
- },
- };
-
- const result = configFormSchema.safeParse(validConfig);
- expect(result.success).toBe(true);
- });
- });
-
- describe('server validation', () => {
- it('should reject empty host', () => {
- const config = {
- ...defaultConfigValues,
- server: { ...defaultConfigValues.server, host: '' },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(false);
- if (!result.success) {
- expect(result.error.errors[0].path).toEqual(['server', 'host']);
- expect(result.error.errors[0].message).toBe('Host is required');
- }
- });
-
- it('should reject invalid port ranges', () => {
- const invalidPorts = [0, -1, 65536, 99999];
-
- invalidPorts.forEach(port => {
- const config = {
- ...defaultConfigValues,
- server: { ...defaultConfigValues.server, port },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(false);
- });
- });
-
- it('should accept valid port ranges', () => {
- const validPorts = [1, 80, 443, 5055, 65535];
-
- validPorts.forEach(port => {
- const config = {
- ...defaultConfigValues,
- server: { ...defaultConfigValues.server, port },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(true);
- });
- });
- });
-
- describe('IP address validation', () => {
- it('should reject invalid IP addresses', () => {
- const invalidIPs = [
- '256.1.1.1',
- '192.168.1',
- '192.168.1.256',
- 'not-an-ip',
- '192.168.1.1.1',
- '',
- ];
-
- invalidIPs.forEach(ip => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- dns: { ip },
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(false);
- });
- });
-
- it('should accept valid IP addresses', () => {
- const validIPs = [
- '192.168.1.1',
- '10.0.0.1',
- '172.16.0.1',
- '127.0.0.1',
- '0.0.0.0',
- '255.255.255.255',
- ];
-
- validIPs.forEach(ip => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- dns: { ip },
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(true);
- });
- });
- });
-
- describe('domain validation', () => {
- it('should reject invalid domains', () => {
- const invalidDomains = [
- '',
- '.com',
- 'domain.',
- 'domain..com',
- 'domain-.com',
- '-domain.com',
- 'domain.c',
- 'very-long-domain-name-that-exceeds-the-maximum-allowed-length-for-a-domain-label.com',
- ];
-
- invalidDomains.forEach(domain => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- domain,
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success, `Domain "${domain}" should be invalid but passed validation`).toBe(false);
- });
- });
-
- it('should accept valid domains', () => {
- const validDomains = [
- 'wildcloud.local',
- 'example.com',
- 'sub.domain.com',
- 'localhost',
- 'test123.example.org',
- 'my-domain.net',
- ];
-
- validDomains.forEach(domain => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- domain,
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(true);
- });
- });
- });
-
- describe('DHCP range validation', () => {
- it('should reject invalid DHCP ranges', () => {
- const invalidRanges = [
- '',
- '192.168.1.1',
- '192.168.1.1,',
- ',192.168.1.200',
- '192.168.1.1-192.168.1.200',
- '192.168.1.1,192.168.1.256',
- 'start,end',
- ];
-
- invalidRanges.forEach(dhcpRange => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- dhcpRange,
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(false);
- });
- });
-
- it('should accept valid DHCP ranges', () => {
- const validRanges = [
- '192.168.1.100,192.168.1.200',
- '10.0.0.10,10.0.0.100',
- '172.16.1.1,172.16.1.254',
- ];
-
- validRanges.forEach(dhcpRange => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- dhcpRange,
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(true);
- });
- });
- });
-
- describe('version validation', () => {
- it('should reject invalid versions', () => {
- const invalidVersions = [
- '',
- '1.8.0',
- 'v1.8',
- 'v1.8.0.1',
- 'version1.8.0',
- 'v1.8.0-beta',
- ];
-
- invalidVersions.forEach(version => {
- const config = {
- ...defaultConfigValues,
- cluster: {
- ...defaultConfigValues.cluster,
- nodes: {
- talos: { version },
- },
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(false);
- });
- });
-
- it('should accept valid versions', () => {
- const validVersions = [
- 'v1.8.0',
- 'v1.0.0',
- 'v10.20.30',
- 'v0.0.1',
- ];
-
- validVersions.forEach(version => {
- const config = {
- ...defaultConfigValues,
- cluster: {
- ...defaultConfigValues.cluster,
- nodes: {
- talos: { version },
- },
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(true);
- });
- });
- });
-
- describe('network interface validation', () => {
- it('should reject invalid interfaces', () => {
- const invalidInterfaces = [
- '',
- 'eth-0',
- 'eth.0',
- 'eth 0',
- 'eth/0',
- ];
-
- invalidInterfaces.forEach(interfaceName => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- dnsmasq: { interface: interfaceName },
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(false);
- });
- });
-
- it('should accept valid interfaces', () => {
- const validInterfaces = [
- 'eth0',
- 'eth1',
- 'enp0s3',
- 'wlan0',
- 'lo',
- 'br0',
- ];
-
- validInterfaces.forEach(interfaceName => {
- const config = {
- ...defaultConfigValues,
- cloud: {
- ...defaultConfigValues.cloud,
- dnsmasq: { interface: interfaceName },
- },
- };
-
- const result = configFormSchema.safeParse(config);
- expect(result.success).toBe(true);
- });
- });
- });
-});
\ No newline at end of file
diff --git a/experimental/app/src/schemas/config.ts b/experimental/app/src/schemas/config.ts
deleted file mode 100644
index bdb99ab..0000000
--- a/experimental/app/src/schemas/config.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import { z } from 'zod';
-
-// Network validation helpers
-const ipAddressSchema = z.string().regex(
- /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
- 'Must be a valid IP address'
-);
-
-const domainSchema = z.string().regex(
- /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9]))*$/,
- 'Must be a valid domain name'
-);
-
-const dhcpRangeSchema = z.string().regex(
- /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?),(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
- 'Must be in format: start_ip,end_ip (e.g., 192.168.1.100,192.168.1.200)'
-);
-
-const interfaceSchema = z.string().regex(
- /^[a-zA-Z0-9]+$/,
- 'Must be a valid network interface name (e.g., eth0, enp0s3)'
-);
-
-const versionSchema = z.string().regex(
- /^v\d+\.\d+\.\d+$/,
- 'Must be a valid version format (e.g., v1.8.0)'
-);
-
-// Server configuration schema
-const serverConfigSchema = z.object({
- host: z.string().min(1, 'Host is required').default('0.0.0.0'),
- port: z.number()
- .int('Port must be an integer')
- .min(1, 'Port must be at least 1')
- .max(65535, 'Port must be at most 65535')
- .default(5055),
-});
-
-// Cloud DNS configuration schema
-const cloudDnsSchema = z.object({
- ip: ipAddressSchema,
-});
-
-// Cloud router configuration schema
-const cloudRouterSchema = z.object({
- ip: ipAddressSchema,
-});
-
-// Cloud dnsmasq configuration schema
-const cloudDnsmasqSchema = z.object({
- interface: interfaceSchema,
-});
-
-// Cloud configuration schema
-const cloudConfigSchema = z.object({
- domain: domainSchema,
- internalDomain: domainSchema,
- dhcpRange: dhcpRangeSchema,
- dns: cloudDnsSchema,
- router: cloudRouterSchema,
- dnsmasq: cloudDnsmasqSchema,
-});
-
-// Talos configuration schema
-const talosConfigSchema = z.object({
- version: versionSchema,
-});
-
-// Nodes configuration schema
-const nodesConfigSchema = z.object({
- talos: talosConfigSchema,
-});
-
-// Cluster configuration schema
-const clusterConfigSchema = z.object({
- endpointIp: ipAddressSchema,
- nodes: nodesConfigSchema,
-});
-
-// Wildcloud configuration schema (optional)
-const wildcloudConfigSchema = z.object({
- repository: z.string().min(1, 'Repository is required'),
- currentPhase: z.enum(['setup', 'infrastructure', 'cluster', 'apps']).optional(),
- completedPhases: z.array(z.enum(['setup', 'infrastructure', 'cluster', 'apps'])).optional(),
-}).optional();
-
-// Main configuration schema
-export const configSchema = z.object({
- server: serverConfigSchema,
- cloud: cloudConfigSchema,
- cluster: clusterConfigSchema,
- wildcloud: wildcloudConfigSchema,
-});
-
-// Form schema for creating new configurations (some fields can be optional for partial updates)
-export const configFormSchema = z.object({
- server: z.object({
- host: z.string().min(1, 'Host is required'),
- port: z.coerce.number()
- .int('Port must be an integer')
- .min(1, 'Port must be at least 1')
- .max(65535, 'Port must be at most 65535'),
- }),
- cloud: z.object({
- domain: z.string().min(1, 'Domain is required').refine(
- (val) => domainSchema.safeParse(val).success,
- 'Must be a valid domain name'
- ),
- internalDomain: z.string().min(1, 'Internal domain is required').refine(
- (val) => domainSchema.safeParse(val).success,
- 'Must be a valid domain name'
- ),
- dhcpRange: z.string().min(1, 'DHCP range is required').refine(
- (val) => dhcpRangeSchema.safeParse(val).success,
- 'Must be in format: start_ip,end_ip'
- ),
- dns: z.object({
- ip: z.string().min(1, 'DNS IP is required').refine(
- (val) => ipAddressSchema.safeParse(val).success,
- 'Must be a valid IP address'
- ),
- }),
- router: z.object({
- ip: z.string().min(1, 'Router IP is required').refine(
- (val) => ipAddressSchema.safeParse(val).success,
- 'Must be a valid IP address'
- ),
- }),
- dnsmasq: z.object({
- interface: z.string().min(1, 'Interface is required').refine(
- (val) => interfaceSchema.safeParse(val).success,
- 'Must be a valid network interface name'
- ),
- }),
- }),
- cluster: z.object({
- endpointIp: z.string().min(1, 'Endpoint IP is required').refine(
- (val) => ipAddressSchema.safeParse(val).success,
- 'Must be a valid IP address'
- ),
- nodes: z.object({
- talos: z.object({
- version: z.string().min(1, 'Talos version is required').refine(
- (val) => versionSchema.safeParse(val).success,
- 'Must be a valid version format (e.g., v1.8.0)'
- ),
- }),
- }),
- }),
-});
-
-// Type exports
-export type Config = z.infer;
-export type ConfigFormData = z.infer;
-
-// Default values for the form
-export const defaultConfigValues: ConfigFormData = {
- server: {
- host: '0.0.0.0',
- port: 5055,
- },
- cloud: {
- domain: 'wildcloud.local',
- internalDomain: 'cluster.local',
- dhcpRange: '192.168.8.100,192.168.8.200',
- dns: {
- ip: '192.168.8.50',
- },
- router: {
- ip: '192.168.8.1',
- },
- dnsmasq: {
- interface: 'eth0',
- },
- },
- cluster: {
- endpointIp: '192.168.8.60',
- nodes: {
- talos: {
- version: 'v1.8.0',
- },
- },
- },
-};
\ No newline at end of file
diff --git a/experimental/app/src/services/api.ts b/experimental/app/src/services/api.ts
deleted file mode 100644
index 3c69b0d..0000000
--- a/experimental/app/src/services/api.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import type { Status, ConfigResponse, Config, HealthResponse, StatusResponse } from '../types';
-
-const API_BASE = 'http://localhost:5055';
-
-class ApiService {
- private baseUrl: string;
-
- constructor(baseUrl: string = API_BASE) {
- this.baseUrl = baseUrl;
- }
-
- private async request(endpoint: string, options?: RequestInit): Promise {
- const url = `${this.baseUrl}${endpoint}`;
- const response = await fetch(url, options);
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- return response.json();
- }
-
- private async requestText(endpoint: string, options?: RequestInit): Promise {
- const url = `${this.baseUrl}${endpoint}`;
- const response = await fetch(url, options);
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- return response.text();
- }
-
- async getStatus(): Promise {
- return this.request('/api/status');
- }
-
- async getHealth(): Promise {
- return this.request('/api/v1/health');
- }
-
- async getConfig(): Promise {
- return this.request('/api/v1/config');
- }
-
- async getConfigYaml(): Promise {
- return this.requestText('/api/v1/config/yaml');
- }
-
- async updateConfigYaml(yamlContent: string): Promise {
- return this.request('/api/v1/config/yaml', {
- method: 'PUT',
- headers: { 'Content-Type': 'text/plain' },
- body: yamlContent
- });
- }
-
- async createConfig(config: Config): Promise {
- return this.request('/api/v1/config', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(config)
- });
- }
-
- async updateConfig(config: Config): Promise {
- return this.request('/api/v1/config', {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(config)
- });
- }
-
- async getDnsmasqConfig(): Promise {
- return this.requestText('/api/v1/dnsmasq/config');
- }
-
- async restartDnsmasq(): Promise {
- return this.request('/api/v1/dnsmasq/restart', {
- method: 'POST'
- });
- }
-
- async downloadPXEAssets(): Promise {
- return this.request('/api/v1/pxe/assets', {
- method: 'POST'
- });
- }
-}
-
-export const apiService = new ApiService();
-export default ApiService;
\ No newline at end of file
diff --git a/experimental/app/src/test/setup.ts b/experimental/app/src/test/setup.ts
deleted file mode 100644
index eccd9d7..0000000
--- a/experimental/app/src/test/setup.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import '@testing-library/jest-dom/vitest';
-
-// Mock window.matchMedia
-Object.defineProperty(window, 'matchMedia', {
- writable: true,
- value: vi.fn().mockImplementation((query: string) => ({
- matches: false,
- media: query,
- onchange: null,
- addListener: vi.fn(), // deprecated
- removeListener: vi.fn(), // deprecated
- addEventListener: vi.fn(),
- removeEventListener: vi.fn(),
- dispatchEvent: vi.fn(),
- })),
-});
-
-// Mock ResizeObserver
-global.ResizeObserver = vi.fn().mockImplementation(() => ({
- observe: vi.fn(),
- unobserve: vi.fn(),
- disconnect: vi.fn(),
-}));
\ No newline at end of file
diff --git a/experimental/app/src/test/test-utils.tsx b/experimental/app/src/test/test-utils.tsx
deleted file mode 100644
index 927e3df..0000000
--- a/experimental/app/src/test/test-utils.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React, { ReactElement } from 'react';
-import { render, RenderOptions } from '@testing-library/react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { ThemeProvider } from '../contexts/ThemeContext';
-
-// Custom render function that includes providers
-const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
- const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- gcTime: 0,
- },
- mutations: {
- retry: false,
- },
- },
- });
-
- return (
-
-
- {children}
-
-
- );
-};
-
-const customRender = (
- ui: ReactElement,
- options?: Omit,
-) => render(ui, { wrapper: AllTheProviders, ...options });
-
-export * from '@testing-library/react';
-export { customRender as render };
\ No newline at end of file
diff --git a/experimental/app/src/types/index.ts b/experimental/app/src/types/index.ts
deleted file mode 100644
index a185b64..0000000
--- a/experimental/app/src/types/index.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-export interface Status {
- status: string;
- version: string;
- uptime: string;
- timestamp: string;
-}
-
-export interface ServerConfig {
- host: string;
- port: number;
-}
-
-export interface CloudDns {
- ip: string;
-}
-
-export interface CloudRouter {
- ip: string;
-}
-
-export interface CloudDnsmasq {
- interface: string;
-}
-
-export interface CloudConfig {
- domain: string;
- internalDomain: string;
- dhcpRange: string;
- dns: CloudDns;
- router: CloudRouter;
- dnsmasq: CloudDnsmasq;
-}
-
-export interface TalosConfig {
- version: string;
-}
-
-export interface NodesConfig {
- talos: TalosConfig;
-}
-
-export interface ClusterConfig {
- endpointIp: string;
- nodes: NodesConfig;
-}
-
-export interface WildcloudConfig {
- repository: string;
- currentPhase?: 'setup' | 'infrastructure' | 'cluster' | 'apps';
- completedPhases?: ('setup' | 'infrastructure' | 'cluster' | 'apps')[];
-}
-
-export interface Config {
- server: ServerConfig;
- cloud: CloudConfig;
- cluster: ClusterConfig;
- wildcloud?: WildcloudConfig;
-}
-
-export interface ConfigResponse {
- configured: boolean;
- config?: Config;
- message?: string;
-}
-
-export interface Message {
- message: string;
- type: 'info' | 'success' | 'error';
-}
-
-export interface LoadingState {
- [key: string]: boolean;
-}
-
-export interface Messages {
- [key: string]: Message;
-}
-
-export interface HealthResponse {
- service: string;
- status: string;
-}
-
-export interface StatusResponse {
- status: string;
-}
\ No newline at end of file
diff --git a/experimental/app/src/utils/formatters.ts b/experimental/app/src/utils/formatters.ts
deleted file mode 100644
index 6028b36..0000000
--- a/experimental/app/src/utils/formatters.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const formatTimestamp = (timestamp: string): string => {
- return new Date(timestamp).toLocaleString();
-};
\ No newline at end of file
diff --git a/experimental/app/src/utils/yamlParser.ts b/experimental/app/src/utils/yamlParser.ts
deleted file mode 100644
index 7042f3c..0000000
--- a/experimental/app/src/utils/yamlParser.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Config } from '../types';
-
-// Simple YAML to JSON parser for basic configuration
-export const parseSimpleYaml = (yamlText: string): Config => {
- const config: Config = {
- cloud: {
- domain: '',
- internalDomain: '',
- dhcpRange: '',
- dns: { ip: '' },
- router: { ip: '' },
- dnsmasq: { interface: '' }
- },
- cluster: {
- endpointIp: '',
- nodes: { talos: { version: '' } }
- },
- server: { host: '', port: 0 }
- };
-
- const lines = yamlText.split('\n');
- let currentSection: 'cloud' | 'cluster' | 'server' | null = null;
- let currentSubsection: string | null = null;
-
- for (const line of lines) {
- const trimmed = line.trim();
- if (!trimmed || trimmed.startsWith('#')) continue;
-
- if (trimmed.startsWith('cloud:')) currentSection = 'cloud';
- else if (trimmed.startsWith('cluster:')) currentSection = 'cluster';
- else if (trimmed.startsWith('server:')) currentSection = 'server';
- else if (trimmed.startsWith('dns:')) currentSubsection = 'dns';
- else if (trimmed.startsWith('router:')) currentSubsection = 'router';
- else if (trimmed.startsWith('dnsmasq:')) currentSubsection = 'dnsmasq';
- else if (trimmed.startsWith('nodes:')) currentSubsection = 'nodes';
- else if (trimmed.startsWith('talos:')) currentSubsection = 'talos';
- else if (trimmed.includes(':')) {
- const [key, value] = trimmed.split(':').map(s => s.trim());
- const cleanValue = value.replace(/"/g, '');
-
- if (currentSection === 'cloud') {
- if (currentSubsection === 'dns') (config.cloud.dns as any)[key] = cleanValue;
- else if (currentSubsection === 'router') (config.cloud.router as any)[key] = cleanValue;
- else if (currentSubsection === 'dnsmasq') (config.cloud.dnsmasq as any)[key] = cleanValue;
- else (config.cloud as any)[key] = cleanValue;
- } else if (currentSection === 'cluster') {
- if (currentSubsection === 'nodes') {
- // Skip nodes level
- } else if (currentSubsection === 'talos') {
- (config.cluster.nodes.talos as any)[key] = cleanValue;
- } else {
- (config.cluster as any)[key] = cleanValue;
- }
- } else if (currentSection === 'server') {
- (config.server as any)[key] = key === 'port' ? parseInt(cleanValue) : cleanValue;
- }
- }
- }
-
- return config;
-};
\ No newline at end of file
diff --git a/experimental/app/src/vite-env.d.ts b/experimental/app/src/vite-env.d.ts
deleted file mode 100644
index 11f02fe..0000000
--- a/experimental/app/src/vite-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/experimental/app/tsconfig.app.json b/experimental/app/tsconfig.app.json
deleted file mode 100644
index 3d79573..0000000
--- a/experimental/app/tsconfig.app.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- "target": "ES2020",
- "useDefineForClassFields": true,
- "lib": [
- "ES2020",
- "DOM",
- "DOM.Iterable"
- ],
- "module": "ESNext",
- "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true,
- "baseUrl": ".",
- "paths": {
- "@/*": [
- "./src/*"
- ]
- }
- },
- "include": [
- "src"
- ]
-}
\ No newline at end of file
diff --git a/experimental/app/tsconfig.json b/experimental/app/tsconfig.json
deleted file mode 100644
index 20ff94b..0000000
--- a/experimental/app/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "files": [],
- "references": [
- {
- "path": "./tsconfig.app.json"
- },
- {
- "path": "./tsconfig.node.json"
- }
- ],
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "@/*": [
- "./src/*"
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/experimental/app/tsconfig.node.json b/experimental/app/tsconfig.node.json
deleted file mode 100644
index 9728af2..0000000
--- a/experimental/app/tsconfig.node.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2022",
- "lib": ["ES2023"],
- "module": "ESNext",
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["vite.config.ts"]
-}
diff --git a/experimental/app/vite.config.ts b/experimental/app/vite.config.ts
deleted file mode 100644
index 38a0f37..0000000
--- a/experimental/app/vite.config.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import path from "path"
-import tailwindcss from "@tailwindcss/vite"
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-
-// https://vite.dev/config/
-export default defineConfig({
- plugins: [react(), tailwindcss()],
- resolve: {
- alias: {
- "@": path.resolve(__dirname, "./src"),
- },
- },
-})
diff --git a/experimental/app/vitest.config.ts b/experimental/app/vitest.config.ts
deleted file mode 100644
index 112e798..0000000
--- a/experimental/app/vitest.config.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-///
-import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-import { resolve } from 'path';
-import { fileURLToPath, URL } from 'node:url';
-
-export default defineConfig({
- plugins: [react()],
- resolve: {
- alias: {
- "@": fileURLToPath(new URL('./src', import.meta.url)),
- },
- },
- test: {
- globals: true,
- environment: 'jsdom',
- setupFiles: ['./src/test/setup.ts'],
- css: true,
- include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- },
-});
\ No newline at end of file
diff --git a/experimental/daemon/Makefile b/experimental/daemon/Makefile
deleted file mode 100644
index 7f2a26f..0000000
--- a/experimental/daemon/Makefile
+++ /dev/null
@@ -1,90 +0,0 @@
-# Default target
-.DEFAULT_GOAL := help
-
-# Build configuration
-BINARY_NAME := wild-api
-VERSION ?= 0.1.1
-BUILD_DIR := build
-
-# Go build configuration
-GO_VERSION := $(shell go version | cut -d' ' -f3)
-GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
-BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
-LDFLAGS := -X main.Version=$(VERSION) -X main.GitCommit=$(GIT_COMMIT) -X main.BuildTime=$(BUILD_TIME)
-
-.PHONY: help build clean test run install check fmt vet lint deps-check version
-
-# Usage: $(call package_deb,architecture,binary_name)
-help:
- @echo "๐๏ธ Wild Cloud API Build System"
- @echo ""
- @echo "๐ฆ Build targets (compile binaries):"
- @echo " build - Build for current architecture"
- @echo ""
- @echo "๐ Quality assurance:"
- @echo " check - Run all checks (fmt + vet + test)"
- @echo " fmt - Format Go code"
- @echo " vet - Run go vet"
- @echo " test - Run tests"
- @echo ""
- @echo "๐ ๏ธ Development:"
- @echo " run - Run application locally"
- @echo " clean - Remove all build artifacts"
- @echo " deps-check - Verify and tidy dependencies"
- @echo " version - Show build information"
- @echo " install - Install to system"
- @echo ""
- @echo "๐ Directory structure:"
- @echo " build/ - Intermediate build artifacts"
-
-build:
- @echo "Building $(BINARY_NAME) for current architecture..."
- @mkdir -p $(BUILD_DIR)
- go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME) .
- @echo "โ
Build complete: $(BUILD_DIR)/$(BINARY_NAME)"
-
-clean:
- @echo "๐งน Cleaning build artifacts..."
- @rm -rf $(BUILD_DIR) $(DIST_DIR) $(DEB_DIR)-* $(DEB_DIR)
- @go clean
- @echo "โ
Clean complete"
-
-test:
- @echo "๐งช Running tests..."
- @go test -v ./...
-
-run:
- @echo "๐ Running $(BINARY_NAME)..."
- @go run -ldflags="$(LDFLAGS)" .
-
-# Code quality targets
-fmt:
- @echo "๐จ Formatting code..."
- @go fmt ./...
- @echo "โ
Format complete"
-
-vet:
- @echo "๐ Running go vet..."
- @go vet ./...
- @echo "โ
Vet complete"
-
-check: fmt vet test
- @echo "โ
All checks passed"
-
-# Dependency management
-deps-check:
- @echo "๐ฆ Checking dependencies..."
- @go mod verify
- @go mod tidy
- @echo "โ
Dependencies verified"
-
-# Version information
-version:
- @echo "Version: $(VERSION)"
- @echo "Git Commit: $(GIT_COMMIT)"
- @echo "Build Time: $(BUILD_TIME)"
- @echo "Go Version: $(GO_VERSION)"
-
-dev:
- go run . &
- echo "Server started on http://localhost:5055"
\ No newline at end of file
diff --git a/experimental/daemon/README.md b/experimental/daemon/README.md
deleted file mode 100644
index 18d8675..0000000
--- a/experimental/daemon/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Wild Cloud API Backend Service
diff --git a/experimental/daemon/build/wild-api b/experimental/daemon/build/wild-api
deleted file mode 100755
index a163841..0000000
Binary files a/experimental/daemon/build/wild-api and /dev/null differ
diff --git a/experimental/daemon/go.mod b/experimental/daemon/go.mod
deleted file mode 100644
index 03b728e..0000000
--- a/experimental/daemon/go.mod
+++ /dev/null
@@ -1,8 +0,0 @@
-module wild-cloud-central
-
-go 1.21
-
-require (
- github.com/gorilla/mux v1.8.1
- gopkg.in/yaml.v3 v3.0.1
-)
diff --git a/experimental/daemon/go.sum b/experimental/daemon/go.sum
deleted file mode 100644
index 3e12cf5..0000000
--- a/experimental/daemon/go.sum
+++ /dev/null
@@ -1,6 +0,0 @@
-github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
-github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/experimental/daemon/internal/config/config.go b/experimental/daemon/internal/config/config.go
deleted file mode 100644
index b12d22e..0000000
--- a/experimental/daemon/internal/config/config.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package config
-
-import (
- "fmt"
- "os"
- "path/filepath"
-
- "gopkg.in/yaml.v3"
-)
-
-// Config represents the main configuration structure
-type Config struct {
- Wildcloud struct {
- Repository string `yaml:"repository" json:"repository"`
- CurrentPhase string `yaml:"currentPhase" json:"currentPhase"`
- CompletedPhases []string `yaml:"completedPhases" json:"completedPhases"`
- } `yaml:"wildcloud" json:"wildcloud"`
- Server struct {
- Port int `yaml:"port" json:"port"`
- Host string `yaml:"host" json:"host"`
- } `yaml:"server" json:"server"`
- Cloud struct {
- Domain string `yaml:"domain" json:"domain"`
- InternalDomain string `yaml:"internalDomain" json:"internalDomain"`
- DNS struct {
- IP string `yaml:"ip" json:"ip"`
- } `yaml:"dns" json:"dns"`
- Router struct {
- IP string `yaml:"ip" json:"ip"`
- } `yaml:"router" json:"router"`
- DHCPRange string `yaml:"dhcpRange" json:"dhcpRange"`
- Dnsmasq struct {
- Interface string `yaml:"interface" json:"interface"`
- } `yaml:"dnsmasq" json:"dnsmasq"`
- } `yaml:"cloud" json:"cloud"`
- Cluster struct {
- EndpointIP string `yaml:"endpointIp" json:"endpointIp"`
- Nodes struct {
- Talos struct {
- Version string `yaml:"version" json:"version"`
- } `yaml:"talos" json:"talos"`
- } `yaml:"nodes" json:"nodes"`
- } `yaml:"cluster" json:"cluster"`
-}
-
-// Load loads configuration from the specified path
-func Load(configPath string) (*Config, error) {
- data, err := os.ReadFile(configPath)
- if err != nil {
- return nil, fmt.Errorf("reading config file %s: %w", configPath, err)
- }
-
- config := &Config{}
- if err := yaml.Unmarshal(data, config); err != nil {
- return nil, fmt.Errorf("parsing config file: %w", err)
- }
-
- // Set defaults
- if config.Server.Port == 0 {
- config.Server.Port = 5055
- }
- if config.Server.Host == "" {
- config.Server.Host = "0.0.0.0"
- }
-
- return config, nil
-}
-
-// Save saves the configuration to the specified path
-func Save(config *Config, configPath string) error {
- // Ensure the directory exists
- if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
- return fmt.Errorf("creating config directory: %w", err)
- }
-
- data, err := yaml.Marshal(config)
- if err != nil {
- return fmt.Errorf("marshaling config: %w", err)
- }
-
- return os.WriteFile(configPath, data, 0644)
-}
-
-// IsEmpty checks if the configuration is empty or uninitialized
-func (c *Config) IsEmpty() bool {
- if c == nil {
- return true
- }
-
- // Check if any essential fields are empty
- return c.Cloud.Domain == "" ||
- c.Cloud.DNS.IP == "" ||
- c.Cluster.Nodes.Talos.Version == ""
-}
diff --git a/experimental/daemon/internal/data/paths.go b/experimental/daemon/internal/data/paths.go
deleted file mode 100644
index 6a85b79..0000000
--- a/experimental/daemon/internal/data/paths.go
+++ /dev/null
@@ -1,130 +0,0 @@
-package data
-
-import (
- "fmt"
- "log"
- "os"
- "path/filepath"
- "strings"
-)
-
-// Paths represents the data directory paths configuration
-type Paths struct {
- ConfigFile string
- DataDir string
- LogsDir string
- AssetsDir string
- DnsmasqConf string
-}
-
-// Manager handles data directory management
-type Manager struct {
- dataDir string
- isDev bool
-}
-
-// NewManager creates a new data manager
-func NewManager() *Manager {
- return &Manager{}
-}
-
-// Initialize sets up the data directory structure
-func (m *Manager) Initialize() error {
- // Detect environment: development vs production
- m.isDev = m.isDevelopmentMode()
-
- var dataDir string
- if m.isDev {
- // Development mode: use .wildcloud in current directory
- cwd, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("failed to get current directory: %w", err)
- }
- dataDir = filepath.Join(cwd, ".wildcloud")
- log.Printf("Running in development mode, using data directory: %s", dataDir)
- } else {
- // Production mode: use standard Linux directories
- dataDir = "/var/lib/wild-cloud-central"
- log.Printf("Running in production mode, using data directory: %s", dataDir)
- }
-
- m.dataDir = dataDir
-
- // Create directory structure
- paths := m.GetPaths()
-
- // Create all necessary directories
- for _, dir := range []string{paths.DataDir, paths.LogsDir, paths.AssetsDir} {
- if err := os.MkdirAll(dir, 0755); err != nil {
- return fmt.Errorf("failed to create directory %s: %w", dir, err)
- }
- }
-
- log.Printf("Data directory structure initialized at: %s", dataDir)
- return nil
-}
-
-// isDevelopmentMode detects if we're running in development mode
-func (m *Manager) isDevelopmentMode() bool {
- // Check multiple indicators for development mode
-
- // 1. Check if GO_ENV is set to development
- if env := os.Getenv("GO_ENV"); env == "development" {
- return true
- }
-
- // 2. Check if running as systemd service (has INVOCATION_ID)
- if os.Getenv("INVOCATION_ID") != "" {
- return false // Running under systemd
- }
-
- // 3. Check if running from a typical development location
- if exe, err := os.Executable(); err == nil {
- // If executable is in current directory or contains "wild-central" without being in /usr/bin
- if strings.Contains(exe, "/usr/bin") || strings.Contains(exe, "/usr/local/bin") {
- return false
- }
- if filepath.Base(exe) == "wild-central" && !strings.HasPrefix(exe, "/") {
- return true
- }
- }
-
- // 4. Check if we can write to /var/lib (if not, probably development)
- if _, err := os.Stat("/var/lib"); err != nil {
- return true
- }
-
- // 5. Default to development if uncertain
- return true
-}
-
-// GetPaths returns the appropriate paths for the current environment
-func (m *Manager) GetPaths() Paths {
- if m.isDev {
- return Paths{
- ConfigFile: filepath.Join(m.dataDir, "config.yaml"),
- DataDir: m.dataDir,
- LogsDir: filepath.Join(m.dataDir, "logs"),
- AssetsDir: filepath.Join(m.dataDir, "assets"),
- DnsmasqConf: filepath.Join(m.dataDir, "dnsmasq.conf"),
- }
- } else {
- return Paths{
- ConfigFile: "/etc/wild-cloud-central/config.yaml",
- DataDir: m.dataDir,
- LogsDir: "/var/log/wild-cloud-central",
- AssetsDir: "/var/www/html/wild-central",
- DnsmasqConf: "/etc/dnsmasq.conf",
- }
- }
-}
-
-// GetDataDir returns the current data directory
-func (m *Manager) GetDataDir() string {
- return m.dataDir
-}
-
-// IsDevelopment returns true if running in development mode
-func (m *Manager) IsDevelopment() bool {
- return m.isDev
-}
diff --git a/experimental/daemon/internal/dnsmasq/config.go b/experimental/daemon/internal/dnsmasq/config.go
deleted file mode 100644
index 2356ba5..0000000
--- a/experimental/daemon/internal/dnsmasq/config.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package dnsmasq
-
-import (
- "fmt"
- "log"
- "os"
- "os/exec"
-
- "wild-cloud-central/internal/config"
-)
-
-// ConfigGenerator handles dnsmasq configuration generation
-type ConfigGenerator struct{}
-
-// NewConfigGenerator creates a new dnsmasq config generator
-func NewConfigGenerator() *ConfigGenerator {
- return &ConfigGenerator{}
-}
-
-// Generate creates a dnsmasq configuration from the app config
-func (g *ConfigGenerator) Generate(cfg *config.Config) string {
- template := `# Configuration file for dnsmasq.
-
-# Basic Settings
-interface=%s
-listen-address=%s
-domain-needed
-bogus-priv
-no-resolv
-
-# DNS Local Resolution - Central server handles these domains authoritatively
-local=/%s/
-address=/%s/%s
-local=/%s/
-address=/%s/%s
-server=1.1.1.1
-server=8.8.8.8
-
-# --- DHCP Settings ---
-dhcp-range=%s,12h
-dhcp-option=3,%s
-dhcp-option=6,%s
-
-# --- PXE Booting ---
-enable-tftp
-tftp-root=/var/ftpd
-
-dhcp-match=set:efi-x86_64,option:client-arch,7
-dhcp-boot=tag:efi-x86_64,ipxe.efi
-dhcp-boot=tag:!efi-x86_64,undionly.kpxe
-
-dhcp-match=set:efi-arm64,option:client-arch,11
-dhcp-boot=tag:efi-arm64,ipxe-arm64.efi
-
-dhcp-userclass=set:ipxe,iPXE
-dhcp-boot=tag:ipxe,http://%s/boot.ipxe
-
-log-queries
-log-dhcp
-`
-
- return fmt.Sprintf(template,
- cfg.Cloud.Dnsmasq.Interface,
- cfg.Cloud.DNS.IP,
- cfg.Cloud.Domain,
- cfg.Cloud.Domain,
- cfg.Cluster.EndpointIP,
- cfg.Cloud.InternalDomain,
- cfg.Cloud.InternalDomain,
- cfg.Cluster.EndpointIP,
- cfg.Cloud.DHCPRange,
- cfg.Cloud.Router.IP,
- cfg.Cloud.DNS.IP,
- cfg.Cloud.DNS.IP,
- )
-}
-
-// WriteConfig writes the dnsmasq configuration to the specified path
-func (g *ConfigGenerator) WriteConfig(cfg *config.Config, configPath string) error {
- configContent := g.Generate(cfg)
-
- log.Printf("Writing dnsmasq config to: %s", configPath)
- if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
- return fmt.Errorf("writing dnsmasq config: %w", err)
- }
-
- return nil
-}
-
-// RestartService restarts the dnsmasq service
-func (g *ConfigGenerator) RestartService() error {
- cmd := exec.Command("sudo", "/usr/bin/systemctl", "restart", "dnsmasq.service")
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("failed to restart dnsmasq: %w", err)
- }
- return nil
-}
diff --git a/experimental/daemon/internal/handlers/dnsmasq.go b/experimental/daemon/internal/handlers/dnsmasq.go
deleted file mode 100644
index 51365d4..0000000
--- a/experimental/daemon/internal/handlers/dnsmasq.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package handlers
-
-import (
- "encoding/json"
- "log"
- "net/http"
-)
-
-// GetDnsmasqConfigHandler handles requests to view the dnsmasq configuration
-func (app *App) GetDnsmasqConfigHandler(w http.ResponseWriter, r *http.Request) {
- if app.Config == nil || app.Config.IsEmpty() {
- http.Error(w, "No configuration available. Please configure the system first.", http.StatusPreconditionFailed)
- return
- }
-
- config := app.DnsmasqManager.Generate(app.Config)
- w.Header().Set("Content-Type", "text/plain")
- w.Write([]byte(config))
-}
-
-// RestartDnsmasqHandler handles requests to restart the dnsmasq service
-func (app *App) RestartDnsmasqHandler(w http.ResponseWriter, r *http.Request) {
- if app.Config == nil || app.Config.IsEmpty() {
- http.Error(w, "No configuration available. Please configure the system first.", http.StatusPreconditionFailed)
- return
- }
-
- // Update dnsmasq config first
- paths := app.DataManager.GetPaths()
- if err := app.DnsmasqManager.WriteConfig(app.Config, paths.DnsmasqConf); err != nil {
- log.Printf("Failed to update dnsmasq config: %v", err)
- http.Error(w, "Failed to update dnsmasq config", http.StatusInternalServerError)
- return
- }
-
- // Restart dnsmasq service
- if err := app.DnsmasqManager.RestartService(); err != nil {
- log.Printf("Failed to restart dnsmasq: %v", err)
- http.Error(w, "Failed to restart dnsmasq service", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"status": "restarted"})
-}
diff --git a/experimental/daemon/internal/handlers/handlers.go b/experimental/daemon/internal/handlers/handlers.go
deleted file mode 100644
index 40aca8a..0000000
--- a/experimental/daemon/internal/handlers/handlers.go
+++ /dev/null
@@ -1,263 +0,0 @@
-package handlers
-
-import (
- "encoding/json"
- "io"
- "log"
- "net/http"
- "os"
- "time"
-
- "wild-cloud-central/internal/config"
- "wild-cloud-central/internal/data"
- "wild-cloud-central/internal/dnsmasq"
-)
-
-// App represents the application with its dependencies
-type App struct {
- Config *config.Config
- StartTime time.Time
- DataManager *data.Manager
- DnsmasqManager *dnsmasq.ConfigGenerator
-}
-
-// NewApp creates a new application instance
-func NewApp() *App {
- return &App{
- StartTime: time.Now(),
- DataManager: data.NewManager(),
- DnsmasqManager: dnsmasq.NewConfigGenerator(),
- }
-}
-
-// HealthHandler handles health check requests
-func (app *App) HealthHandler(w http.ResponseWriter, r *http.Request) {
- response := map[string]string{
- "status": "healthy",
- "service": "wild-cloud-central",
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(response)
-}
-
-// StatusHandler handles status requests for the UI
-func (app *App) StatusHandler(w http.ResponseWriter, r *http.Request) {
- uptime := time.Since(app.StartTime)
-
- response := map[string]interface{}{
- "status": "running",
- "version": "1.0.0",
- "uptime": uptime.String(),
- "timestamp": time.Now().UnixMilli(),
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(response)
-}
-
-// GetConfigHandler handles configuration retrieval requests
-func (app *App) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json")
-
- // Always reload config from file on each request
- paths := app.DataManager.GetPaths()
- cfg, err := config.Load(paths.ConfigFile)
- if err != nil {
- log.Printf("Failed to load config from file: %v", err)
- response := map[string]interface{}{
- "configured": false,
- "message": "No configuration found. Please POST a configuration to /api/v1/config to get started.",
- }
- json.NewEncoder(w).Encode(response)
- return
- }
-
- // Update the cached config with fresh data
- app.Config = cfg
-
- // Check if config is empty/uninitialized
- if cfg.IsEmpty() {
- response := map[string]interface{}{
- "configured": false,
- "message": "Configuration is incomplete. Please complete the setup.",
- }
- json.NewEncoder(w).Encode(response)
- return
- }
-
- response := map[string]interface{}{
- "configured": true,
- "config": cfg,
- }
- json.NewEncoder(w).Encode(response)
-}
-
-// CreateConfigHandler handles configuration creation requests
-func (app *App) CreateConfigHandler(w http.ResponseWriter, r *http.Request) {
- // Only allow config creation if no config exists
- if app.Config != nil && !app.Config.IsEmpty() {
- http.Error(w, "Configuration already exists. Use PUT to update.", http.StatusConflict)
- return
- }
-
- var newConfig config.Config
- if err := json.NewDecoder(r.Body).Decode(&newConfig); err != nil {
- http.Error(w, "Invalid JSON", http.StatusBadRequest)
- return
- }
-
- // Set defaults
- if newConfig.Server.Port == 0 {
- newConfig.Server.Port = 5055
- }
- if newConfig.Server.Host == "" {
- newConfig.Server.Host = "0.0.0.0"
- }
-
- app.Config = &newConfig
-
- // Persist config to file
- paths := app.DataManager.GetPaths()
- if err := config.Save(app.Config, paths.ConfigFile); err != nil {
- log.Printf("Failed to save config: %v", err)
- http.Error(w, "Failed to save config", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"status": "created"})
-}
-
-// UpdateConfigHandler handles configuration update requests
-func (app *App) UpdateConfigHandler(w http.ResponseWriter, r *http.Request) {
- // Check if config exists
- if app.Config == nil || app.Config.IsEmpty() {
- http.Error(w, "No configuration exists. Use POST to create initial configuration.", http.StatusNotFound)
- return
- }
-
- var newConfig config.Config
- if err := json.NewDecoder(r.Body).Decode(&newConfig); err != nil {
- http.Error(w, "Invalid JSON", http.StatusBadRequest)
- return
- }
-
- app.Config = &newConfig
-
- // Persist config to file
- paths := app.DataManager.GetPaths()
- if err := config.Save(app.Config, paths.ConfigFile); err != nil {
- log.Printf("Failed to save config: %v", err)
- http.Error(w, "Failed to save config", http.StatusInternalServerError)
- return
- }
-
- // Regenerate and apply dnsmasq config
- if err := app.DnsmasqManager.WriteConfig(app.Config, paths.DnsmasqConf); err != nil {
- log.Printf("Failed to update dnsmasq config: %v", err)
- http.Error(w, "Failed to update dnsmasq config", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"status": "updated"})
-}
-
-// GetConfigYamlHandler handles raw YAML config file retrieval
-func (app *App) GetConfigYamlHandler(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodGet {
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
- return
- }
-
- paths := app.DataManager.GetPaths()
-
- // Read the raw config file
- yamlContent, err := os.ReadFile(paths.ConfigFile)
- if err != nil {
- if os.IsNotExist(err) {
- http.Error(w, "Configuration file not found", http.StatusNotFound)
- return
- }
- log.Printf("Failed to read config file: %v", err)
- http.Error(w, "Failed to read configuration file", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "text/plain; charset=utf-8")
- w.Write(yamlContent)
-}
-
-// UpdateConfigYamlHandler handles raw YAML config file updates
-func (app *App) UpdateConfigYamlHandler(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPut {
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
- return
- }
-
- // Read the raw YAML content from request body
- yamlContent, err := io.ReadAll(r.Body)
- if err != nil {
- log.Printf("Failed to read request body: %v", err)
- http.Error(w, "Failed to read request body", http.StatusBadRequest)
- return
- }
-
- paths := app.DataManager.GetPaths()
-
- // Write the raw YAML content to file
- if err := os.WriteFile(paths.ConfigFile, yamlContent, 0644); err != nil {
- log.Printf("Failed to write config file: %v", err)
- http.Error(w, "Failed to write configuration file", http.StatusInternalServerError)
- return
- }
-
- // Try to reload the config to validate it and update the in-memory config
- newConfig, err := config.Load(paths.ConfigFile)
- if err != nil {
- log.Printf("Warning: Saved YAML config but failed to parse it: %v", err)
- // File was written but parsing failed - this is a validation warning
- w.Header().Set("Content-Type", "application/json")
- response := map[string]interface{}{
- "status": "saved_with_warnings",
- "warning": "Configuration saved but contains validation errors: " + err.Error(),
- }
- json.NewEncoder(w).Encode(response)
- return
- }
-
- // Update in-memory config if parsing succeeded
- app.Config = newConfig
-
- // Try to regenerate dnsmasq config if the new config is valid
- if err := app.DnsmasqManager.WriteConfig(app.Config, paths.DnsmasqConf); err != nil {
- log.Printf("Warning: Failed to update dnsmasq config: %v", err)
- // Config was saved but dnsmasq update failed
- w.Header().Set("Content-Type", "application/json")
- response := map[string]interface{}{
- "status": "saved_with_warnings",
- "warning": "Configuration saved but failed to update dnsmasq config: " + err.Error(),
- }
- json.NewEncoder(w).Encode(response)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"status": "updated"})
-}
-
-// CORSMiddleware adds CORS headers to responses
-func (app *App) CORSMiddleware(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
- w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
-
- if r.Method == "OPTIONS" {
- w.WriteHeader(http.StatusOK)
- return
- }
-
- next.ServeHTTP(w, r)
- })
-}
diff --git a/experimental/daemon/internal/handlers/pxe.go b/experimental/daemon/internal/handlers/pxe.go
deleted file mode 100644
index f1c9a9a..0000000
--- a/experimental/daemon/internal/handlers/pxe.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package handlers
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net/http"
- "os"
- "path/filepath"
-)
-
-// DownloadPXEAssetsHandler handles requests to download PXE boot assets
-func (app *App) DownloadPXEAssetsHandler(w http.ResponseWriter, r *http.Request) {
- if app.Config == nil || app.Config.IsEmpty() {
- http.Error(w, "No configuration available. Please configure the system first.", http.StatusPreconditionFailed)
- return
- }
-
- if err := app.downloadTalosAssets(); err != nil {
- log.Printf("Failed to download PXE assets: %v", err)
- http.Error(w, "Failed to download PXE assets", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"status": "downloaded"})
-}
-
-// downloadTalosAssets downloads Talos Linux PXE assets
-func (app *App) downloadTalosAssets() error {
- // Get assets directory from data paths
- paths := app.DataManager.GetPaths()
- assetsDir := filepath.Join(paths.AssetsDir, "talos")
-
- log.Printf("Downloading Talos assets to: %s", assetsDir)
- if err := os.MkdirAll(filepath.Join(assetsDir, "amd64"), 0755); err != nil {
- return fmt.Errorf("creating assets directory: %w", err)
- }
-
- // Create Talos bare metal configuration (schematic format)
- bareMetalConfig := `customization:
- extraKernelArgs:
- - net.ifnames=0
- systemExtensions:
- officialExtensions:
- - siderolabs/gvisor
- - siderolabs/intel-ucode`
-
- // Create Talos schematic
- var buf bytes.Buffer
- buf.WriteString(bareMetalConfig)
-
- resp, err := http.Post("https://factory.talos.dev/schematics", "text/yaml", &buf)
- if err != nil {
- return fmt.Errorf("creating Talos schematic: %w", err)
- }
- defer resp.Body.Close()
-
- var schematic struct {
- ID string `json:"id"`
- }
- if err := json.NewDecoder(resp.Body).Decode(&schematic); err != nil {
- return fmt.Errorf("decoding schematic response: %w", err)
- }
-
- log.Printf("Created Talos schematic with ID: %s", schematic.ID)
-
- // Download kernel
- kernelURL := fmt.Sprintf("https://pxe.factory.talos.dev/image/%s/%s/kernel-amd64",
- schematic.ID, app.Config.Cluster.Nodes.Talos.Version)
- if err := downloadFile(kernelURL, filepath.Join(assetsDir, "amd64", "vmlinuz")); err != nil {
- return fmt.Errorf("downloading kernel: %w", err)
- }
-
- // Download initramfs
- initramfsURL := fmt.Sprintf("https://pxe.factory.talos.dev/image/%s/%s/initramfs-amd64.xz",
- schematic.ID, app.Config.Cluster.Nodes.Talos.Version)
- if err := downloadFile(initramfsURL, filepath.Join(assetsDir, "amd64", "initramfs.xz")); err != nil {
- return fmt.Errorf("downloading initramfs: %w", err)
- }
-
- // Create boot.ipxe file
- bootScript := fmt.Sprintf(`#!ipxe
-imgfree
-kernel http://%s/amd64/vmlinuz talos.platform=metal console=tty0 init_on_alloc=1 slab_nomerge pti=on consoleblank=0 nvme_core.io_timeout=4294967295 printk.devkmsg=on ima_template=ima-ng ima_appraise=fix ima_hash=sha512 selinux=1 net.ifnames=0
-initrd http://%s/amd64/initramfs.xz
-boot
-`, app.Config.Cloud.DNS.IP, app.Config.Cloud.DNS.IP)
-
- if err := os.WriteFile(filepath.Join(assetsDir, "boot.ipxe"), []byte(bootScript), 0644); err != nil {
- return fmt.Errorf("writing boot script: %w", err)
- }
-
- // Download iPXE bootloaders
- tftpDir := filepath.Join(paths.AssetsDir, "tftp")
- if err := os.MkdirAll(tftpDir, 0755); err != nil {
- return fmt.Errorf("creating tftp directory: %w", err)
- }
-
- bootloaders := map[string]string{
- "http://boot.ipxe.org/ipxe.efi": filepath.Join(tftpDir, "ipxe.efi"),
- "http://boot.ipxe.org/undionly.kpxe": filepath.Join(tftpDir, "undionly.kpxe"),
- "http://boot.ipxe.org/arm64-efi/ipxe.efi": filepath.Join(tftpDir, "ipxe-arm64.efi"),
- }
-
- for url, path := range bootloaders {
- if err := downloadFile(url, path); err != nil {
- return fmt.Errorf("downloading %s: %w", url, err)
- }
- }
-
- log.Printf("Successfully downloaded PXE assets")
- return nil
-}
-
-// downloadFile downloads a file from a URL to a local path
-func downloadFile(url, filepath string) error {
- resp, err := http.Get(url)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("bad status: %s", resp.Status)
- }
-
- out, err := os.Create(filepath)
- if err != nil {
- return err
- }
- defer out.Close()
-
- _, err = io.Copy(out, resp.Body)
- return err
-}
diff --git a/experimental/daemon/main.go b/experimental/daemon/main.go
deleted file mode 100644
index a06cd52..0000000
--- a/experimental/daemon/main.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "net/http"
-
- "github.com/gorilla/mux"
-
- "wild-cloud-central/internal/config"
- "wild-cloud-central/internal/handlers"
-)
-
-func main() {
- // Create application instance
- app := handlers.NewApp()
-
- // Initialize data directory
- if err := app.DataManager.Initialize(); err != nil {
- log.Fatalf("Failed to initialize data directory: %v", err)
- }
-
- // Load configuration if it exists
- paths := app.DataManager.GetPaths()
- if cfg, err := config.Load(paths.ConfigFile); err != nil {
- log.Printf("No configuration found, starting with empty config: %v", err)
- } else {
- app.Config = cfg
- log.Printf("Configuration loaded successfully")
- }
-
- // Set up HTTP router
- router := mux.NewRouter()
- setupRoutes(app, router)
-
- // Use default server settings if config is empty
- host := "0.0.0.0"
- port := 5055
- if app.Config != nil && app.Config.Server.Host != "" {
- host = app.Config.Server.Host
- }
- if app.Config != nil && app.Config.Server.Port != 0 {
- port = app.Config.Server.Port
- }
-
- addr := fmt.Sprintf("%s:%d", host, port)
- log.Printf("Starting wild-cloud-central server on %s", addr)
-
- if err := http.ListenAndServe(addr, router); err != nil {
- log.Fatal("Server failed to start:", err)
- }
-}
-
-func setupRoutes(app *handlers.App, router *mux.Router) {
- // Add CORS middleware
- router.Use(app.CORSMiddleware)
-
- // API v1 routes
- router.HandleFunc("/api/v1/health", app.HealthHandler).Methods("GET")
- router.HandleFunc("/api/v1/config", app.GetConfigHandler).Methods("GET")
- router.HandleFunc("/api/v1/config", app.UpdateConfigHandler).Methods("PUT")
- router.HandleFunc("/api/v1/config", app.CreateConfigHandler).Methods("POST")
- router.HandleFunc("/api/v1/config/yaml", app.GetConfigYamlHandler).Methods("GET")
- router.HandleFunc("/api/v1/config/yaml", app.UpdateConfigYamlHandler).Methods("PUT")
- router.HandleFunc("/api/v1/dnsmasq/config", app.GetDnsmasqConfigHandler).Methods("GET")
- router.HandleFunc("/api/v1/dnsmasq/restart", app.RestartDnsmasqHandler).Methods("POST")
- router.HandleFunc("/api/v1/pxe/assets", app.DownloadPXEAssetsHandler).Methods("POST")
-
- // UI-specific endpoints
- router.HandleFunc("/api/status", app.StatusHandler).Methods("GET")
-
- // Serve static files
- router.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))
-}
diff --git a/experimental/daemon/tests/integration/debug-container.sh b/experimental/daemon/tests/integration/debug-container.sh
deleted file mode 100755
index c229d0f..0000000
--- a/experimental/daemon/tests/integration/debug-container.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/bash
-
-set -e
-
-echo "๐ณ Starting wild-cloud-central debug container..."
-
-# Build the Docker image if it doesn't exist
-if ! docker images | grep -q wild-cloud-central-test; then
- echo "๐จ Building Docker image..."
- docker build -t wild-cloud-central-test .
-fi
-
-echo ""
-echo "๐ง Starting container with shell access..."
-echo ""
-echo "๐ Access points:"
-echo " - Management UI: http://localhost:9080"
-echo " - API directly: http://localhost:9081"
-echo ""
-echo "๐ก Inside the container you can:"
-echo " - Start services manually: /test-installation.sh"
-echo " - Check logs: journalctl or service status"
-echo " - Test APIs: curl http://localhost:5055/api/v1/health"
-echo " - Modify config: nano /etc/wild-cloud-central/config.yaml"
-echo " - View web files: ls /var/www/html/wild-central/"
-echo ""
-
-# Run container with shell access
-docker run --rm -it \
- -p 127.0.0.1:9081:5055 \
- -p 127.0.0.1:9080:80 \
- -p 127.0.0.1:9053:53/udp \
- -p 127.0.0.1:9067:67/udp \
- -p 127.0.0.1:9069:69/udp \
- --cap-add=NET_ADMIN \
- --cap-add=NET_BIND_SERVICE \
- --name wild-central-debug \
- wild-cloud-central-test \
- /bin/bash
\ No newline at end of file
diff --git a/experimental/daemon/tests/integration/start-background.sh b/experimental/daemon/tests/integration/start-background.sh
deleted file mode 100755
index 82a5856..0000000
--- a/experimental/daemon/tests/integration/start-background.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/bash
-
-set -e
-
-echo "๐ Starting wild-cloud-central in background..."
-
-# Build the Docker image if it doesn't exist
-if ! docker images | grep -q wild-cloud-central-test; then
- echo "๐จ Building Docker image..."
- docker build -t wild-cloud-central-test .
-fi
-
-# Stop any existing container
-docker rm -f wild-central-bg 2>/dev/null || true
-
-echo "๐ Starting services in background..."
-
-# Start container in background
-docker run -d \
- --name wild-central-bg \
- -p 127.0.0.1:9081:5055 \
- -p 127.0.0.1:9080:80 \
- -p 127.0.0.1:9053:53/udp \
- -p 127.0.0.1:9067:67/udp \
- -p 127.0.0.1:9069:69/udp \
- --cap-add=NET_ADMIN \
- --cap-add=NET_BIND_SERVICE \
- wild-cloud-central-test \
- /bin/bash -c '
- # Start nginx
- nginx &
-
- # Start dnsmasq
- dnsmasq --keep-in-foreground --log-facility=- &
-
- # Start wild-cloud-central
- /usr/bin/wild-cloud-central &
-
- # Wait indefinitely
- tail -f /dev/null
- '
-
-echo "โณ Waiting for services to start..."
-sleep 5
-
-# Test if services are running
-if curl -s http://localhost:9081/api/v1/health > /dev/null 2>&1; then
- echo "โ
Services started successfully!"
- echo ""
- echo "๐ Access points (localhost only):"
- echo " - Management UI: http://localhost:9080"
- echo " - API: http://localhost:9081/api/v1/health"
- echo " - DNS: localhost:9053 (for testing)"
- echo " - DHCP: localhost:9067 (for testing)"
- echo " - TFTP: localhost:9069 (for testing)"
- echo ""
- echo "๐ง Container management:"
- echo " - View logs: docker logs wild-central-bg"
- echo " - Stop services: docker stop wild-central-bg"
- echo " - Remove container: docker rm wild-central-bg"
- echo ""
- echo "๐ก Test commands:"
- echo " curl http://localhost:9081/api/v1/health"
- echo " dig @localhost -p 9053 wildcloud.local"
- echo " curl http://localhost:9081/api/v1/dnsmasq/config"
-else
- echo "โ Services failed to start. Check logs with: docker logs wild-central-bg"
- exit 1
-fi
\ No newline at end of file
diff --git a/experimental/daemon/tests/integration/start-interactive.sh b/experimental/daemon/tests/integration/start-interactive.sh
deleted file mode 100755
index ac54bf3..0000000
--- a/experimental/daemon/tests/integration/start-interactive.sh
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/bin/bash
-
-set -e
-
-echo "๐ Starting wild-cloud-central for interactive testing..."
-
-# Build the Docker image if it doesn't exist
-if ! docker images | grep -q wild-cloud-central-test; then
- echo "๐จ Building Docker image..."
- docker build -t wild-cloud-central-test .
-fi
-
-echo ""
-echo "๐ Starting services... This will take a few seconds."
-echo ""
-echo "๐ Access points:"
-echo " - Management UI: http://localhost:9080"
-echo " - API directly: http://localhost:9081"
-echo " - Health check: http://localhost:9081/api/v1/health"
-echo ""
-echo "๐ง Available API endpoints:"
-echo " - GET /api/v1/health"
-echo " - GET /api/v1/config"
-echo " - PUT /api/v1/config"
-echo " - GET /api/v1/dnsmasq/config"
-echo " - POST /api/v1/dnsmasq/restart"
-echo " - POST /api/v1/pxe/assets"
-echo ""
-echo "๐ก Example commands to try:"
-echo " curl http://localhost:9081/api/v1/health"
-echo " curl http://localhost:9081/api/v1/config"
-echo " curl http://localhost:9081/api/v1/dnsmasq/config"
-echo " curl -X POST http://localhost:9081/api/v1/pxe/assets"
-echo ""
-echo "๐ Press Ctrl+C to stop all services"
-echo ""
-
-# Create a custom startup script that keeps services running
-docker run --rm -it \
- -p 127.0.0.1:9081:5055 \
- -p 127.0.0.1:9080:80 \
- -p 127.0.0.1:9053:53/udp \
- -p 127.0.0.1:9067:67/udp \
- -p 127.0.0.1:9069:69/udp \
- --cap-add=NET_ADMIN \
- --cap-add=NET_BIND_SERVICE \
- --name wild-central-interactive \
- wild-cloud-central-test \
- /bin/bash -c '
- echo "๐ง Starting all services..."
-
- # Start nginx
- nginx &
- NGINX_PID=$!
-
- # Start dnsmasq
- dnsmasq --keep-in-foreground --log-facility=- &
- DNSMASQ_PID=$!
-
- # Start wild-cloud-central
- /usr/bin/wild-cloud-central &
- SERVICE_PID=$!
-
- # Wait for services to start
- sleep 3
-
- echo "โ
All services started!"
- echo " - nginx (PID: $NGINX_PID)"
- echo " - dnsmasq (PID: $DNSMASQ_PID)"
- echo " - wild-cloud-central (PID: $SERVICE_PID)"
- echo ""
- echo "๐ Services are now available:"
- echo " - Web UI: http://localhost:9080"
- echo " - API: http://localhost:9081"
- echo ""
-
- # Function to handle shutdown
- shutdown() {
- echo ""
- echo "๐ Shutting down services..."
- kill $SERVICE_PID $DNSMASQ_PID $NGINX_PID 2>/dev/null || true
- echo "โ
Shutdown complete."
- exit 0
- }
-
- # Set up signal handlers
- trap shutdown SIGTERM SIGINT
-
- # Keep container running and wait for signals
- echo "โจ Container is ready! Press Ctrl+C to stop."
- wait
- '
\ No newline at end of file
diff --git a/experimental/daemon/tests/integration/stop-background.sh b/experimental/daemon/tests/integration/stop-background.sh
deleted file mode 100755
index 12470bb..0000000
--- a/experimental/daemon/tests/integration/stop-background.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-echo "๐ Stopping wild-cloud-central background services..."
-
-if docker ps | grep -q wild-central-bg; then
- docker stop wild-central-bg
- docker rm wild-central-bg
- echo "โ
Services stopped and container removed."
-else
- echo "โน๏ธ No background services running."
-fi
\ No newline at end of file
diff --git a/experimental/daemon/tests/integration/test-docker.sh b/experimental/daemon/tests/integration/test-docker.sh
deleted file mode 100755
index 168a7f2..0000000
--- a/experimental/daemon/tests/integration/test-docker.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-set -e
-
-echo "๐งช Testing wild-cloud-central Docker installation..."
-
-# Change to project root directory
-cd "$(dirname "$0")/../.."
-
-# Build the Docker image
-echo "๐จ Building Docker image..."
-docker build -t wild-cloud-central-test .
-
-# Run the container to test installation
-echo "๐ Running installation test..."
-echo "Access points after container starts:"
-echo " - Management UI: http://localhost:9080"
-echo " - API directly: http://localhost:9055"
-echo ""
-docker run --rm -p 9055:5055 -p 9080:80 wild-cloud-central-test
\ No newline at end of file
diff --git a/experimental/daemon/tests/test-installation.sh b/experimental/daemon/tests/test-installation.sh
deleted file mode 100755
index 6a0dab6..0000000
--- a/experimental/daemon/tests/test-installation.sh
+++ /dev/null
@@ -1,146 +0,0 @@
-#!/bin/bash
-
-set -e
-
-echo "๐ Testing wild-cloud-central installation..."
-
-# Verify the binary was installed
-echo "โ
Checking binary installation..."
-if [ -f "/usr/bin/wild-cloud-central" ]; then
- echo " Binary installed at /usr/bin/wild-cloud-central"
-else
- echo "โ Binary not found at /usr/bin/wild-cloud-central"
- exit 1
-fi
-
-# Verify config was installed
-echo "โ
Checking configuration..."
-if [ -f "/etc/wild-cloud-central/config.yaml" ]; then
- echo " Config installed at /etc/wild-cloud-central/config.yaml"
-else
- echo "โ Config not found at /etc/wild-cloud-central/config.yaml"
- exit 1
-fi
-
-# Verify systemd service file was installed
-echo "โ
Checking systemd service..."
-if [ -f "/etc/systemd/system/wild-cloud-central.service" ]; then
- echo " Service file installed at /etc/systemd/system/wild-cloud-central.service"
-else
- echo "โ Service file not found"
- exit 1
-fi
-
-# Verify nginx config was installed
-echo "โ
Checking nginx configuration..."
-if [ -f "/etc/nginx/sites-available/wild-central" ]; then
- echo " Nginx config installed at /etc/nginx/sites-available/wild-central"
- # Enable the site for testing
- ln -sf /etc/nginx/sites-available/wild-central /etc/nginx/sites-enabled/
- rm -f /etc/nginx/sites-enabled/default
-else
- echo "โ Nginx config not found"
- exit 1
-fi
-
-# Verify web assets were installed
-echo "โ
Checking web assets..."
-if [ -f "/var/www/html/wild-central/index.html" ]; then
- echo " Web assets installed at /var/www/html/wild-central/"
-else
- echo "โ Web assets not found"
- exit 1
-fi
-
-# Start nginx (simulating systemd)
-echo "๐ง Starting nginx..."
-nginx &
-NGINX_PID=$!
-
-# Start dnsmasq (simulating systemd)
-echo "๐ง Starting dnsmasq..."
-dnsmasq --keep-in-foreground --log-facility=- &
-DNSMASQ_PID=$!
-
-# Start wild-cloud-central service (simulating systemd)
-echo "๐ง Starting wild-cloud-central service..."
-/usr/bin/wild-cloud-central &
-SERVICE_PID=$!
-
-# Wait for service to start
-echo "โณ Waiting for services to start..."
-sleep 5
-
-# Test health endpoint
-echo "๐ฉบ Testing health endpoint..."
-if curl -s http://localhost:5055/api/v1/health | grep -q "healthy"; then
- echo " โ
Health check passed"
-else
- echo " โ Health check failed"
- exit 1
-fi
-
-# Test configuration endpoint
-echo "๐ง Testing configuration endpoint..."
-CONFIG_RESPONSE=$(curl -s http://localhost:5055/api/v1/config)
-if echo "$CONFIG_RESPONSE" | grep -q "Server"; then
- echo " โ
Configuration endpoint working"
-else
- echo " โ Configuration endpoint failed"
- echo " Response: $CONFIG_RESPONSE"
- echo " Checking if service is still running..."
- if kill -0 $SERVICE_PID 2>/dev/null; then
- echo " Service is running"
- else
- echo " Service has died"
- fi
- exit 1
-fi
-
-# Test dnsmasq config generation
-echo "๐ง Testing dnsmasq config generation..."
-if curl -s http://localhost:5055/api/v1/dnsmasq/config | grep -q "interface"; then
- echo " โ
Dnsmasq config generation working"
-else
- echo " โ Dnsmasq config generation failed"
- exit 1
-fi
-
-# Test web interface accessibility (through nginx)
-echo "๐ Testing web interface..."
-if curl -s http://localhost:80/ | grep -q "Wild Cloud Central"; then
- echo " โ
Web interface accessible through nginx"
-else
- echo " โ Web interface not accessible"
- exit 1
-fi
-
-echo ""
-echo "๐ All installation tests passed!"
-echo ""
-echo "Services running:"
-echo " - wild-cloud-central: http://localhost:5055"
-echo " - Web interface: http://localhost:80"
-echo " - API health: http://localhost:5055/api/v1/health"
-echo ""
-echo "Installation simulation successful! ๐"
-
-# Keep services running for manual testing
-echo "Services will continue running. Press Ctrl+C to stop."
-
-# Function to handle shutdown
-shutdown() {
- echo ""
- echo "๐ Shutting down services..."
- kill $SERVICE_PID 2>/dev/null || true
- kill $DNSMASQ_PID 2>/dev/null || true
- kill $NGINX_PID 2>/dev/null || true
- echo "Shutdown complete."
- exit 0
-}
-
-# Set up signal handlers
-trap shutdown SIGTERM SIGINT
-
-# Wait for signals
-wait
\ No newline at end of file