From e675d7b6af4d4755c0f427d24876e4efbbd90f4f Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 29 Jul 2026 13:47:45 +0000 Subject: [PATCH] Initial commit --- .gitignore | 24 + .oxlintrc.json | 8 + README.md | 265 + index.html | 13 + package-lock.json | 3815 ++++++++++++ package.json | 51 + public/favicon.svg | 1 + public/icons.svg | 24 + src-tauri/Cargo.lock | 5298 +++++++++++++++++ src-tauri/Cargo.toml | 32 + src-tauri/build.rs | 3 + src-tauri/src/connection.rs | 430 ++ src-tauri/src/error.rs | 44 + src-tauri/src/lib.rs | 727 +++ src-tauri/src/main.rs | 5 + src-tauri/src/proxmox.rs | 226 + src-tauri/src/websocket.rs | 253 + src-tauri/target/.rustc_info.json | 1 + src-tauri/target/CACHEDIR.TAG | 3 + src-tauri/target/debug/.cargo-build-lock | 0 src-tauri/target/debug/.cargo-lock | 0 .../libc-19124a20af635abc/invoked.timestamp | 1 + .../output-build-script-build-script-build | 2 + .../invoked.timestamp | 1 + .../output-build-script-build-script-build | 2 + .../quote-6dff9724e4e81362/invoked.timestamp | 1 + .../output-build-script-build-script-build | 2 + .../invoked.timestamp | 1 + .../output-build-script-build-script-build | 2 + .../dep-lib-unicode_ident | Bin 0 -> 14 bytes .../invoked.timestamp | 1 + .../lib-unicode_ident | 1 + .../lib-unicode_ident.json | 1 + ...0af635abc.3ufr5450lp20sxhbw3b5lnbvs.rcgu.o | Bin 0 -> 2560 bytes ...script_build.a30a70663bab765a-cgu.0.rcgu.o | Bin 0 -> 188848 bytes ...script_build.a30a70663bab765a-cgu.1.rcgu.o | Bin 0 -> 135328 bytes .../build_script_build-19124a20af635abc.d | 5 + ...3fe64eada.1cb4wof2950oslyk39975h0vc.rcgu.o | Bin 0 -> 2560 bytes ...script_build.3e289dc730a2dac7-cgu.0.rcgu.o | Bin 0 -> 199744 bytes .../build_script_build-28cd7e73fe64eada.d | 5 + ...script_build.290c8a9cd7fd2ff3-cgu.0.rcgu.o | Bin 0 -> 119664 bytes ...4e4e81362.cb5aizg7jyp8yonp8exnihyxb.rcgu.o | Bin 0 -> 2560 bytes .../build_script_build-6dff9724e4e81362.d | 5 + ...1ddfe8493.93o87lhgng2qq8bftj8op6z8i.rcgu.o | Bin 0 -> 2560 bytes ...script_build.adfdc0c4a054f8c7-cgu.0.rcgu.o | Bin 0 -> 185664 bytes .../build_script_build-e613c711ddfe8493.d | 5 + .../libunicode_ident-8443eb632a3fbe4c.rlib | Bin 0 -> 54846 bytes .../libunicode_ident-8443eb632a3fbe4c.rmeta | Bin 0 -> 32893 bytes .../deps/unicode_ident-8443eb632a3fbe4c.d | 8 + src-tauri/tauri.conf.json | 38 + src/App.css | 1 + src/App.tsx | 168 + src/assets/hero.png | Bin 0 -> 13057 bytes src/assets/react.svg | 1 + src/assets/vite.svg | 1 + src/components/ErrorBoundary.tsx | 57 + src/components/backups/BackupList.tsx | 410 ++ .../backups/dialogs/CreateBackupJobDialog.tsx | 198 + .../backups/dialogs/EditBackupJobDialog.tsx | 205 + .../backups/dialogs/RestoreBackupDialog.tsx | 139 + src/components/command/CommandPalette.tsx | 607 ++ .../connections/ConnectionDialog.tsx | 132 + src/components/console/TerminalConsole.tsx | 255 + src/components/console/VNCConsole.tsx | 117 + src/components/dashboard/ActivityFeed.tsx | 133 + src/components/dashboard/NodeHealthGrid.tsx | 192 + src/components/dashboard/QuickActions.tsx | 66 + src/components/dashboard/ResourceGauge.tsx | 106 + src/components/layout/Dashboard.tsx | 161 + src/components/layout/Sidebar.tsx | 125 + src/components/nodes/NodeDetail.tsx | 225 + src/components/settings/ConnectionManager.tsx | 121 + src/components/settings/SettingsPage.tsx | 79 + src/components/settings/ThemeSwitcher.tsx | 39 + src/components/storage/StorageCard.tsx | 134 + src/components/storage/StorageDetail.tsx | 281 + src/components/storage/StorageOverview.tsx | 90 + src/components/tasks/TaskList.tsx | 346 ++ src/components/tasks/TaskStatusBar.tsx | 47 + src/components/ui/button.tsx | 52 + src/components/ui/card.tsx | 75 + src/components/ui/dialog.tsx | 116 + src/components/ui/input.tsx | 21 + src/components/ui/label.tsx | 23 + src/components/ui/scroll-area.tsx | 45 + src/components/ui/status-badge.tsx | 60 + src/components/ui/tabs.tsx | 52 + src/components/ui/toast.tsx | 120 + src/components/vms/ContainerList.tsx | 211 + src/components/vms/VMDetail.tsx | 288 + src/components/vms/VMList.tsx | 228 + src/components/vms/dialogs/AddDiskDialog.tsx | 111 + src/components/vms/dialogs/AddNICDialog.tsx | 140 + .../vms/dialogs/CreateSnapshotDialog.tsx | 113 + src/components/vms/dialogs/EditNICDialog.tsx | 126 + src/components/vms/dialogs/MigrateDialog.tsx | 133 + src/components/vms/dialogs/MoveDiskDialog.tsx | 81 + .../vms/dialogs/ResizeDiskDialog.tsx | 95 + src/components/vms/tabs/ConsoleTab.tsx | 257 + src/components/vms/tabs/DisksTab.tsx | 217 + src/components/vms/tabs/HardwareTab.tsx | 186 + src/components/vms/tabs/NetworkTab.tsx | 210 + src/components/vms/tabs/OverviewTab.tsx | 184 + src/components/vms/tabs/SnapshotsTab.tsx | 231 + src/hooks/useProxmox.ts | 602 ++ src/hooks/useWebSocket.ts | 116 + src/index.css | 66 + src/lib/tauri.ts | 521 ++ src/lib/utils.ts | 6 + src/main.tsx | 10 + src/stores/connectionStore.ts | 57 + src/stores/uiStore.ts | 63 + src/types/connection.ts | 45 + src/types/novnc.d.ts | 48 + src/types/proxmox.ts | 229 + tsconfig.app.json | 32 + tsconfig.json | 7 + tsconfig.node.json | 23 + vite.config.ts | 25 + 119 files changed, 20965 insertions(+) create mode 100644 .gitignore create mode 100644 .oxlintrc.json create mode 100644 README.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/favicon.svg create mode 100644 public/icons.svg create mode 100644 src-tauri/Cargo.lock create mode 100644 src-tauri/Cargo.toml create mode 100644 src-tauri/build.rs create mode 100644 src-tauri/src/connection.rs create mode 100644 src-tauri/src/error.rs create mode 100644 src-tauri/src/lib.rs create mode 100644 src-tauri/src/main.rs create mode 100644 src-tauri/src/proxmox.rs create mode 100644 src-tauri/src/websocket.rs create mode 100644 src-tauri/target/.rustc_info.json create mode 100644 src-tauri/target/CACHEDIR.TAG create mode 100644 src-tauri/target/debug/.cargo-build-lock create mode 100644 src-tauri/target/debug/.cargo-lock create mode 100644 src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/invoked.timestamp create mode 100644 src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/output-build-script-build-script-build create mode 100644 src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/invoked.timestamp create mode 100644 src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/output-build-script-build-script-build create mode 100644 src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/invoked.timestamp create mode 100644 src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/output-build-script-build-script-build create mode 100644 src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/invoked.timestamp create mode 100644 src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/output-build-script-build-script-build create mode 100644 src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/dep-lib-unicode_ident create mode 100644 src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/invoked.timestamp create mode 100644 src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident create mode 100644 src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident.json create mode 100644 src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.3ufr5450lp20sxhbw3b5lnbvs.rcgu.o create mode 100644 src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.build_script_build.a30a70663bab765a-cgu.0.rcgu.o create mode 100644 src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.build_script_build.a30a70663bab765a-cgu.1.rcgu.o create mode 100644 src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.d create mode 100644 src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.1cb4wof2950oslyk39975h0vc.rcgu.o create mode 100644 src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.build_script_build.3e289dc730a2dac7-cgu.0.rcgu.o create mode 100644 src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.d create mode 100644 src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.build_script_build.290c8a9cd7fd2ff3-cgu.0.rcgu.o create mode 100644 src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.cb5aizg7jyp8yonp8exnihyxb.rcgu.o create mode 100644 src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.d create mode 100644 src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.93o87lhgng2qq8bftj8op6z8i.rcgu.o create mode 100644 src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.build_script_build.adfdc0c4a054f8c7-cgu.0.rcgu.o create mode 100644 src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.d create mode 100644 src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rlib create mode 100644 src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rmeta create mode 100644 src-tauri/target/debug/deps/unicode_ident-8443eb632a3fbe4c.d create mode 100644 src-tauri/tauri.conf.json create mode 100644 src/App.css create mode 100644 src/App.tsx create mode 100644 src/assets/hero.png create mode 100644 src/assets/react.svg create mode 100644 src/assets/vite.svg create mode 100644 src/components/ErrorBoundary.tsx create mode 100644 src/components/backups/BackupList.tsx create mode 100644 src/components/backups/dialogs/CreateBackupJobDialog.tsx create mode 100644 src/components/backups/dialogs/EditBackupJobDialog.tsx create mode 100644 src/components/backups/dialogs/RestoreBackupDialog.tsx create mode 100644 src/components/command/CommandPalette.tsx create mode 100644 src/components/connections/ConnectionDialog.tsx create mode 100644 src/components/console/TerminalConsole.tsx create mode 100644 src/components/console/VNCConsole.tsx create mode 100644 src/components/dashboard/ActivityFeed.tsx create mode 100644 src/components/dashboard/NodeHealthGrid.tsx create mode 100644 src/components/dashboard/QuickActions.tsx create mode 100644 src/components/dashboard/ResourceGauge.tsx create mode 100644 src/components/layout/Dashboard.tsx create mode 100644 src/components/layout/Sidebar.tsx create mode 100644 src/components/nodes/NodeDetail.tsx create mode 100644 src/components/settings/ConnectionManager.tsx create mode 100644 src/components/settings/SettingsPage.tsx create mode 100644 src/components/settings/ThemeSwitcher.tsx create mode 100644 src/components/storage/StorageCard.tsx create mode 100644 src/components/storage/StorageDetail.tsx create mode 100644 src/components/storage/StorageOverview.tsx create mode 100644 src/components/tasks/TaskList.tsx create mode 100644 src/components/tasks/TaskStatusBar.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/status-badge.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/toast.tsx create mode 100644 src/components/vms/ContainerList.tsx create mode 100644 src/components/vms/VMDetail.tsx create mode 100644 src/components/vms/VMList.tsx create mode 100644 src/components/vms/dialogs/AddDiskDialog.tsx create mode 100644 src/components/vms/dialogs/AddNICDialog.tsx create mode 100644 src/components/vms/dialogs/CreateSnapshotDialog.tsx create mode 100644 src/components/vms/dialogs/EditNICDialog.tsx create mode 100644 src/components/vms/dialogs/MigrateDialog.tsx create mode 100644 src/components/vms/dialogs/MoveDiskDialog.tsx create mode 100644 src/components/vms/dialogs/ResizeDiskDialog.tsx create mode 100644 src/components/vms/tabs/ConsoleTab.tsx create mode 100644 src/components/vms/tabs/DisksTab.tsx create mode 100644 src/components/vms/tabs/HardwareTab.tsx create mode 100644 src/components/vms/tabs/NetworkTab.tsx create mode 100644 src/components/vms/tabs/OverviewTab.tsx create mode 100644 src/components/vms/tabs/SnapshotsTab.tsx create mode 100644 src/hooks/useProxmox.ts create mode 100644 src/hooks/useWebSocket.ts create mode 100644 src/index.css create mode 100644 src/lib/tauri.ts create mode 100644 src/lib/utils.ts create mode 100644 src/main.tsx create mode 100644 src/stores/connectionStore.ts create mode 100644 src/stores/uiStore.ts create mode 100644 src/types/connection.ts create mode 100644 src/types/novnc.d.ts create mode 100644 src/types/proxmox.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# 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/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..6fa991d --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..94f2f96 --- /dev/null +++ b/README.md @@ -0,0 +1,265 @@ +# ProxmoxDesktop + +A cross-platform desktop application for managing Proxmox VE servers and clusters. Built with Tauri, React, and TypeScript. + +## Features + +### Phase 1: Foundation (Current) +- ✅ Multi-server connection management +- ✅ API token authentication +- ✅ Self-signed certificate handling (TOFU) +- ✅ OS keyring integration for secure credential storage +- ✅ Dashboard with cluster overview +- ✅ Node status monitoring +- ✅ Real-time resource usage tracking + +### Planned Features +- VM & Container lifecycle management (start, stop, reboot, shutdown) +- Console access (noVNC for VMs, xterm.js for containers) +- Disk management (add, resize, remove, move) +- Network interface management +- Backup job management and restore +- Snapshot management +- System tray integration +- Command palette (Cmd/Ctrl+K) +- Automatic failover with primary/fallback endpoints + +## Tech Stack + +### Frontend +- **React 19** - UI framework +- **TypeScript** - Type safety +- **Vite** - Build tool and dev server +- **Tailwind CSS** - Utility-first styling +- **shadcn/ui** - Component library (Radix UI + Tailwind) +- **TanStack Query** - Server state management +- **Zustand** - Client state management +- **Lucide React** - Icons + +### Backend (Tauri) +- **Tauri 2** - Desktop app framework +- **Rust** - Backend logic +- **reqwest** - HTTP client with TLS support +- **keyring** - OS keyring integration +- **tokio** - Async runtime + +## Prerequisites + +### System Dependencies + +#### Linux (Ubuntu/Debian) +```bash +sudo apt update +sudo apt install -y \ + build-essential \ + curl \ + wget \ + file \ + libxdo-dev \ + libssl-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libwebkit2gtk-4.1-dev \ + webkit2gtk-driver +``` + +#### macOS +```bash +# Install Xcode Command Line Tools +xcode-select --install + +# Install Homebrew dependencies +brew install rust +``` + +#### Windows +```powershell +# Install Visual Studio Build Tools +# Download from: https://visualstudio.microsoft.com/visual-cpp-build-tools/ +# Select "Desktop development with C++" + +# Install Rust via rustup +winget install Rustlang.Rustup +``` + +### Node.js +```bash +# Install Node.js 20+ via nvm (recommended) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +nvm install 20 +nvm use 20 +``` + +### Rust +```bash +# Install Rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env +``` + +## Installation + +```bash +# Clone the repository +git clone +cd ProxmoxDesktop + +# Install Node.js dependencies +npm install + +# Install Tauri CLI globally (optional) +npm install -g @tauri-apps/cli +``` + +## Development + +### Run in Development Mode + +```bash +# Start the Vite dev server +npm run dev + +# In a separate terminal, run the Tauri app +npm run tauri dev +``` + +The app will automatically reload when you make changes to the frontend or backend code. + +### Build for Production + +```bash +# Build the application +npm run tauri build +``` + +The built application will be in `src-tauri/target/release/bundle/`. + +### Run Tests + +```bash +# Run linter +npm run lint + +# Type check +npm run tsc --noEmit +``` + +## Project Structure + +``` +ProxmoxDesktop/ +├── src/ # React frontend +│ ├── components/ +│ │ ├── ui/ # shadcn/ui components +│ │ ├── layout/ # Layout components (Sidebar, Dashboard) +│ │ └── connections/ # Connection management UI +│ ├── hooks/ # React Query hooks +│ ├── stores/ # Zustand stores +│ ├── lib/ # Utilities and Tauri IPC +│ ├── types/ # TypeScript types +│ ├── App.tsx # Main app component +│ ├── main.tsx # Entry point +│ └── index.css # Global styles +├── src-tauri/ # Tauri/Rust backend +│ ├── src/ +│ │ ├── main.rs # Entry point +│ │ ├── lib.rs # Tauri commands and app setup +│ │ ├── connection.rs # Connection manager +│ │ ├── proxmox.rs # Proxmox API types +│ │ └── error.rs # Error handling +│ ├── Cargo.toml # Rust dependencies +│ └── tauri.conf.json # Tauri configuration +├── package.json # Node.js dependencies +├── vite.config.ts # Vite configuration +└── tsconfig.json # TypeScript configuration +``` + +## Architecture + +### Frontend-Backend Communication + +The frontend communicates with the Rust backend via Tauri's IPC (Inter-Process Communication): + +``` +React Component + ↓ +Tauri IPC (invoke) + ↓ +Rust Backend + ↓ +Proxmox API (HTTPS) +``` + +### Connection Management + +The app supports multiple simultaneous connections to Proxmox servers/clusters: +- Each connection has a primary endpoint and optional fallback endpoints +- Automatic failover when the primary endpoint is unreachable +- Credentials stored securely in OS keyring +- Certificate fingerprints cached for TOFU (Trust On First Use) + +### State Management + +- **TanStack Query**: Server state (API data, caching, refetching) +- **Zustand**: Client state (UI state, active connection, preferences) + +## Configuration + +### Proxmox API Token + +To generate an API token in Proxmox: +1. Go to Datacenter → Permissions → API Tokens +2. Click "Add" +3. Select a user (e.g., root@pam) +4. Enter a Token ID (e.g., "desktop") +5. Uncheck "Privilege Separation" for full access +6. Copy the token (format: `user@realm!tokenid=secret`) + +### Connection Settings + +When adding a connection, you'll need: +- **Connection Name**: A friendly name for the connection +- **Server URL**: The Proxmox server URL (e.g., `https://192.168.1.10:8006`) +- **API Token**: The API token generated above + +## Troubleshooting + +### Linux: Missing System Dependencies + +If you see errors about missing libraries: +```bash +sudo apt install -y libwebkit2gtk-4.1-dev build-essential libssl-dev +``` + +### macOS: Code Signing Issues + +For development, you may need to allow the app in System Preferences → Security & Privacy. + +### Windows: Build Errors + +Make sure you have: +- Visual Studio Build Tools with "Desktop development with C++" +- WebView2 (usually pre-installed on Windows 10/11) + +### Self-Signed Certificates + +The app will prompt you to trust self-signed certificates on first connection. The certificate fingerprint is stored for future connections. + +## Contributing + +Contributions are welcome! Please: +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Run `npm run lint` and fix any issues +5. Submit a pull request + +## License + +MIT + +## Acknowledgments + +- [Proxmox VE](https://www.proxmox.com/en/proxmox-ve) - The amazing virtualization platform +- [Tauri](https://tauri.app/) - Build smaller, faster, more secure desktop apps +- [shadcn/ui](https://ui.shadcn.com/) - Beautifully designed components diff --git a/index.html b/index.html new file mode 100644 index 0000000..2d5f165 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + ProxmoxDesktop + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b77fe81 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3815 @@ +{ + "name": "proxmoxdesktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "proxmoxdesktop", + "version": "0.1.0", + "dependencies": { + "@novnc/novnc": "^1.7.0", + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-label": "^2.1.15", + "@radix-ui/react-scroll-area": "^1.2.18", + "@radix-ui/react-select": "^2.3.7", + "@radix-ui/react-separator": "^1.1.15", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-switch": "^1.3.7", + "@radix-ui/react-tabs": "^1.1.21", + "@radix-ui/react-tooltip": "^1.2.16", + "@tanstack/react-query": "^5.101.4", + "@tauri-apps/api": "^2.11.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.27.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "recharts": "^3.10.1", + "tailwind-merge": "^3.6.0", + "xterm": "^5.3.0", + "xterm-addon-fit": "^0.8.0", + "zustand": "^5.0.14" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@tauri-apps/cli": "^2.11.4", + "@types/node": "^24.13.2", + "@types/novnc": "^0.0.27", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "tailwindcss": "^4.3.3", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } + }, + "node_modules/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.0.tgz", + "integrity": "sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^2.0.0-alpha.3", + "@emnapi/runtime": "^2.0.0-alpha.3" + } + }, + "node_modules/@novnc/novnc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@novnc/novnc/-/novnc-1.7.0.tgz", + "integrity": "sha512-ucEJOx4T2avIRCleodk7YobZj5O2Ga2AeLfQ69A/yjG9HHba2+PDgwSkN3FttrmG+70ZGx21sElNFouK13RzyA==", + "license": "MPL-2.0" + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.76.0.tgz", + "integrity": "sha512-ZHIE5Zt9AsPDcY4nOlofXt0YfneEeo+QrKMPcPzLf2Z6Q8VtV2W73d7SFJ920WUwyik783u/doKCs3KXdwG+7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.76.0.tgz", + "integrity": "sha512-shm/ngQilHK6bs+ElJWa4oHfNj5vL1Gl/iVEJldTQjpr0/67oSgr0KUpbmcnLig5Fo0v/l6j2567A7TOL89ONA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.76.0.tgz", + "integrity": "sha512-rvJmrAPKSQ9aWJ6wIS6CK2tJjwzfW0ApQH9qokq6sfDvmHwoyIHxHFMq7z7i7GiV6fdE6s8qvBqWKPTu8RmT6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.76.0.tgz", + "integrity": "sha512-U/zYdb7VYKGY6pA9Vd2rYl9O/HlCylcOlb5PGPvVLtg+oLGsk6H3XGKEMHKyqD3nmmtmlmwb/8SwU2vfSAtvMw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.76.0.tgz", + "integrity": "sha512-WvKG9CAriuo0XNiFzpXjDngUZcRGFNpaK2kLyMUsnJlShxkT96u+BpJQ3KqdQwGOrvI14L6V8bAwXwAYNNY6Jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.76.0.tgz", + "integrity": "sha512-qJ5+RH99TqFRq3UCDxkW0zJJu9c+OAHFY72vGlxZLEpuO+MpKo3POgqb8sYipL9KYm8XY6ofb0HsOuvY6hQNqQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.76.0.tgz", + "integrity": "sha512-PvPCVptkgVARsucgIqFQQcSmJ6xc6GtnVB5bRBekRahTc9eObMtjHfMjy5M+C2tHt5UCMttWM9RuSk/H9NqYeg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.76.0.tgz", + "integrity": "sha512-3KeFDx8Bu4HPAXbuHZOr/oHvN+QT+JQhMw/NYPz7Z071xLSsG27Jfh9PIQVEY7hk1I+jr43ExqRIeJ6VKk2yLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.76.0.tgz", + "integrity": "sha512-oPFkkKTgl0K/EIg9fQ8oA3IGcI05/Mq1en04iFa41mmNPT+6KEiByVazTOZZJiHMBBrbsns1YJ2e1Scqwzesjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.76.0.tgz", + "integrity": "sha512-gN7yZ0eqflA5Fhf1wvHxGUltIV3FsvmB1zhNMDEK9vSHhc7E6qg9CuPeBgPZab66Tjzq6w6kHAtNEvnTHf4cyw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.76.0.tgz", + "integrity": "sha512-S/HqMbn22mQrjtErUxEoS/a55u8kIeXvreIxiJu5G7Le3UecEd6SQZxrDIpuhtgaFnsY/nVra3ytP+pRljDilA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.76.0.tgz", + "integrity": "sha512-ZIga3097VJZolGZk6SrIAUokIGfRkxRlhiHDUznZptGBfwrhD7pNfD1rzEzsCwvk/1DX0A1bLz+liuNh5QKIVQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.76.0.tgz", + "integrity": "sha512-ZGiiA7pFzMJSyMWYZTVlPgbTsx+Vl8ihLGMIujPwaslUF7kIPPWAbVmAlTc+9lWDV+DCiB8Ikixu+lSHeOIIWQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.76.0.tgz", + "integrity": "sha512-JLiy5WuvEBFTT6ErIFV35SLzi0R7Iri6MKU6dZbTxfIx8pndbbPs3Mj780nMipBFcPkti+okAPOJ9POKkHFEgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.76.0.tgz", + "integrity": "sha512-z7lgKQtbo/I1NIe8G5NHLesxJDv0tRSUWTpXKb9Pm3E9nKFKfO4IOSDtFroKgXtOYb0jQbcdH+0wzTyMXVes+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.76.0.tgz", + "integrity": "sha512-JOjKymIpb9QcYfEhZsN6h4V9Ivd474W38cNIBRv6bg2TbIvogbMTH0Mg6YWW9TiRDqfcX+/Hyfsbo5vcSE5guQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.76.0.tgz", + "integrity": "sha512-pqDWZiwcmByWUEm1NFUBNiT6aentCcaoMWJv0HbXEmuYermJ4sg8ppVrshubYP2MZ6SHccJJcpr6x469PuDFIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.76.0.tgz", + "integrity": "sha512-Ba0O659kgMv6pwO3z9PdO+K3aMxQRaw9HnG+e6AtOfgwcKFvYilciQYBoUBmxfQvOCKZe1SwjMkuB542NkuDMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.76.0.tgz", + "integrity": "sha512-5qcirPHO8nKfkoowEVWtpAoVTcYDy6g0UT0NGic450Qv8J2NrOqg4uQ8QppRP4MDTC7Xx47lbZnmadTH03CGGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "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 + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/novnc": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/@types/novnc/-/novnc-0.0.27.tgz", + "integrity": "sha512-s/VuD19lSAscYCBf6dJ6oftVk5bq/7AqyitEhfSFg1NJr3xSNZZ4l0nyr0eop2o2oJVRZTnvEWwdzBzDiEbiOw==", + "deprecated": "'@types/novnc' is now '@types/novnc-core'", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.4.tgz", + "integrity": "sha512-XcCQz0TBpBgljhj0gMuuDj49i6Ytqh5q1osT/Gp5uAVJUCTWxyskk/l1jwYYiu2xcNHHipdMz40EGfM1VdamVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.4.tgz", + "integrity": "sha512-GVoi+ICHocoOIU7qVVM48wOJziRsqrsyqlI0Ce0LdowRn6v3bcH2zUa9kp85ncx0nwIb9/HOCOLS3fdThDG/XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types" + ] + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/immer": { + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz", + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide-react": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.27.0.tgz", + "integrity": "sha512-rJicGl/3Fly/E0rOH1YmPZ6e49JCnKknh1ox1vpHnkfjujAkKA6sqUZvH3MTAaXXjgexyUwgNwTJzTtYuAFYJw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oxlint": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.76.0.tgz", + "integrity": "sha512-6QoFioEU4fNdiUx/2Eo6TRd6NG7H7njnRCz8rhB66cZmMHDTqcm1Rjvl8Wry+ZTQMBAmyb4Mlf62Mk5X+eHSOw==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.76.0", + "@oxlint/binding-android-arm64": "1.76.0", + "@oxlint/binding-darwin-arm64": "1.76.0", + "@oxlint/binding-darwin-x64": "1.76.0", + "@oxlint/binding-freebsd-x64": "1.76.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.76.0", + "@oxlint/binding-linux-arm-musleabihf": "1.76.0", + "@oxlint/binding-linux-arm64-gnu": "1.76.0", + "@oxlint/binding-linux-arm64-musl": "1.76.0", + "@oxlint/binding-linux-ppc64-gnu": "1.76.0", + "@oxlint/binding-linux-riscv64-gnu": "1.76.0", + "@oxlint/binding-linux-riscv64-musl": "1.76.0", + "@oxlint/binding-linux-s390x-gnu": "1.76.0", + "@oxlint/binding-linux-x64-gnu": "1.76.0", + "@oxlint/binding-linux-x64-musl": "1.76.0", + "@oxlint/binding-openharmony-arm64": "1.76.0", + "@oxlint/binding-win32-arm64-msvc": "1.76.0", + "@oxlint/binding-win32-ia32-msvc": "1.76.0", + "@oxlint/binding-win32-x64-msvc": "1.76.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.24.tgz", + "integrity": "sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "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 + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "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 + } + } + }, + "node_modules/recharts": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "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 + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "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 + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/xterm": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz", + "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT" + }, + "node_modules/xterm-addon-fit": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz", + "integrity": "sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==", + "deprecated": "This package is now deprecated. Move to @xterm/addon-fit instead.", + "license": "MIT", + "peerDependencies": { + "xterm": "^5.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..46347b0 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "proxmoxdesktop", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@novnc/novnc": "^1.7.0", + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-label": "^2.1.15", + "@radix-ui/react-scroll-area": "^1.2.18", + "@radix-ui/react-select": "^2.3.7", + "@radix-ui/react-separator": "^1.1.15", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-switch": "^1.3.7", + "@radix-ui/react-tabs": "^1.1.21", + "@radix-ui/react-tooltip": "^1.2.16", + "@tanstack/react-query": "^5.101.4", + "@tauri-apps/api": "^2.11.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.27.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "recharts": "^3.10.1", + "tailwind-merge": "^3.6.0", + "xterm": "^5.3.0", + "xterm-addon-fit": "^0.8.0", + "zustand": "^5.0.14" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@tauri-apps/cli": "^2.11.4", + "@types/node": "^24.13.2", + "@types/novnc": "^0.0.27", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "tailwindcss": "^4.3.3", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons.svg b/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..64bfd17 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,5298 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.4+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.9", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "linux-keyutils", + "log", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-keyutils" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" +dependencies = [ + "bitflags 2.13.1", + "libc", +] + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.19", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "open" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b3d059e795d52b8a72fef45658620edd4d9c359b338564aa14391ffa511ed5" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxmox-desktop" +version = "0.1.0" +dependencies = [ + "futures-util", + "hex", + "keyring", + "reqwest 0.12.28", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "tauri", + "tauri-build", + "tauri-plugin-shell", + "thiserror 2.0.19", + "tokio", + "tokio-tungstenite", + "url", + "uuid", + "x509-cert", +] + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.19", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.19", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.19", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.9", +] + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.1", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest 0.13.4", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.19", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.19", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.19", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.19", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.19", + "toml 1.1.4+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.4+spec-1.1.0", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.19", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.7", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.19", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.19", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", + "tls_codec", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..6b730fc --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "proxmox-desktop" +version = "0.1.0" +description = "A cross-platform desktop application for managing Proxmox VE servers" +authors = ["you"] +edition = "2021" + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = ["tray-icon"] } +tauri-plugin-shell = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } +tokio = { version = "1", features = ["full"] } +thiserror = "2" +keyring = { version = "3", features = ["apple-native", "windows-native", "linux-native"] } +uuid = { version = "1", features = ["v4"] } +rustls = "0.23" +rustls-pemfile = "2" +x509-cert = "0.2" +sha2 = "0.10" +hex = "0.4" +tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] } +futures-util = "0.3" +url = "2" + +[features] +default = ["custom-protocol"] +custom-protocol = ["tauri/custom-protocol"] diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/src/connection.rs b/src-tauri/src/connection.rs new file mode 100644 index 0000000..387bd27 --- /dev/null +++ b/src-tauri/src/connection.rs @@ -0,0 +1,430 @@ +use crate::error::Error; +use crate::proxmox::{ + AddDiskConfig, AddNICConfig, ApiResponse, Backup, BackupJob, BackupJobConfig, ClusterStatus, + CreateSnapshotConfig, Disk, EditNICConfig, NetworkInterface, Node, RestoreConfig, Snapshot, + Storage, StorageContent, StorageDetail, Task, VM, +}; +use crate::{CertificateInfo, ConnectionConfig, TermProxyResponse, VNCProxyResponse}; +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; +use reqwest::Client; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +struct Connection { + config: ConnectionConfig, + client: Client, + current_endpoint_index: usize, +} + +pub struct ConnectionManager { + connections: HashMap, +} + +impl ConnectionManager { + pub fn new() -> Self { + Self { + connections: HashMap::new(), + } + } + + pub async fn add_connection(&self, config: ConnectionConfig) -> crate::Result<()> { + // In a real implementation, we'd store this to disk + // For now, just validate the config + if config.primary.url.is_empty() { + return Err(Error::InvalidUrl("URL cannot be empty".to_string())); + } + Ok(()) + } + + pub async fn remove_connection(&self, id: &str) -> crate::Result<()> { + // Remove from storage + Ok(()) + } + + pub async fn connect(&self, id: &str) -> crate::Result<()> { + // Connect to the server + Ok(()) + } + + pub async fn disconnect(&self, id: &str) -> crate::Result<()> { + // Disconnect from the server + Ok(()) + } + + pub async fn get_certificate_info(&self, url: &str) -> crate::Result { + // Fetch certificate info from the server + Ok(CertificateInfo { + fingerprint: "AB:CD:EF:12:34:56:78:90".to_string(), + issuer: "Proxmox".to_string(), + subject: "pve".to_string(), + valid_from: "2024-01-01".to_string(), + valid_to: "2034-01-01".to_string(), + self_signed: true, + }) + } + + pub async fn trust_certificate(&self, id: &str, fingerprint: &str) -> crate::Result<()> { + // Store trusted certificate + Ok(()) + } + + pub async fn get_nodes(&self, connection_id: &str) -> crate::Result> { + // Fetch nodes from Proxmox API + Ok(vec![]) + } + + pub async fn get_vms(&self, connection_id: &str) -> crate::Result> { + // Fetch VMs from Proxmox API + Ok(vec![]) + } + + pub async fn get_storage(&self, connection_id: &str) -> crate::Result> { + // Fetch storage from Proxmox API + Ok(vec![]) + } + + pub async fn get_storage_content( + &self, + _connection_id: &str, + _storage: &str, + ) -> crate::Result> { + // Fetch content of a storage pool via Proxmox API + Ok(vec![]) + } + + pub async fn get_storage_detail( + &self, + _connection_id: &str, + _node: &str, + _storage: &str, + ) -> crate::Result { + // Fetch detailed info about a storage pool via Proxmox API + Ok(StorageDetail { + storage: String::new(), + r#type: String::new(), + content: String::new(), + active: 0, + enabled: 0, + shared: 0, + used: 0, + total: 0, + avail: 0, + node: String::new(), + }) + } + + pub async fn get_tasks(&self, connection_id: &str) -> crate::Result> { + // Fetch tasks from Proxmox API + Ok(vec![]) + } + + pub async fn get_cluster_status(&self, connection_id: &str) -> crate::Result { + // Fetch cluster status from Proxmox API + Ok(ClusterStatus { + r#type: "cluster".to_string(), + name: "default".to_string(), + id: "cluster/default".to_string(), + nodes: None, + }) + } + + pub async fn start_vm(&self, connection_id: &str, node: &str, vmid: u32) -> crate::Result<()> { + // Start VM via Proxmox API + Ok(()) + } + + pub async fn stop_vm(&self, connection_id: &str, node: &str, vmid: u32) -> crate::Result<()> { + // Stop VM via Proxmox API + Ok(()) + } + + pub async fn shutdown_vm(&self, connection_id: &str, node: &str, vmid: u32) -> crate::Result<()> { + // Shutdown VM via Proxmox API + Ok(()) + } + + pub async fn reboot_vm(&self, connection_id: &str, node: &str, vmid: u32) -> crate::Result<()> { + // Reboot VM via Proxmox API + Ok(()) + } + + pub async fn suspend_vm(&self, connection_id: &str, node: &str, vmid: u32) -> crate::Result<()> { + // Suspend (pause) VM via Proxmox API + Ok(()) + } + + pub async fn resume_vm(&self, connection_id: &str, node: &str, vmid: u32) -> crate::Result<()> { + // Resume suspended VM via Proxmox API + Ok(()) + } + + pub async fn get_disks( + &self, + connection_id: &str, + node: &str, + vmid: u32, + ) -> crate::Result> { + // Fetch disks for a VM via Proxmox API + Ok(vec![]) + } + + pub async fn add_disk( + &self, + connection_id: &str, + node: &str, + vmid: u32, + _config: AddDiskConfig, + ) -> crate::Result<()> { + // Add a disk to a VM via Proxmox API + Ok(()) + } + + pub async fn resize_disk( + &self, + connection_id: &str, + node: &str, + vmid: u32, + _disk: &str, + _size: u64, + ) -> crate::Result<()> { + // Resize a disk via Proxmox API + Ok(()) + } + + pub async fn remove_disk( + &self, + connection_id: &str, + node: &str, + vmid: u32, + _disk: &str, + ) -> crate::Result<()> { + // Remove a disk via Proxmox API + Ok(()) + } + + pub async fn move_disk( + &self, + connection_id: &str, + node: &str, + vmid: u32, + _disk: &str, + _storage: &str, + ) -> crate::Result<()> { + // Move a disk to different storage via Proxmox API + Ok(()) + } + + pub async fn get_network_interfaces( + &self, + connection_id: &str, + node: &str, + vmid: u32, + ) -> crate::Result> { + // Fetch network interfaces for a VM via Proxmox API + Ok(vec![]) + } + + pub async fn add_nic( + &self, + connection_id: &str, + node: &str, + vmid: u32, + _config: AddNICConfig, + ) -> crate::Result<()> { + // Add a network interface to a VM via Proxmox API + Ok(()) + } + + pub async fn edit_nic( + &self, + connection_id: &str, + node: &str, + vmid: u32, + _nic: &str, + _config: EditNICConfig, + ) -> crate::Result<()> { + // Edit a network interface on a VM via Proxmox API + Ok(()) + } + + pub async fn remove_nic( + &self, + connection_id: &str, + node: &str, + vmid: u32, + _nic: &str, + ) -> crate::Result<()> { + // Remove a network interface from a VM via Proxmox API + Ok(()) + } + + pub async fn get_snapshots( + &self, + _connection_id: &str, + _node: &str, + _vmid: u32, + ) -> crate::Result> { + // Fetch snapshots for a VM via Proxmox API + Ok(vec![]) + } + + pub async fn create_snapshot( + &self, + _connection_id: &str, + _node: &str, + _vmid: u32, + _config: CreateSnapshotConfig, + ) -> crate::Result<()> { + // Create a snapshot for a VM via Proxmox API + Ok(()) + } + + pub async fn delete_snapshot( + &self, + _connection_id: &str, + _node: &str, + _vmid: u32, + _name: &str, + ) -> crate::Result<()> { + // Delete a snapshot from a VM via Proxmox API + Ok(()) + } + + pub async fn rollback_snapshot( + &self, + _connection_id: &str, + _node: &str, + _vmid: u32, + _name: &str, + ) -> crate::Result<()> { + // Rollback a VM to a snapshot via Proxmox API + Ok(()) + } + + pub async fn migrate_vm( + &self, + _connection_id: &str, + _node: &str, + _vmid: u32, + _target_node: &str, + _online: bool, + ) -> crate::Result<()> { + // Migrate a VM to another node via Proxmox API + Ok(()) + } + + pub async fn create_vnc_proxy( + &self, + _connection_id: &str, + _node: &str, + _vmid: u32, + ) -> crate::Result { + // Create a VNC proxy via Proxmox API + // POST /nodes/{node}/qemu/{vmid}/vncproxy + // Returns ticket, port, and certificate + Ok(VNCProxyResponse { + ticket: String::new(), + port: 0, + cert: String::new(), + }) + } + + pub async fn create_term_proxy( + &self, + _connection_id: &str, + _node: &str, + _vmid: u32, + ) -> crate::Result { + // Create a terminal proxy via Proxmox API + // POST /nodes/{node}/lxc/{vmid}/termproxy + // Returns ticket and port + Ok(TermProxyResponse { + ticket: String::new(), + port: 0, + }) + } + + pub async fn get_websocket_url( + &self, + _connection_id: &str, + _node: &str, + ) -> crate::Result { + // Build the WebSocket base URL from the connection config + // Returns wss://{host}:{port} for the given connection + Ok(String::new()) + } + + pub async fn get_backup_jobs( + &self, + _connection_id: &str, + ) -> crate::Result> { + // Fetch backup jobs from Proxmox API + Ok(vec![]) + } + + pub async fn get_backups( + &self, + _connection_id: &str, + _storage: Option<&str>, + ) -> crate::Result> { + // Fetch existing backups from Proxmox API + Ok(vec![]) + } + + pub async fn create_backup_job( + &self, + _connection_id: &str, + _config: BackupJobConfig, + ) -> crate::Result<()> { + // Create a backup job via Proxmox API + Ok(()) + } + + pub async fn update_backup_job( + &self, + _connection_id: &str, + _id: &str, + _config: BackupJobConfig, + ) -> crate::Result<()> { + // Update a backup job via Proxmox API + Ok(()) + } + + pub async fn delete_backup_job( + &self, + _connection_id: &str, + _id: &str, + ) -> crate::Result<()> { + // Delete a backup job via Proxmox API + Ok(()) + } + + pub async fn run_backup( + &self, + _connection_id: &str, + _config: BackupJobConfig, + ) -> crate::Result<()> { + // Trigger an immediate backup run via Proxmox API + Ok(()) + } + + pub async fn restore_backup( + &self, + _connection_id: &str, + _volid: &str, + _config: RestoreConfig, + ) -> crate::Result<()> { + // Restore a backup via Proxmox API + Ok(()) + } + + pub async fn delete_backup( + &self, + _connection_id: &str, + _volid: &str, + ) -> crate::Result<()> { + // Delete a backup file via Proxmox API + Ok(()) + } +} diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs new file mode 100644 index 0000000..675412f --- /dev/null +++ b/src-tauri/src/error.rs @@ -0,0 +1,44 @@ +use serde::{Serialize, Serializer}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Connection not found: {0}")] + ConnectionNotFound(String), + + #[error("Not connected to server")] + NotConnected, + + #[error("HTTP request failed: {0}")] + HttpError(#[from] reqwest::Error), + + #[error("Certificate error: {0}")] + CertificateError(String), + + #[error("Authentication failed: {0}")] + AuthError(String), + + #[error("Keyring error: {0}")] + KeyringError(String), + + #[error("Serialization error: {0}")] + SerializationError(String), + + #[error("Invalid URL: {0}")] + InvalidUrl(String), + + #[error("API error: {0}")] + ApiError(String), + + #[error("WebSocket error: {0}")] + WebSocketError(String), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..b7b3116 --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,727 @@ +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tauri::menu::{MenuBuilder, MenuItemBuilder}; +use tauri::tray::TrayIconBuilder; +use tauri::Manager; +use tokio::sync::RwLock; + +mod connection; +mod proxmox; +mod error; +mod websocket; + +use connection::ConnectionManager; +use error::Error; +use websocket::WebSocketManager; + +pub type Result = std::result::Result; + +#[derive(Clone, Serialize, Deserialize)] +pub struct ConnectionConfig { + pub id: String, + pub name: String, + pub primary: EndpointConfig, + pub fallbacks: Vec, + pub cert_fingerprint: Option, + pub trusted: bool, + pub status: String, + pub cluster_name: Option, + pub is_cluster: bool, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct EndpointConfig { + pub url: String, + pub node: Option, + pub token: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct CertificateInfo { + pub fingerprint: String, + pub issuer: String, + pub subject: String, + pub valid_from: String, + pub valid_to: String, + pub self_signed: bool, +} + +struct AppState { + connection_manager: Arc>, + ws_manager: Arc>, +} + +#[tauri::command] +async fn add_connection( + state: tauri::State<'_, AppState>, + config: ConnectionConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.add_connection(config).await +} + +#[tauri::command] +async fn remove_connection( + state: tauri::State<'_, AppState>, + id: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.remove_connection(&id).await +} + +#[tauri::command] +async fn connect_to_server( + state: tauri::State<'_, AppState>, + id: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.connect(&id).await +} + +#[tauri::command] +async fn disconnect_from_server( + state: tauri::State<'_, AppState>, + id: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.disconnect(&id).await +} + +#[tauri::command] +async fn get_certificate_info( + state: tauri::State<'_, AppState>, + url: String, +) -> Result { + let manager = state.connection_manager.read().await; + manager.get_certificate_info(&url).await +} + +#[tauri::command] +async fn trust_certificate( + state: tauri::State<'_, AppState>, + id: String, + fingerprint: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.trust_certificate(&id, &fingerprint).await +} + +#[tauri::command] +async fn get_nodes( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_nodes(&connection_id).await +} + +#[tauri::command] +async fn get_vms( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_vms(&connection_id).await +} + +#[tauri::command] +async fn get_storage( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_storage(&connection_id).await +} + +#[tauri::command] +async fn get_storage_content( + state: tauri::State<'_, AppState>, + connection_id: String, + storage: String, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_storage_content(&connection_id, &storage).await +} + +#[tauri::command] +async fn get_storage_detail( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + storage: String, +) -> Result { + let manager = state.connection_manager.read().await; + manager.get_storage_detail(&connection_id, &node, &storage).await +} + +#[tauri::command] +async fn get_tasks( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_tasks(&connection_id).await +} + +#[tauri::command] +async fn get_cluster_status( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result { + let manager = state.connection_manager.read().await; + manager.get_cluster_status(&connection_id).await +} + +#[tauri::command] +async fn start_vm( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.start_vm(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn stop_vm( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.stop_vm(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn shutdown_vm( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.shutdown_vm(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn reboot_vm( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.reboot_vm(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn suspend_vm( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.suspend_vm(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn resume_vm( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.resume_vm(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn get_disks( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_disks(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn add_disk( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + config: proxmox::AddDiskConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.add_disk(&connection_id, &node, vmid, config).await +} + +#[tauri::command] +async fn resize_disk( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + disk: String, + size: u64, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.resize_disk(&connection_id, &node, vmid, &disk, size).await +} + +#[tauri::command] +async fn remove_disk( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + disk: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.remove_disk(&connection_id, &node, vmid, &disk).await +} + +#[tauri::command] +async fn move_disk( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + disk: String, + storage: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.move_disk(&connection_id, &node, vmid, &disk, &storage).await +} + +#[tauri::command] +async fn get_network_interfaces( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_network_interfaces(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn add_nic( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + config: proxmox::AddNICConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.add_nic(&connection_id, &node, vmid, config).await +} + +#[tauri::command] +async fn edit_nic( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + nic: String, + config: proxmox::EditNICConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.edit_nic(&connection_id, &node, vmid, &nic, config).await +} + +#[tauri::command] +async fn remove_nic( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + nic: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.remove_nic(&connection_id, &node, vmid, &nic).await +} + +#[tauri::command] +async fn get_snapshots( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_snapshots(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn create_snapshot( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + config: proxmox::CreateSnapshotConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.create_snapshot(&connection_id, &node, vmid, config).await +} + +#[tauri::command] +async fn delete_snapshot( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + name: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.delete_snapshot(&connection_id, &node, vmid, &name).await +} + +#[tauri::command] +async fn rollback_snapshot( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + name: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.rollback_snapshot(&connection_id, &node, vmid, &name).await +} + +#[tauri::command] +async fn migrate_vm( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, + target_node: String, + online: bool, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.migrate_vm(&connection_id, &node, vmid, &target_node, online).await +} + +// Console proxy types +#[derive(Clone, Serialize, Deserialize)] +pub struct VNCProxyResponse { + pub ticket: String, + pub port: u32, + pub cert: String, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct TermProxyResponse { + pub ticket: String, + pub port: u32, +} + +#[tauri::command] +async fn create_vnc_proxy( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result { + let manager = state.connection_manager.read().await; + manager.create_vnc_proxy(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn create_term_proxy( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, + vmid: u32, +) -> Result { + let manager = state.connection_manager.read().await; + manager.create_term_proxy(&connection_id, &node, vmid).await +} + +#[tauri::command] +async fn get_websocket_url( + state: tauri::State<'_, AppState>, + connection_id: String, + node: String, +) -> Result { + let manager = state.connection_manager.read().await; + manager.get_websocket_url(&connection_id, &node).await +} + +#[tauri::command] +async fn connect_websocket( + state: tauri::State<'_, AppState>, + connection_id: String, + url: String, + app_handle: tauri::AppHandle, +) -> Result<()> { + let mut ws_manager = state.ws_manager.write().await; + ws_manager.connect(connection_id, url, app_handle).await +} + +#[tauri::command] +async fn disconnect_websocket( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result<()> { + let mut ws_manager = state.ws_manager.write().await; + ws_manager.disconnect(&connection_id).await +} + +#[tauri::command] +async fn is_websocket_connected( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result { + let ws_manager = state.ws_manager.read().await; + Ok(ws_manager.is_connected(&connection_id)) +} + +// Backup management commands +#[tauri::command] +async fn get_backup_jobs( + state: tauri::State<'_, AppState>, + connection_id: String, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_backup_jobs(&connection_id).await +} + +#[tauri::command] +async fn get_backups( + state: tauri::State<'_, AppState>, + connection_id: String, + storage: Option, +) -> Result> { + let manager = state.connection_manager.read().await; + manager.get_backups(&connection_id, storage.as_deref()).await +} + +#[tauri::command] +async fn create_backup_job( + state: tauri::State<'_, AppState>, + connection_id: String, + config: proxmox::BackupJobConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.create_backup_job(&connection_id, config).await +} + +#[tauri::command] +async fn update_backup_job( + state: tauri::State<'_, AppState>, + connection_id: String, + id: String, + config: proxmox::BackupJobConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.update_backup_job(&connection_id, &id, config).await +} + +#[tauri::command] +async fn delete_backup_job( + state: tauri::State<'_, AppState>, + connection_id: String, + id: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.delete_backup_job(&connection_id, &id).await +} + +#[tauri::command] +async fn run_backup( + state: tauri::State<'_, AppState>, + connection_id: String, + config: proxmox::BackupJobConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.run_backup(&connection_id, config).await +} + +#[tauri::command] +async fn restore_backup( + state: tauri::State<'_, AppState>, + connection_id: String, + volid: String, + config: proxmox::RestoreConfig, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.restore_backup(&connection_id, &volid, config).await +} + +#[tauri::command] +async fn delete_backup( + state: tauri::State<'_, AppState>, + connection_id: String, + volid: String, +) -> Result<()> { + let manager = state.connection_manager.read().await; + manager.delete_backup(&connection_id, &volid).await +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct TrayConnectionInfo { + pub id: String, + pub name: String, + pub status: String, +} + +#[tauri::command] +async fn update_tray_menu( + app: tauri::AppHandle, + connections: Vec, +) -> Result<()> { + let mut menu_builder = MenuBuilder::new(&app); + + // Show/Hide window item + let show_hide = MenuItemBuilder::new("Show / Hide") + .id("show_hide") + .build(&app)?; + menu_builder = menu_builder.item(&show_hide); + menu_builder = menu_builder.separator(); + + // Connection items with status + for conn in &connections { + let status_icon = match conn.status.as_str() { + "connected" => "🟢", + "connecting" | "failover" => "🟡", + "failed" => "🔴", + _ => "⚪", + }; + let label = format!("{} {}", status_icon, conn.name); + let item = MenuItemBuilder::new(&label) + .id(format!("connection_{}", conn.id)) + .build(&app)?; + menu_builder = menu_builder.item(&item); + } + + if connections.is_empty() { + let no_conn = MenuItemBuilder::new("No connections") + .id("no_connections") + .disabled(true) + .build(&app)?; + menu_builder = menu_builder.item(&no_conn); + } + + menu_builder = menu_builder.separator(); + + // Quit item + let quit = MenuItemBuilder::new("Quit").id("quit").build(&app)?; + menu_builder = menu_builder.item(&quit); + + let menu = menu_builder.build()?; + + // Update the tray menu + if let Some(tray) = app.tray_by_id("main-tray") { + tray.set_menu(Some(menu))?; + } + + Ok(()) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .manage(AppState { + connection_manager: Arc::new(RwLock::new(ConnectionManager::new())), + ws_manager: Arc::new(RwLock::new(WebSocketManager::new())), + }) + .invoke_handler(tauri::generate_handler![ + add_connection, + remove_connection, + connect_to_server, + disconnect_from_server, + get_certificate_info, + trust_certificate, + get_nodes, + get_vms, + get_storage, + get_storage_content, + get_storage_detail, + get_tasks, + get_cluster_status, + start_vm, + stop_vm, + shutdown_vm, + reboot_vm, + suspend_vm, + resume_vm, + get_disks, + add_disk, + resize_disk, + remove_disk, + move_disk, + get_network_interfaces, + add_nic, + edit_nic, + remove_nic, + get_snapshots, + create_snapshot, + delete_snapshot, + rollback_snapshot, + migrate_vm, + create_vnc_proxy, + create_term_proxy, + get_websocket_url, + connect_websocket, + disconnect_websocket, + is_websocket_connected, + get_backup_jobs, + get_backups, + create_backup_job, + update_backup_job, + delete_backup_job, + run_backup, + restore_backup, + delete_backup, + update_tray_menu, + ]) + .setup(|app| { + // Build the system tray menu + let show_hide = MenuItemBuilder::new("Show / Hide") + .id("show_hide") + .build(app)?; + let quit = MenuItemBuilder::new("Quit").id("quit").build(app)?; + + let menu = MenuBuilder::new(app) + .item(&show_hide) + .separator() + .item(&quit) + .build()?; + + let _tray = TrayIconBuilder::new() + .id("main-tray") + .tooltip("ProxmoxDesktop") + .icon(app.default_window_icon().cloned().expect("no default icon")) + .menu(&menu) + .on_menu_event(move |app, event| { + match event.id.as_ref() { + "show_hide" => { + if let Some(window) = app.get_webview_window("main") { + if window.is_visible().unwrap_or(false) { + let _ = window.hide(); + } else { + let _ = window.show(); + let _ = window.set_focus(); + } + } + } + "quit" => { + app.exit(0); + } + _ => {} + } + }) + .build(app)?; + + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..7db13bd --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + proxmox_desktop::run() +} diff --git a/src-tauri/src/proxmox.rs b/src-tauri/src/proxmox.rs new file mode 100644 index 0000000..f47cde6 --- /dev/null +++ b/src-tauri/src/proxmox.rs @@ -0,0 +1,226 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Disk { + pub device: String, + pub size: u64, + pub storage: String, + pub format: String, + pub usage: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AddDiskConfig { + pub storage: String, + pub size: u64, + pub bus_type: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Node { + pub node: String, + pub status: String, + pub cpu: f64, + pub maxcpu: u32, + pub mem: u64, + pub maxmem: u64, + pub disk: u64, + pub maxdisk: u64, + pub uptime: u64, + pub level: String, + pub id: String, + pub r#type: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VM { + pub vmid: u32, + pub name: Option, + pub status: String, + pub r#type: String, + pub node: String, + pub cpu: f64, + pub cpus: u32, + pub mem: u64, + pub maxmem: u64, + pub disk: u64, + pub maxdisk: u64, + pub uptime: u64, + pub netin: u64, + pub netout: u64, + pub diskread: u64, + pub diskwrite: u64, + pub pid: Option, + pub template: Option, + pub lock: Option, + pub tags: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Storage { + pub storage: String, + pub r#type: String, + pub content: String, + pub active: u32, + pub enabled: u32, + pub shared: u32, + pub used: u64, + pub total: u64, + pub avail: u64, + pub node: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Task { + pub upid: String, + pub node: String, + pub pid: u32, + pub pstart: u64, + pub starttime: u64, + pub endtime: Option, + pub r#type: String, + pub id: String, + pub user: String, + pub status: Option, + pub exitstatus: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClusterStatus { + pub r#type: String, + pub name: String, + pub id: String, + pub nodes: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClusterNode { + pub name: String, + pub nodeid: u32, + pub online: u32, + pub local: Option, + pub ip: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Snapshot { + pub name: String, + pub description: String, + pub snaptime: u64, + pub vmstate: u32, + pub parent: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CreateSnapshotConfig { + pub name: String, + pub description: Option, + pub vmstate: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ApiResponse { + pub data: T, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetworkInterface { + pub name: String, + pub model: String, + pub macaddr: String, + pub bridge: Option, + pub tag: Option, + pub firewall: Option, + pub link_down: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AddNICConfig { + pub bridge: String, + pub model: String, + pub macaddr: Option, + pub tag: Option, + pub firewall: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EditNICConfig { + pub bridge: Option, + pub model: Option, + pub tag: Option, + pub firewall: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Backup { + pub volid: String, + pub backupid: String, + #[serde(rename = "backup-type")] + pub backup_type: String, + #[serde(rename = "backup-id")] + pub backup_id: String, + #[serde(rename = "backup-time")] + pub backup_time: u64, + pub storage: String, + pub size: u64, + pub ctime: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BackupJob { + pub id: String, + pub store: String, + pub schedule: String, + pub all: u32, + pub enabled: u32, + pub node: Option, + pub vmid: Option, + pub compress: Option, + pub mode: Option, + pub quiet: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BackupJobConfig { + pub id: Option, + pub storage: String, + pub schedule: String, + pub mode: String, + pub compression: String, + pub all: bool, + pub vmid: Option, + pub enabled: bool, + pub node: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RestoreConfig { + pub volid: String, + pub node: String, + pub storage: String, + pub vmid: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StorageContent { + pub content: String, + pub ctime: u64, + pub format: Option, + pub size: Option, + pub subtype: Option, + pub volid: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StorageDetail { + pub storage: String, + pub r#type: String, + pub content: String, + pub active: u32, + pub enabled: u32, + pub shared: u32, + pub used: u64, + pub total: u64, + pub avail: u64, + pub node: String, +} diff --git a/src-tauri/src/websocket.rs b/src-tauri/src/websocket.rs new file mode 100644 index 0000000..3817643 --- /dev/null +++ b/src-tauri/src/websocket.rs @@ -0,0 +1,253 @@ +use futures_util::{SinkExt, StreamExt}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tokio::sync::mpsc; +use tokio::time::{sleep, Duration}; +use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::connect_async; + +use crate::error::Error; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TaskUpdate { + pub connection_id: String, + pub upid: String, + pub node: String, + pub task_type: String, + pub status: Option, + pub exitstatus: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NodeStatusChange { + pub connection_id: String, + pub node: String, + pub status: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VMStatusChange { + pub connection_id: String, + pub node: String, + pub vmid: u32, + pub status: String, +} + +/// Manages WebSocket connections per connection ID. +/// +/// Each connection ID maps to a background task that reads messages from +/// the Proxmox WebSocket and re-emits them as Tauri events. +pub struct WebSocketManager { + connections: HashMap>, +} + +impl WebSocketManager { + pub fn new() -> Self { + Self { + connections: HashMap::new(), + } + } + + /// Connect to a Proxmox WebSocket URL for the given connection ID. + /// + /// Messages are forwarded as Tauri events via `app_handle`. If a connection + /// already exists for this ID, it is disconnected first. + pub async fn connect( + &mut self, + connection_id: String, + url: String, + app_handle: tauri::AppHandle, + ) -> crate::Result<()> { + // Disconnect any existing connection for this ID + self.disconnect(&connection_id).await; + + let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1); + + let cid = connection_id.clone(); + let ws_url = url.clone(); + + tokio::spawn(async move { + let mut reconnect_delay = Duration::from_secs(1); + const MAX_DELAY: Duration = Duration::from_secs(30); + + loop { + tokio::select! { + _ = shutdown_rx.recv() => { + break; + } + result = connect_and_run(&cid, &ws_url, &app_handle) => { + match result { + Ok(()) => { + // Normal close or stream ended – attempt reconnect + reconnect_delay = Duration::from_secs(1); + } + Err(e) => { + eprintln!("[ws] connection error for {}: {e}", cid); + } + } + } + } + + // Reconnect back-off + tokio::select! { + _ = shutdown_rx.recv() => { + break; + } + _ = sleep(reconnect_delay) => {} + } + reconnect_delay = (reconnect_delay * 2).min(MAX_DELAY); + } + }); + + self.connections.insert(connection_id, shutdown_tx); + Ok(()) + } + + /// Disconnect the WebSocket for the given connection ID. + pub async fn disconnect(&mut self, connection_id: &str) -> crate::Result<()> { + if let Some(tx) = self.connections.remove(connection_id) { + let _ = tx.send(()).await; + } + Ok(()) + } + + /// Check whether a WebSocket connection is active. + pub fn is_connected(&self, connection_id: &str) -> bool { + self.connections.contains_key(connection_id) + } +} + +/// Connect to the Proxmox WebSocket and relay messages as Tauri events. +async fn connect_and_run( + connection_id: &str, + url: &str, + app_handle: &tauri::AppHandle, +) -> crate::Result<()> { + let (ws_stream, _) = connect_async(url) + .await + .map_err(|e| Error::WebSocketError(e.to_string()))?; + + let cid = connection_id.to_string(); + let (mut write, mut read) = ws_stream.split(); + + while let Some(msg_result) = read.next().await { + match msg_result { + Ok(Message::Text(text)) => { + handle_ws_message(&cid, &text, app_handle); + } + Ok(Message::Close(_)) => { + break; + } + Ok(_) => {} + Err(e) => { + eprintln!("[ws] read error: {e}"); + break; + } + } + } + + // Attempt clean close + let _ = write.close().await; + Ok(()) +} + +/// Parse a Proxmox WebSocket message and emit the appropriate Tauri event. +/// +/// Proxmox sends JSON messages in the format: +/// ```json +/// { "type": "task", "data": { ... } } +/// ``` +/// or various status update formats. We try to detect known types and emit +/// events, falling back to a generic broadcast. +fn handle_ws_message(connection_id: &str, text: &str, app_handle: &tauri::AppHandle) { + let Ok(value) = serde_json::from_str::(text) else { + return; + }; + + // Try to detect task-related messages + if let Some(msg_type) = value.get("type").and_then(|v| v.as_str()) { + match msg_type { + "task" => { + if let Some(data) = value.get("data") { + let update = TaskUpdate { + connection_id: connection_id.to_string(), + upid: data + .get("upid") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + node: data + .get("node") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + task_type: data + .get("type") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + status: data + .get("status") + .and_then(|v| v.as_str()) + .map(String::from), + exitstatus: data + .get("exitstatus") + .and_then(|v| v.as_str()) + .map(String::from), + }; + let _ = app_handle.emit("task-update", update); + } + } + "node" => { + if let Some(data) = value.get("data") { + let change = NodeStatusChange { + connection_id: connection_id.to_string(), + node: data + .get("node") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + status: data + .get("status") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + }; + let _ = app_handle.emit("node-status-change", change); + } + } + "vm" => { + if let Some(data) = value.get("data") { + let change = VMStatusChange { + connection_id: connection_id.to_string(), + node: data + .get("node") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + vmid: data + .get("vmid") + .and_then(|v| v.as_u64()) + .unwrap_or(0) as u32, + status: data + .get("status") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + }; + let _ = app_handle.emit("vm-status-change", change); + } + } + _ => { + // Unknown message type – emit generic event with raw data + let _ = app_handle.emit( + "ws-raw", + serde_json::json!({ + "connection_id": connection_id, + "data": value, + }), + ); + } + } + } +} diff --git a/src-tauri/target/.rustc_info.json b/src-tauri/target/.rustc_info.json new file mode 100644 index 0000000..d63ba35 --- /dev/null +++ b/src-tauri/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":9200552825474144316,"outputs":{"7752308220390268658":{"success":true,"status":"","code":0,"stdout":"rustc 1.97.1 (8bab26f4f 2026-07-14)\nbinary: rustc\ncommit-hash: 8bab26f4f68e0e26f0bb7960be334d5b520ea452\ncommit-date: 2026-07-14\nhost: x86_64-unknown-linux-gnu\nrelease: 1.97.1\nLLVM version: 22.1.6\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_primitive_alignment=\"16\"\ntarget_has_atomic_primitive_alignment=\"32\"\ntarget_has_atomic_primitive_alignment=\"64\"\ntarget_has_atomic_primitive_alignment=\"8\"\ntarget_has_atomic_primitive_alignment=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/src-tauri/target/CACHEDIR.TAG b/src-tauri/target/CACHEDIR.TAG new file mode 100644 index 0000000..20d7c31 --- /dev/null +++ b/src-tauri/target/CACHEDIR.TAG @@ -0,0 +1,3 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by cargo. +# For information about cache directory tags see https://bford.info/cachedir/ diff --git a/src-tauri/target/debug/.cargo-build-lock b/src-tauri/target/debug/.cargo-build-lock new file mode 100644 index 0000000..e69de29 diff --git a/src-tauri/target/debug/.cargo-lock b/src-tauri/target/debug/.cargo-lock new file mode 100644 index 0000000..e69de29 diff --git a/src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/invoked.timestamp b/src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/output-build-script-build-script-build b/src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/output-build-script-build-script-build new file mode 100644 index 0000000..3eae485 --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/libc-19124a20af635abc/output-build-script-build-script-build @@ -0,0 +1,2 @@ +{"$message_type":"diagnostic","message":"linker `cc` not found","code":null,"level":"error","spans":[],"children":[{"message":"No such file or directory (os error 2)","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: linker `cc` not found\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: No such file or directory (os error 2)\n\n"} +{"$message_type":"diagnostic","message":"aborting due to 1 previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: aborting due to 1 previous error\u001b[0m\n\n"} diff --git a/src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/invoked.timestamp b/src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/output-build-script-build-script-build b/src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/output-build-script-build-script-build new file mode 100644 index 0000000..3eae485 --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/proc-macro2-28cd7e73fe64eada/output-build-script-build-script-build @@ -0,0 +1,2 @@ +{"$message_type":"diagnostic","message":"linker `cc` not found","code":null,"level":"error","spans":[],"children":[{"message":"No such file or directory (os error 2)","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: linker `cc` not found\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: No such file or directory (os error 2)\n\n"} +{"$message_type":"diagnostic","message":"aborting due to 1 previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: aborting due to 1 previous error\u001b[0m\n\n"} diff --git a/src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/invoked.timestamp b/src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/output-build-script-build-script-build b/src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/output-build-script-build-script-build new file mode 100644 index 0000000..3eae485 --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/quote-6dff9724e4e81362/output-build-script-build-script-build @@ -0,0 +1,2 @@ +{"$message_type":"diagnostic","message":"linker `cc` not found","code":null,"level":"error","spans":[],"children":[{"message":"No such file or directory (os error 2)","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: linker `cc` not found\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: No such file or directory (os error 2)\n\n"} +{"$message_type":"diagnostic","message":"aborting due to 1 previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: aborting due to 1 previous error\u001b[0m\n\n"} diff --git a/src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/invoked.timestamp b/src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/output-build-script-build-script-build b/src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/output-build-script-build-script-build new file mode 100644 index 0000000..3eae485 --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/serde_core-e613c711ddfe8493/output-build-script-build-script-build @@ -0,0 +1,2 @@ +{"$message_type":"diagnostic","message":"linker `cc` not found","code":null,"level":"error","spans":[],"children":[{"message":"No such file or directory (os error 2)","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: linker `cc` not found\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: No such file or directory (os error 2)\n\n"} +{"$message_type":"diagnostic","message":"aborting due to 1 previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m: aborting due to 1 previous error\u001b[0m\n\n"} diff --git a/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/dep-lib-unicode_ident b/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/dep-lib-unicode_ident new file mode 100644 index 0000000000000000000000000000000000000000..ec3cb8bfd28046e47ad4528d5b57859bc05b62f2 GIT binary patch literal 14 QcmZQ%U|{&q2qeG&016uc0ssI2 literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/invoked.timestamp b/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident b/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident new file mode 100644 index 0000000..5c7ec14 --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident @@ -0,0 +1 @@ +4609aa34aaf3f167 \ No newline at end of file diff --git a/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident.json b/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident.json new file mode 100644 index 0000000..718e584 --- /dev/null +++ b/src-tauri/target/debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/lib-unicode_ident.json @@ -0,0 +1 @@ +{"rustc":12019306335353385202,"features":"[]","declared_features":"[]","target":14045917370260632744,"profile":2225463790103693989,"path":11797909581934051219,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/unicode-ident-8443eb632a3fbe4c/dep-lib-unicode_ident","checksum":false}}],"rustflags":[],"config":9396254390672932401,"compile_kind":0} \ No newline at end of file diff --git a/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.3ufr5450lp20sxhbw3b5lnbvs.rcgu.o b/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.3ufr5450lp20sxhbw3b5lnbvs.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..ba2de300ea59dd3907f9098e7e478dba42fd9bda GIT binary patch literal 2560 zcmbVN&x_Mg5PsRN?y8H_#e=XOauO99(qG%H2Q8?pq7+>f_8`QjO=&T0N|JVKFIz!G zJo%T%Uc`$B(To3u-o(@5LGU1~GwGY=HT%+T9SCpc%Qx?vndBwAH}7pOC<-G}*i{xr zgfjN&Sm>7mx5Ul>^JfSL-n{V_=5{^5s`f0`YdUQ;trZJeTD`boR?N(LJy%yVdS*SP z7gFimB~Uoa^_0ODQ~F6|ZDncsEfkyt{5-!$r6Q(Fup1I=JA$E(%`w?L_^t7RT8r%0 z!59#n1WO1}=q4Cd8e-@r*wQ4xqXat}5$Xu|aqb6;=39NAX;r7yvf4h&cI$3Fm)Gr1M)&#+ zb0=%&?Y7zTnAUcDOS@Hmobvoi^%>J#%dTiv!>GHJmc>KdOf$C1z3X0aXY+yEs}8ok zyGFqJzbnJ^Ncsg@%(*9!Ee3T3$vap%>j&7ncu zBWh%*kJ(+M#c?ss^IgAU!uq@wtg}Xz&68By;Uo7N&6d&hjBXnqylGoTFC)G{|L1>$ zgCoArw99J_7Q{mUH|d-a4`aM0-erhOaLG(OBY6KMBu~p1&kpkOJ1g2QB;%eX5d?pa z*L{xTn1?OlHv^4uHAesbh&FL{5sK=s#pr*P=wmNY{f~gj`hN;I+N}OR07usUcZoj! zRigT3z-0Ozz|m&u-vN$H|Bpl;zy4AE{h0X=fs2N&=_$(^b literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.build_script_build.a30a70663bab765a-cgu.0.rcgu.o b/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.build_script_build.a30a70663bab765a-cgu.0.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..a91ef612b527ed36a4f3847389cb0229465c2549 GIT binary patch literal 188848 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 literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.build_script_build.a30a70663bab765a-cgu.1.rcgu.o b/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.build_script_build.a30a70663bab765a-cgu.1.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..2c0bc9d4574c47fa5b5c1d8aeb8b78b91b09cbac GIT binary patch literal 135328 zcmeEv3w&Hvo&TMgmWEQAPz!{Y3@|`IXftV&rsb8sV1TrQwm?B;I!Pu?N-`5>CVjA7 zEu=_8KygLH#Ya_Ce68$?h=^<}tX5W8eC?v^u0>gC9R6;E@Lpo% z#rni(N313>x;$2w82w#htiYQ;CB`UNSc7D|jfS^tE3$w}P&;=RZvMt`3eeKj$v zX!+uI7RFYRBJrI~v5m|iCGOl1izY@l#Of6va;_(qP`Aa?5@V0WHiJ$-9o;tiOk(s7 zg^X^B-Gv9me@nc)FqWX?h2s)q3uB#$QIx~niUepjk|tbK1@$HFEX1Okv%}Z=)N8dQ zUMGn`K1PXAKX7_TF-hEcBv=_i@Ip;u>~8&FtOHbMJ(UP;Pu%<@YYsi^*%La-X?L9V z$=5oUUG6v^4AowXCn`~&ralW<2>*f1V90+wJ?<|(G_X4{Iw7XZ7@H8g)-;SQ|K=AT zKz4L{V)O^5UC~l#O4SaKBnClZxJGe}LWlQ*8d~6mZ8KyTFNd^f(+0AoX*BX-Lt^AR zb=33Fqm94~DVTqo>c&6);8BP*Jeu&HE<~H({Nw`wl%TL5<`YK1DKTY98&H}S5<*c` zFdM#z9ISIwYztq3l@I=%f+qLB1`Er zl{b2dS`ixfFlzPGSE;9!)Kg07DO#x~V`1c`6YQyN^D~R@Eki#2G@IC0;&Enci8fG1P{BJ$6)L6uB#*$xi6$ zN_e17++T_gov1IqQyYLH=IAqaC2+Wz+PZl)wPz_W&;h3d(%J^x)gILje?*mPuTwmU z1;L@VIfYY;XS;^mhE`}V{z^Z5S&f9lZ!Mm|wrbPO#8%U0{F^;Tlb>ow^Nj9hACR*enN4gHRsqk*lPjK};U=^xzoh)}l%7+Z zp<}IO>MQ&W`YU}g3{iP3ut!&frGxYAkT!hWMvIhrc4&A_akVS6?oN=|jK~d?zQNxi z9z*u%;28hO+4R%ti90W#B#E(!yzF_V=+qve>&cGBcXY?ZsDd%&@aT;#&ig~hlU60j zzs}F!K-CSp?#GiypQ(q6Av*00kwm5q^8DFeb*RY>syeTV+oz#h@b zo(bpEfiM%fHs)3`hu#iXMR+u!GAofmohQT+BfFio)9LuzYgigGiD($%>9kU8BQQ1Y zQMPvSO3-Z1)OEol?Sj_P!l93;$4)-iIptfZz^_8cu^MR4u`5vcq{P@YXh?=;5D#** zjlgP>P>b<-c39WV3AG?nR=N=ui=qR^IuLiTX;mD%oFQ3Bsm0COX+qv9Z=KxiG@Ri$ zOK@dEJjM8(Ln>lAhLB@RAUuraVkNxNZ99cWwfGNm2o9k1+OLfsPi4*7o@;j>^|52L z)gHwO094$mS{Co2L6mTu9UzAwbwg~nQpET-D1}lhD%eKWDDLi9R7rJ5tU+Bj#Tt1X zGgq)cNt+arSD2~~IQ7}-1ch0_UOL1#H?klEt2=Pzpfyx`)~47q1n+z-_K3PZr0%s` zTZoK37HdPn3NgLz4uybEJ04&L1gW8V_F%B7vBaFk+^uf?d*eBYu{&aQs!p1W64vt` zZ6}mI{+?I&?4f$1rY|txfPk2-rn+a8$Pg>GOG#{|Q@hoTS8I;7EJIZk#GB3Yt-Rv? zE_1)#+}~^N6;nLlV(t}R+&^IM6<^#x$oo4VQhdFrdVKL7rqHo#KNa5BlY_$xY6QD0cp_P0)&$&_8KY0Yk)25^AZS zh=$^+tZt1$iz(M$*<@%Twp8)osVbNZMgb!VyJ92#zETzEhmDVJ#~Yfr%a7{gOp(&K z8hk89-ii`e>hY+zPCU<SxCtu*iW;)KY`U{*JPE}&M|LFF|R9(%| z>*>^@e3-biMP0lS>-VT>VDV%YuMK4UL!`9cUEAt&oaedULOLiJ#KrH`9#L-o zDwaOB{^vr3k?1X20HsYO{|a_o>jB|@DQ{!B{pyttOu|V3F7P(^=XM868^@l%F?Ut*I6Yy} z{fAW?w(XcEs(ko%RBU1Kfs%qIqYS(h#XB67 zR#=;bSVQp}i9o?Og{UrjP%HX>a4tfWc5O82GYB z6E{JxlXMeLN7FEXa*AK~Z0oSi+crK2v-z%S*R30-mA7m0Tx!xQZZFn5JfnE&?H#d6 zG=*(D4%_$~Eatmfd4wpx6=D1<$~s`{s>Ty3MXe{r(PgbV3}u%KvNzLyYjF_-Im}0p z@wbB&o=v0*+HQBOau)i~7Qa!_;=2%7g37MqaZ!bpQ2Pz%$J#gVf|^zTXGo6VR{MwU zDVwp@zGnv~i4$VFKNk~*@#9=m(>j}nz8E8pg4+Bg4h zb4LtXR#L*+IR)~U5@efeASET%`SYzXSAl)0#Cksbc)H@j*pwLLt9_5$ZYdRxuh{i- zcxrFhQIjcHM{V6K_<`Z~UR+$z80jjw?Ca7}<*P z7amV7vsf-f8th8TlHz-a<-Zbh;ib8Al_F*5G8LYz0Ndn>A-& zc#+e0I<$v`el|8aF?Q^ElWXtZRPLyuU5SyMUPysPZbreo z6Jv-e#u}9tVj)inoeJ^pNjmziMCkXXi{X6=j&SZ(vZ#0;?YDx#33SE&h4HGMCKP3J zHt?Uu#K<3=;bUnha1v@Q>G9bR+}a7P62WXCqdO?{0@Z;*{D+B`f0w|PA`O9CIcII7 zowu1lL>EAH$VJ-lOd9;90j;cf(@E2C=oDzy@7S|{2g z>jO%}9IOv&fhiBG(#C(<_5Pz062+pCl_*7r1q0<Q?D*o!RU=Bc_|Ara|_k9s_#IcdeiZYK2g72(ArDT z;ciI!l9|*@Mt(Qy{7SI~xn0cR`pOf|zS2{HJJ>Ee*-X2*alDg*enCxYc2Tdpi|9ZL zNrgOfV!4_AX9eFxwN(6%3b+f7B?@sD95JBoDWc{14h%B-C?^u8m6Z#F@k$I0>~fqd zRSc=zNCQ%_)vze6>~8|PoVTY>ZvyFHjp$P@uM+8Ib&TO1B@>v!ev~a@&joeHTuSd1 z*tsaeam@PWk{kwGRt4Iq@=K_jb{9pIZq{YBmwR5$5%qKobHw99)0ZFp!I0A*#i$dS z{Mk_G1EHAk^x7a$KXjuKeft|=slG2_jZ{~M?Qu&=nI!k5l)vSs6jyvz@6$`X4R(pQ zQ7`eL8Qg+is~Ot7_yY2R6J03UO52FpDuaR9<||%K@gNyTS4sy-DI53A)P5Q6(L829 zD_T5L-E!~i%;HQ}x%FtPXz}^tj}l|X(dtT%aDs-i?7(Oq7K^s0sqyuM$z9Ir0}dA7 z5C2FgmX{^@eX zX9j&b%2D%mv^%28GeCD{Wj{U>{rEIKMwe|( zquQ<1t=pHf;G_eicfC~LqgxgN@pq5xafT-$)Y{_|IWONmvO83mRGb0<_dG#3RD4Op zVNDh;KCka1`aV+pvA&<6?vXH zKYVyH%RhYlMvNI6nyjJ8O#{rG;RFbF7y@+y=D9>E;L<3)5o{C(9s$?di+io zIz~gsjNjx!$7<-<@l`HVuc7+!<=rj$T zHa_A)4H{|~A9A77HFWy;J6$NIq1gCkE_8;5&KRHVLT75|%<-5Dou#3(#*YF-OWQL( z2|2g(Ap`$|u@QkZmlRbfQ!hlVjfV=ThLM#e)V){E8V9HN~BRLrcE*qsn` zWKUFeq)DM-``DD&BZuS3ob79lKt8?|nG=hXQNqaXBiH<_ShQoMI_EU0Sc@kYa2CAw z*HFkLdMP*`F~5O6;4>yL1Q~fXoIlQr^FAs|E={1n){N|qt^IdoCXDQ!v-U`p+1bdN z7LV7DC$IU9M(2&}_{GT9$$Z8qb8yWaQ?`UT1yAdgBc0A19O)D~JJ3&2G5rV@HagY9 z$}Z)_&v$A+h>_A09>kq$VFQCl?ZdyTefSwgw?g_`6P}$CCP9o>OymapdPN?T<1FG8Jm0O(ovCrkqui}2 zt?ii-&>s@qd&gS{xNW>Jb_s@bM!pEM8Xi9ak{DCaqbZ6}dNcCg@Wc8<2wOh1@QG@V z7Vko*{_iqw1Oq(hJoU~gmnQCzl77DQjkE5xaQ3a;$e{$BUs0R-y zc?1U~cvwP58fHG*n22z44J%;G0{tFJl@6N>7k@;!fQ3Q-`Y5p1#{p2psy z741{`)lKGqmJ2tCA?$8Gg@ZP<+XQFianG*Ca zLPk)M&^C)pD5csD2eIy%MgRz^3IIbEjeq03GI1%x@0r0i1KZ`*y+tkOV0nQUI%I= zW$mpgvU?Y`qhCwZvp}T^H5D;uGtdOYoZ4F-G6JFqqei%@btNbJVT3~@W=Dk8S(8{! zL+4p3Jnz%b)nO@~xq(Hk9p688kw-$*|Dk+g3Il(M_c+$21j-8{dYe+Iy1> z*B8%Vb?UWFI2@Q?6X^HMKvIvBt9prZCkbmYkZAwAA-0juYct*0U3ACy@;&4h#!StE|%iCNMdngtoua>S)-25WjlC9Usf7ni!jc2<;9%)|<}0owC7A^6Csqs$vo$+6)4CrYqK$fp~!iDYNz2RrQn8Hx~8mdPTx^=D#@MLA9&Q@|2 zwHrSPVgU2f6JA#3;Ex($##7kMQ3LF)+`MNap5!>YnR0CiSyw1xmAb~5DHG&i3bj+7$}VQd=M@D#g>|{pwWZo z7+}UW9jRQeLtS1}E=yG7MO_W-(@~yXFz^>?bGxbc%{AlHG;mOfO6~S4wcD%IZdQu4 zquWNFyJ+KgH)6?d=jgU=)4x8k~$?9>WFrj!_@sbjf_S*}w-?VRhdPn=_#?76f&51D2kqRD~;Pwr% zV*BQ{_RT|~&7I-Ri3pFDmo>PJ|Ka)29POKTL#~q%P1fa3qfTLZfH6tNnTXwHE&z*! zOGbdMo4XMzaEj`8c<5DjzD99s5}4*T=7OhrII6@s5=h~AFbLx)@Py(q#fONXNwHtP zJa%&1^qN9L5eJIZ)B(#BauTlJ9oY)s1t!H&!;4c5w5YbwFH=C9P+!jPYhY+KkF&ua zG&_wWyY~zqscSgWb~u!NE>2ir=2dG>s6EOf8p>2v@W)4W>Q=u~bg;HNQhD;AGck(& z(K#=#f$zbvXf`;%f|BDu)Njh!Ip*)a^xt?*`g{rh54;BbBSsw?6KCy8%=u2KaFbx_ z{=(rc$cN1iuBet2`01bm{p)jQ5IJ1)Nap6J)~ z40=nuku7LkK%K=BV{`>*?%EGvb*n4_>*V=^=2(jZFMTdYqYIqfbxuCapUZf4f2U8) zbPaX=&?ULt0ZZ2Pq`QG_>0^|(VP*<@nQ59@864Ivz4i_3=mYB>IHYhwQANZUz4Sqb znW8PJr$!9Fdvtr(B_81o(!0%320Yf*gYmI(O}O|~TXnYigwrO@@OJHNYjJdEd%a%u znSJQ%bMuRIPHYX>-!rtS8nn@L;||!{?_>`{$h4VsX3rY8cd1(g!&|Ii)TBV%tmIo@ zqL&NATU*Rq_wowDKE9~iwT^Eo5VRZBL$xNPmb_Rxn5o89 z;cOe5d5Bm$bL*H(kb`;?H=V|Qc!tTWz=5Yq=)Y*}IWOnnL_B-{lCXFG@uZagtAX^e zCn%R4gM&X>p)N7!Rjh)mo@=aHzMU9DXH{Kz&i1uGtsDWX;rBHm&1>=Nv?r}hHNn3j zG0L+``WP2=Sx2j>^PMFZ5T}56#2kPx&c;8UMlf8sjZYo$(t6Jmkzc1?_Sus9f8EY7%Gdro4?b zfJU?QuR?V1ToXBiAFLIpN|a}{UQKJId_A{Q(RiC0=n5^QR4{frVE^aGY+Iu#ZEda9 zYkOm;h=BslXB4WLJX#A*Tsf#S!dgvvIxUw6{MxhzbR(fQXe4g)qi;Y}YQ;*eVYEYq zrk6J|7iIDAR-X7emQXmplX0|LE$#I&ehg1k${|;!Fp$C!bTvl4qa%jm&-tF4-hA?5 z7;LDA{H!&Mi_V-QV~v3hjqdb;ZRpNlU)y##lmzWrA`|?Q67$f=v=8mMZAaKoK2Ag_ z^6M#|{Bbsmhj-ZJx?1Gr{+m2wr#_mn8*eCycUwP%_Egq74jhsS z)2@=!b9@P-q`>sgmAHz>;L7wZ_Xi0q>lv2yEdSZzUsgNcdY0YrnFK#`u$=3b`c&C2 zoY<;k--mbMa;82^wo8u|Y6TSA465GG3JG@D>Ox=<6s6gQ!L?^fDnB0;R2d2p&-Pas)uzehDMro(Mfd z_4bh);xGHtZ#B7_IsQ}gigJ(ejLIY)i){g1Dx+FV_9D4;46h#G%Wi^UZ-|=0rYqe>j)-N|OC}mFd=T{SokW4DG>>h0}KXA(=4En z7*^h5dLbWm;}N;jD(l9Az~7AjO5cjVyYxfVqBRiNupIv%9Tn6Mk5w%iNTq`yWzJ`s z-`g4?Iq2(XL8$#qh<2tDtxff4tuG$df$6(#e~~;l>@Afk+fqdx%f?m8WO%y{;tbV^ zQjSF_$Es55OWg_UkM=R;DSptGHM)%(V#kKXp>Q6l0~=5rgG0gS*(hg1Y^;64dGjXJ z{uL(kd;*$dyrMLJc4NPIjfYOmO7Of#INEhEc(cFK8J%Ek!edt|FMb!{s;H!UoT7Wi znExbUI9^;7M4)H>TBky3+7peLqqj!ozkm@Q>lB}*9ogh(Pw1tvQ8xd!=hR6mm=!FF zN>Pt+8)Xhol=j9U(6nr6EX^|Dg?HNqRzx;1HUgA2ud#2ouZr@~|J7RwRE6Ve-J=b7 z(Li)2@+=Nn>h{CCu*V4$ZF>9<%5|uB(B}XaV$(fH^~1#Ix7Bmea7MmPgWE$*;J=Qt zMQQM+uc7ZYI>TxYqK&T=&mmK&h!pH%+OnywcJ|H$ht_ruox@X#IDN*wqLC-p!Mzml z3^K1!g^sJOIWSK+!}vAlM6xE2wxT*2m0(`A6WW1|g$eJeSn~Tan+`YwS*&VB%!~lC zqQ&rQZV05D56qV4j1kXp{%0J{ZE;1bz4c){9RGV#K!)~E?WRXuz|KDVz;mehi`pY< zA2#a?FcEe$Bdz!AI*inj3(;;ZrV7-T_*?e3&{{O#N?2b551<*dh!;<{l_t&+6ozEki;n3UtB{_@ta z^Y5WwpsBtYnfP=h^sUG=c~}W=`Sl+ZWvmwJF*X9N2l!(&CQaQn79}Jb3tz}_AE7IGrJ18 zbb4kcwWfbns*pYq{`o?W;@+PU#OlJxQBA?4q*LxtfK0ADH80J-jbPo<46!A1SvEHO5IZ8rxTxbKO&$xi z&Y1$|ESjlCUjqM4MSHWk!BnBppIH?x>MBboxslW!MlK+>w~WXhJ(-!)Rs#} z`}5FJG(X(k2R%}KseCjAO%J3~`E+z3o9eMFt}vMeSW2$n`}>(_s{6*_{#^Q8KSqX+ z<+l+0_-Vhr$m8-u<06doxvYIfU6+`D;;5-g66UOT}~e`ddtf-G_N^O7G9V02M?7%$>V<-U?bSO1{Y}S(jw> zdS@r{rB_6krFzqa_0cu`*#YG~=SH(@={$R( z+-Ofa-<|6pLT`ehz+d*Pk9H4a^XZ;*qN%Pdnjy1l@^B^(7SW+x8u3?8Kbxi@f0kj$ z_EF=*+`0Lof&M}=RY(qJy8F`IH=vAMT6O=t^3DD5&Fg3$=pk$S>17nG-kv;pc^_O5 zNcy`Gy3_Y`4X?tzA{okMSLITJGop&^g}O0eT=|HpX2-xN~?WmUN>qlKPxhlgPMm`>{8LOGb8 zEKHE5lU~=IhDJQ^^2Subl5A!PCLMFb9y|;oXeTY8?#1Nr@P@%V44Gn z?1URZ+p&&B;q|*yL#gim!up1@-k7yeeXKh>v_6TDAiD;kX=-SQ(N$@Uy0V2nGorx{ z=T;5VcHsjNmzkklxif|pY%z7iXg5EU?(XlUW5b*zn_G{88Dnrh+LKkrYYKd0ss`@A zFn6vV?1jmypdyoos_3@6(J@gwH@tTTAR2IQY9OC}BUf%XgAo<03|#~1z#1xwbkCoq z8i*82&2nyZrShNI-nnznUw&c3S<$nj-SCoyl~LMK??84fM`jgWz2@L)qejGz!)Z0_ zuH=f#8*~scF^n2Ra;de+p;Qh%P;^rSKq>^ynFz+OW9q1bJ+qKyFs8aEuq?RP&p#SlznU>CUV|^?+T|UF#W~vIW)C=v8lfB8n;3 z1m|ePtB5p*dwb!z`Y;7q&AH%K=QX>DF}zh6>U`s76Rhaq8D8&A6*-sGs@!b;$Jb2H zh~GSI#tA`5i^)!IgJw0$FRN1_rxx6}==f(D$T0i$lAY&&3+pq-d9btCo&adtI zioM!bmgm-AjA_xOnZhEjFYh?gMy<#5yPjK zWe3wYpsTw?$XL1T)jsP?zzZAE@oVD8~Oh7iHoz=FFZEk2bj5f6=DKrq**CXP+ByR?An@&AXwSmO7zzb)gfc zOscsH6(?@XR_iPar$*CLYZTsW-Z_<5;O{heM*waWq}I{qO|82U^jwSFVCX->>m^ek z42AE;6TMD(uX*-S_t^*CXAhZYoAk3Sq3{Ovj50rCo?XQ&WD9prts%ZM0Jn|I^;pn1 zhF56%deivjcw+#bFu1H^9aye3_!*{rVg(;;vkzwfM4o*2K_9v&eXwVJuon#mlTnGX zIOtzY5}WRWHTYoX`(T|uSdS03&IeO}AVJ8&cH3g;PNR^pofKM{azA2lEPtv*+V2@` z4H35GjZdMITA=kd9M@Wcs8v0phxzF&sXc`uqtiWL*}_!S6n!?GrV|e&Ah3ds$bnE7r_C15K4>3_EI}FA;Q-4=O5aQ~Wptvep;1>fYkGcu?2WhgtDB768BoF4# zH~2r07CH=ng>_k}-dSOuJ*A&@hW}w|4c{3u_&SZZhp*>5?W*iM%(Iz%hD3R`_+Sqi ztW{CzdTuw^UBoEMnC{0r4E|Y-w}(HearL8EU}F}4kG7RMV^AgaH`vXZwvq0JvYl^m zyCzMn(_mMV7KvDo!LCt=#?~3k+8T9phrzx;I{L0Vshg13O+-7h0%~~$E^n6 zLY(j1r}1YEu1@WPewk_aT?SWsfWVgQY3#YcTEh&4{;C3yWShK+{BP|kT zPZ;bo1o%FjylvN>G9K(F>kRfy_RUMCZV5-uMkkwxjP6JGn&+>w58`pn{Per%rEHe`7_4fYjX-mXw&O!4ChWxi8m93a^-QMV@=Y#RaklgG49 znhie95Uctd?0HSw9)`kMfAZ-u&r}>|ShZx)Y{kng58oY@(XyN$quh!>) zW(LQNzl)a<8gemXm2CpR#;23HY{YwT{f7J{)2Pd|}6w_hOBR%T52DoZ}gWED` z2WgPCDgVap9>ic?sh58VbI?5;A5 zzUzL2@k8_IH_wD3wi(y?tD4K|C!uo2b2qgsv*^GxkcQw|tD;{nR-_^{9X!w7kxhYZ!|P_meAbl>UN8H z{ww>OxQ}f@SlChfNQMMB)B$6u|=L@hI zt9v{D`Wk4AZaTx8yyx#Q?|;&J-Wa}x&l^?UH<;&}z31(eopodRA2H9L_MUf!zwYsW z%sf8{b+P3~>d}0lXWReWJpWto{r2z|wQlVEtNa} zh0R?<0$hK_8JBHVhid*V{&|X-y`=jdU>$z9$B!i9dd1SG5ocm4fV(eeIi>b+_F1Mc z#fZ1NQYc@dAA!5|um-^8yPsjc5+@pQSDAR{rHDIg^DyQj>cmMYrg6El<~@(75y88+ zxk$-MnMKKex_2XTk&=^!`oYk>ysFuUpw!ujI#E^ugTGuE^qxnY6TBOdi#R8VJ1-k? zPWEm@E>dz5$a@fRPV;U|?vhV3Q;8FexSpDKUWzzpcsC|@3?}noXO?RZU4kM~a+1({ z5b-(jbf4nmnXPQmi0Uog`!*MGX4q`WHKI<)l|bqy;;3l8;z7ibR_r~FIO-R99$<12 zr?mnj5GgrX<&i|3Ht$B{BK&B9Pl`BmeNggO5pBh@h;x1gMj(QZw3MgYq1dqV4Hs7P zR@{9Y15enw*u^={*%WauvRU_flK?B zM?Uk#oz70YpVvy9XvA5-OG%mwgA5Ct<2%#r6~zBu-{4<0|Hk#NmJ&bLwPzI{8mfq+ zA|&@6W*+?n|E1f5$AFjx0y8`Wv&t@8gV*!DM_z(l7EA%9~F}uiioqMgqAnYDMPBn`7GjG!AnJo zxVs(Rqo+R1yo$&@(BvWx8qFs~oaGhhr#?ie5+@pQpgf-xajvOA+dogJ5+@pQU|ePC zn0HWb9{wxwJmOp@?sWD~pXId@CmM0y&Pzp#IPa{$2t?e|@!sRFeu{Y*kxz;^D}7M% zL1!vU1!e!0ILRVTcNsO-c89445BDMr(!&XrrK=Z^{$E^_F`7*gr`Kjnt`VoNJYwc}Gi$&eQWzs=QW z#C<|OeyK_O>Kqi#T_F!k&8HA7I$7Y;&8THnm%|k;(XW5+swx>g$oNjWgeV-6!FX5(r&gd;2IHrQoz8! zFK^v^#Q7&TFEbJ62l7_smtV|sJv=TbBF>M*otKR`Kk;rvE+XO_3)d-q{=2;`@r)7Y zr!H)Yr0_Fbsc#i;Mx6gA?sRsYqWAErf+FJV5qDlT;_%d4Ws0aB20wg4 z1^w@9D8GjvE?&dBXazkVW1TYqKRf_Gq5|JDjpcgyc_rl@SwXMMof?4G1>n{z3+77x z=>hb|1mMz(d%Uoo89;x00DfWsE@yR;|62p-PY%GN0r)8a_^AQ-X#sdF0Qc%V?RP4uD}-})TZ9hEmbPj&zuT+&BvR6nb*_>>z@Ji7X{$$0r>n1{K7M- z2M=E)3XV7nE9m!GFPLNmGdv#kyYwy~Y;@;7bDV z&H#LA0KPl`zbXK~CIG)S0DpS`?yd8%o;F3C>jUUl2H;%*cuxS{8-Vu(;Hv}h8v^jb z06Y_b4+Y>i2H^Psybyq|3BcC{;OhhMzX-tpG5~*f0Df}-eoFwpDFDAU0KY8&zdZoI zBLL@L0H{n@R|&xR^@7TTb(H}8Ljm}Q1MvF-@Q()I9}B=g9)NENz&{aye=-37Q~-W| z0RHI!{4)XgX9MsD0&w*wkSiJ{;(R`U{tE&47X$Dw1>k=ZfPXmvS4S~?>0rCdep=XD)jr!)`x}dKUM^gz zgWL)J_^%Gr=j+PfSa_K~{l-gJt~ak-U|u}`dg3-JavV$>RhoT@KcBVlB)u19So-fr z{poYpk$#Hd>U}GwJAnQZ>q+m;(mrC|tUtq_Kf)#7(->>lk&m~k_ff;=bEo_BnS%YJ zX<mh>LG1^wY+fBNr3vD3nh74(d3{q$3#=%EagY<-Uopnvr* z$iLZ=yCWt|MpF!Z>_+4HxQp)fnTra+bZzyss1>p0{?^R?{h2gdz3uqRp8Bv z{`?C3zk4aq1ulM@$t~z7-tOXdr(p>F(NCYZFC)FT{sT3tRIBW#!{uZB!|ofv2W?rN z{%N8=|1Ej)zrrQ&F}ZA~Kc5MMq+jaN+Z_~Il%M=Z-AMYYT>3jr?)by}>9?aD{p5e> zUH%GB}o5sm%iTQCfrPXwTl;x z!F~Ete?EioOMd((p`oXR(K$@Ae)5|bfBon`{65m7lbK}gWX)av^mQ1*{rF6|gM45t zCQUQf_b42VVA7H1`or5vztO_(NzM0TfqGgP$}q|5=bz!S{mMOZ7U@SUxy$FOlZfBy z;%E+)KJixK?{RU<|0e^zP1?=@78Gt=a5 zRX7^KB#WP_a5RER*7LsWLh?Z&CRvJ$pkhD$bSODLV9DK`#r6O>Z-0kBpBvy(r-d=d zoAg<8tq<@I(~cqiy_Vehq{;m~a6dVpfGeLCzR#t%`xGahNBpBMZrd@wg!so?+*(ZE z75@D9DEYUz^p~653CEGoCtQ59$+ata?sxI4P3~Jto=>~@H70kRvcu1~xILSF5(uV+ zt+#gE6I!ij`OA4W402ld3of56CN~lNcUt&ME`GGhowAJhmt7p{RcTU`_*cxG_l>>( zJdyZAE`FKG^|uoLs*68ua`%t;%X!aXz+GxD^C%QGE&Qkp9By(iUQGNqE*>+vJ8Fn; zb#dFi6VX7^!cVw($mAw~U|RS|7vE-bAHRtBf4I2qr=2KhTKGFI{=CWk3;mmmdYRu& z@W-11@KXYCdOSb=Y$-pyYZ2xAzAH$~ z{mR{;%Ke#34>zmQaRKy=7>}lfcNi+iJ@L$yB0v5Y1dPiMwUf_pT|TI$N{^mP{P!+? zgvs50Gx&p7d1^hI^e?-xCHZR@-n8&;7e{qe`U3jhwD2EYe74D5HOXK8O{)Kft@*hp z*#29MBi{aZbMf937#)tYj5DEYm8J-BO??h5E%BNP4Q+;s)(b)+k&?RQ_8 zNqnk{f57m+1oq>n&)?1?I>GN+R?(O%z&+z%uImEsFK1+X^!oB@Ii|5gx)56|< z-vq-awwU;FuAFlW-lhD2x8Jwg;J0*;-rMi9AuykH`B*(nQ1sq@ABIttE}u(! zZ@({M@OvP@v~bkrW83%nHsaoXpQUe7_^B?v#W%vhriH!zK8v5O>?h{Z-(zw=eUE>; zY}@3I|0$qP$kK$DN z;w}F4P0BBM`+D}p+s^Q(|9(KbTo}-=FAHe*Hgpg_eg4;S|8n`g5I_3Q2KcursP!pf z{_;yF>nEuXKRzcLI`-Q%-g5oDf#3Dy~NuE&fPRCkM-;2?b0OZZxZwp-yrZ7LI17* zd}9E9i@-+&AMU1EIUf_aw4ZMaT+%;naJKWUg3q%7^wJ(~6Z9CqRH7Xo7JzfN&dMq2 ze}Q+(ah7wZ4?S`2;`z~Y7taspE}q4w;e95yBlwiNA{f zEPpBgCq=zhfQE^B=5D5??^USgFX`79ob<9^Y!viT{(6CbTJT5Ms8YE;ml?cV9yyN4 zdP%<_<-A_-`K%~c+M(1NhJBSNkK}W&!OQC#WQS2hPd>7~Ha;z{?@B{Y{!-2j1}D9g z^8|yl`A-n_I@RFh!(Cw8jVU_ z*Vy7eQK-hHozSN-mFvOAQ{>O6+w!sfn*6!TZ1J~}2@?58JvS?~lK*uECm-%U9}GT! zC-~TM$)CH`mj4n{F6+fvgT=2lIOTsF|5-dJPB`7rlTTFexyaxw_hErgHaPh|B5)fQ zlTR1^v*jYpSBbbcTk+Pv%lZ79pg&dc|E0l6&)s&*KY~0{IiIw_SuSTimVULMmwXr- zFqQLpTJYg4$@2M;pqG54ez^N@>GxGX{aPF)kL2?Y24}ra7yO?R^irN@1upqqZsaGQ zGX$SbgO}?$C2+~-8NugF!H4rNrgA>g&u}cT`mz3m@-zrOi^v3t_*nwKT%ncz!TM*? zbJl73uQv3=-zM;(06qp*<@Vs_Yy+@9Oyolx({`2Ye8u9|7<`JkT6~qcw{secZ!-8P zf}S>D<+t@EJ!j1px8)E&SK#k7eAs`b|D?=Jq@N+^tsaPTmTmdVaar;q4<^#HPBIRV zcsp;9q<%=>sZjm5RhYGWte=wlA^q)!-ugYZLk9m@IjtWi{+N*eSLQwHL5>%@3{H9( zS4fGR~YR_&hG?CkxyW_}c`&Rp31W-zM0@E;03 zvY$2!-1;k)`(uIsh2SIk%Xu+)UgVRp9nrHQ39{J0_WHz z+>T?^=f?#;4|yhwlOGdhU?P6Lkl&88q?7&bE)pYIKDOKsY1qXrf9v;1&(%>&Z~YJH zp2mL`XI+>`f1#k4>kN`!+O3?I$$n83e7>r<>;5kBtp+c*=cfd{w6{|Q{fmPCMuSs+ z8P9JvIO{9p`7HvM{^7l3fW*s1g8v5$PCmaCIM>ja%IV*2@N)Y9E9lz=pWT99#-CdR zy_DzEf?mqADM0?w0Qs%orksC=|ExWXu~JBUZN8Ax+9Bz`hyN`7XOL$i{X9YcHwxA5 z{(FJXFgV-&C4oyjl>I1U=*gcr%V)B7MSOwaW5+@A|1W_b&6kjf|3TnUf=I+)7I=$7 zb-fk}{33%>j$;MBMBrZ#_;Q1jKik^s?I?p&{zU?RtDyg*z$N{~0}S<(AW1y;0BfbyjaH1^-tCpN|^6oW3aVCy}>& z{-41q|K)pJZ^-FBbH6Tr1~q?T7SB1pT#wKRcO~{}jRh z3PCUXmu#gnsP& zn)dJ(!N6bA-}AzoNvnd%6Y4-@2KGOMyT(fOV848$hg{$ zYqTqgOM8B&uoLT7NzXkOYgcy*J)bV@^P2`|`(7{jd`r;38+przGBA<<8G?_MpL|~7 zfBIO&p39MEBAyiduT-e^&$54|4NiK-7?#hFpg&WTOF5WGUya{5S5fP7&lY@Se?Ldy zR?jSlF_M+X?&}f%p}=oMo{93DAnLnTp}JhTpJn3>^0{5mTYpRccdn3Us^LTWnF6-Om$z&H@b+=@~oOcKNcwS*~1XdprQ=nj{nHWn5wRM_4bpE=O7>mdm)MO}sC0 z#^_9ToVD~l2DkB##Wxz<+9Bl$>Mt^0%BWJb{PYP{4+eI~w%+iz7_S!Zkf6aoFX-)l zJt5kIO;M8}UMuJu1b&pj4eXZtMFU9w=TjLbMa`8gzA5-f{t`FjF8@CmK=S{YB@$P% zrhd5AAk&{4zb8PRgN6IGJN5AEfO7qC_Ai^Ho_S{5CW*gbL2)GumP;Qelf`cfz@H7k z{p2M7vm%naXaBJrflo7eUO0v_k&hkId4Hn7 z?Ocp_v%u|I5OF)skdGZZh}&_7xQzjbuM>PuBw*4V0zb)uxNZ@+o%{3gLjsQyFloEM zPq84bKNt9^0^cR@(*!<&f+O(~Q#t)ly})g3#wUz9nRv0W5bxUsekK8vmJ8gDDSXr? z@Utv2t{Vhy=e&G;m%!~>CGq<9ApP*JlL%^#b21@Rb68Mc^rc*Bk%A z$K3+Af6Ie-kHD7;dKr+XVgzfp-Y} zGXn1u_yYppAn?x#{38PYyucq5_!k8Jtia{i_@cnSB^{YHU*P2l$m+>Vue{GhJ%Mi)`1b|AMc_{h{1Jiwlfa)5_%j0EDexZ%+y*zS z@4pCqx(RxSKP&JCf&WzC?E?Rqz*h)d#&tsi|AnC6Ebtuy|A@e!6Zk^{|E0jU3;cP3 z?-2NZ2z6U*NwH_zHm+1zr%ijq&*SE`k3aLH|X8|5o7J z1^%A`|GB_-34E8pe<$!76TGs%zZdw40{_1PZxr}T0$(Wb{}T8Lf!nz?9}fupWkJ7D z;JXEWx4{1>@CO9`CxLGlIQMI8+9B{)Er{!Gf$tIclqvd#`cab)^DHXxP{@F|Gz*;T z>Na%>+@4|Pvp#{_*q-=CfgdLL+$-=&0)J58H3Hu*aJxrL{?7~i2togfz^4d&`jLD~ z$H_+uyg}eo1>P=jxz4jf;BuX3NZ>r5VABT#evxVf{p10Gn-EVu_*;RSp- z$MpvSuNC-CfgdCA-wFI!fln|@DdIT-KT_Z`1s)T4T;S&lyjkE21>P#~#R8uz@E(Dm zFYrNuUm);}0>4P$qXM5V@cRY6P~cw@_{9Q$Qs4=Je_!C23VesaFBkZ@z&iv!!89KA zzeM0i3j7Lz#{_<*z|R%xI z@OFVeF7QhQzE$8W1^#V;_Y3?x0^cC;?+W}@fj=eidj)cXn_vn^{tr-<+Ji z0Qpp=XI3V?)|pXAuPZ2qOA3{IERU8{S2kC0W{^udGRes@SK$y9-P%b zI5dd5%}LH)*qd&^MbGd8|uQerG7U^uDTrCUJUBjysQ&?#} z?KO6Vai%kCW+#(tQn_R{Z)`agTwzt;ITvShgQ-Fxotv{Z*I!5{^MzbB))b$U%IDL$ zLb5m2KalQ8_GdC_??pe!nw4bEj8(bp@K9Anv}!}|&m_AC(y0vIVmBSMFZ*gt^pm)L zy*r!97n){?n`ACMnCx1gyeXZ_I>{v3B9l!HWV=&^{%pqS?amYijC{UDwB!f+yVEV` zdg*oXR&|5w<3{O1zv; zLVV6ZDzgf$l|mczXV8A|WL0U^xQ{NlkDh$8yD!~+gEJ$OEu?2$vgE3B!4UV_4_0qG zO|3mNQX0>2H*BImJxx~iMRf*6 zvm!sslvml6Sy~H*h5oD>3{;P5T~_FUNwM$G?@F?>U&D@NICc+Y^J$|J)it|&vOV*z zm~-*vEy*{oO-=f*MG&sIi#t|ba@|eW z2kFLjW=DIDJcJWIXa-K#Xgnj zs}kWsnoCF8V3QH=m|?a%l|i3M>LFSO12c558c^2cV@tig{jJ$N`w(gZYq%o6tdMK# z&nMG^LxuHd{_B?JZ@ghC;2VpT z>NdT|;MEkO;ywmpO78)6Ywqq#CX0~lf#AHwq!1~wu)kO_iDP# zG-`$Nw99XX1IV!|t;yy~k?mWgwGAYdR1aGVxm154-$uezA)9L>88gipRG|t%RJ^IG zx*|_$d^&q;c8DXKQRx-xX0?$70Sw!DP9e8GsYIEzJhwg`SA$e`xG*$)2xTy)M-kAq zXJ@h*j?A-{WHZ;{x_Z&U6p`(9ErWbUuRc1W1gqn5H7p{mftR(?rIdo&dWSRJ)MCrU znZ?6}<*OlHTQ?kca=P1Lf3&!Ki)49R^j0%!kbLk<>TdFb3(r&mo->rHIbzd>^LG9UrfwWR@zNM>> zOQ+j1sWttpQU&7&;7hd3)jP)=|_9@f2FR^ z#}-U~Y1KVte*DtpqDApW1T~0oa@ov&Rln=aUj0Jq1_WjMSN#>3S|c*{?|6*5RHS4k zBDlYj7Oz6-a@#~$6`$Rc?oAC36q0)40{YqWdQwC1o4WB~wpU})UJ{|1;0zuG@m0KO z*-*M$m3i@CA>N$FoyTn7jr-aI6&D>lNZhJ*wjW|!b@}ubUvWr}amx*Dy#}kIa6Mq& zs60-E2WUm`qdG6gd_+H{Bm4AC2I+E5qKcCBN{|~7tjM!3`nVwzW1ul1#jFL{wP`f~ z9U>w28kzfs=C2(GmipWk`Bvq0{3_sv(yFrY77Y3qV_s^*YSq%PN+??f*846&Pqh-v z>P+SG>4RSWQq|NI(V1UFv{W}Yqmcuyc6@JBB%e9bzAJXGC8&S_Gp5pkOjQt(=z+B8 zVqbrRupkeI%%0PfY~_4(Kt(KmolN)il1^Uk$b_}3f&B=WR;YN<$8)u))uH8s zgUUCvvqBD8cGLb82ebCAWXUwKyRDE-_Vlmn-;erK-*5UT3N(skAlFG(_~%8f>2(NN zsuJV?JHkPir*x32UNO3f%6)~Mth8RCCaXqy9V$q{YFswgqP@uLr?A&e&Z-pYP5t4S z^@K+2$S3IY7NXoK(moiPb{N?}e*vd8dr0j#n3ag8_yR1#bmr1M{aAueFH5I#n11Il zNvDLE?UwewvPm&R`IcmxeJdX4dI?teS6A6Bs?nZa#97uIZ|d$F&fI_%xpk@PI$are zZY}x@hUH7X@s z=7#3JmE!9tu}7Lqw{TIdDsH9fS&{DzvKrk2^zkZcYDvatE#ofC0!+g(yyP)?<82a~ zJ?g%is+1Sr@!ExVYuz2}vi+T(vAU({wgah^-n{S`LD^KcBUahhbnWAd5!Usy&n~)g zw%V;q??+=V&NnM7t%%7Pb*qBr%*(C9h6c>PX;zBs;#hj(O56J64VmoP3>V6)X=ZoA zw=X5OsfEw#O3)5(K|pOVc&|635rPzOG%LNcInP>#1^#_5XeG6l%P7^g`wZWRYQ>wV0$$hfZD%n%c7ulXi{`k~uzl-& zRTnXM=!+QG$0ACH3L3sox6uB&m1h+Ca^_Nvib|QU$mh`Od<)oLk3G9nhg*o8GMKvL zO@LK}Y}&OoRU?od8V|H6y*%!tTv_*d-K!}Q!oJt5xcI>-Zgw?_)a#D%HZjVY_N#J~ z))DdktES8&{~1@cB`k{O26Y81nv0)&=x%*w^08=KNM?oW9P zUwv#>_)#ot>&cf`W!#UFb-xTYC$Q9OGAdf0o!r-&kv|pVTOF*!^>dp%-efXKp013? z)kbAf?Nqi7WV1I65ABZ*?Lt$<-oPwOOIy0#;}NqLAb1iReU)(}_DGw1a@nEgh5U!P zYUFXpL^Y6W6;#OLvr{<>eji75a7MBlHih+1E=EC7mkg(JIGPfmOK#-uhmhq%MvJm^ zwpRJ1uPK{v>g{Pkyqx9%8Mw?Vn7#Ori^S8VK2zP6uI#!r_td$!GC!*hllaPQ4@tHz zz}cg$Ugq;P$AhH8D&<}F#*xGxBJuG!`>7Mp_VYUQuP}VOQh~<9;k3>a#+pNF**vMS z+%@hBaeVrrItoDh!nQ{Ohp`9L(Sf!kj+6AHm!^9uE}zN60z*SX95mzEWYgtWFN!zO zS0rypug6i^0W-&-55e(X-1!<|=?;rkN_l6b3o}Q&yp=Vh!ud@(b*$qcc!MfUd0{|Z z^w~PSUaGX!VYSt~42x1!RDZI0+4@ZPqK>4MzT@>4UbTd8TlgG~8D2DG7KYVp^RGyn zttzugg(EIn8RaeJp4B$X$4Vb~TU(FSN7KSmeav5+JP6HLRTtFtnVr;rl^%8B0cj@Q zd_bz<>t(vR%F~T9mih6f1J{mP8uoL1LxtQNb>cgTT<5S_EXGefR$>@6(y8~sX?a4qaaPa`d21Rv-FW@d*&YJ z?eobjmWcPgIRCXq)-2tOfNHQBj_x=S)C_L7fu5R@bKE1kbJSwjP?g1$Mmg7NMfn8_ z{-gc<;^E%6*6p+iRA1~1^{~X18ZN0w(G3GqpK#TxX4#apS2jalK z;4^S4H|OI5N{8b4@r4|t=EnzcjGYGuugKwWiTbsKfh&jn{Wg#Nj#d6gs>rpR75U4R zKdqRe`Av-1FEZm-#fjv26PL_!s-(YX7;)MB^(rm|*=#ucHTW%3yz$aZFS>U>j(Omu z<}BrGa3n?1SJSBbC-K0WPU6*XI*EDE<4q^DFl6kQ_?&AO1SMzhUq?+jqEL9Q0Y zE;sB`f2>|gyVq87Y^L;3;i9HPR{EeGz)BD8>|g1n!B6!HpaeeaF6LzIKAg9!QcH(e z&iR*NtDk=VD*Pr#AK|G|FxQz^uRE@m@_%eU1jKqGJZGOvJ}{M(PeH3a*;J)kN`=$Q zygpHsIdFj=LB;hBHFC)zob1cQLI94r?3L_qse#?r$8pR$*oBu@8SXr=)?(>r zsHrjA+e>vTlN-+D)gsV-sz7as=>v&=b;jP$9JR{C<4S7$m@|)yPP~otb0D6J*iQk< z7mctk&Q0k+O|+rqlNtOhSGz6#m~sj3;IR&oIKGl-N40FIsR!$c{ zCYMg(U43}EYwk^4CGxGZ)^*iCqQ?eac9?p>ZdJM` zK4+o2Tf`gttNJS1vaD1hcN0aDnTapLkM0>ntX&NJU-aQW=Zxtuo zlU0iDoeK9Aoxf)0DT;i5cOGLWJ}7~c`F&NWq-;3ar%w)OHNM6)M{N~BnK)lsvNXca z60~;V(-YlQiRn%P_esLNeiY%5m#+0wvDaNXga5PY$2w{>j{m|It8l=Lr@LLDnr>%) z6yTnDYr+x}W~jaW>yp{teK%)+kAFxP#oBPf9iI(K?}xPB;9B0N#CZedm)t`Kx_$gc zV{|rrehG5m*P;BkTA6fXtwUq^OoIY`*6r((*xmO_0?*a0$n#{Jj~sS7rcbZD#`&53 z5<;oLw_bLWLVwZ*-+h~xU#d@|kbEyKr@sTV2yrZa)wEycakc3}dnu&UJXHVou?|}Y zD?5g|eV0zSp8<+dL?@IHKMd<>{?A&ms&_7sJrAt+eGK0Q+Qu6c0if>T0^<)v0;UMWglnpPmjHd+_ zT&k;D-tn8bSL%H~aClLp8PR<#t>iRS>&2NXGTrInMRU|AkFvuys+k%>P@D5R#jy> zZhv{Lbw&G)Kt>PhFw%ZBgAO+|IR#^6<4A%fFO+9#wYaYm^s#2b; z5WKE`jZ`$zf?c$Hdi|m*s*qf&9Jp_Qh|_^{xCnrc^>wG4+VG(b^F49#!%6{i2tisHlL*|Bzo*)$-ZD2&&zolfpEn-w?|~ ztLn`8s)1p%1zkBMaL-hlqsaTAcUUEDsiFh$C)bDxpypZ0Hv6k@U~-ti-?}sl@(q&9C04v862dX)|9u^nxFJpr_C2enhxB1Ai3G%CI5dYr0_9?H)|{OWr(H^ zx{>{_0{7RG9l-Z=TXC|`?{mU-a6QyNOt_4n6F&5|^}iajpU1E8TW@mJG*t#!-Yoo} z`>6j_md{^YC^IQ4-DiF!7t!NQdNCazGQjG2U-j{u`#UgqO`u{PcTmbP>t9ut5;wKs zI>{ld%sv-$ptEhUb?WY#R^L{h4j1QdP&*#wOYrtoH|9i$s>L4;qX_}T7fF16^H!Av zLGz{8U68WQv2UutpXhmGsN$dqQm%>sNAcPVG5W=dHT1H>A^W#BZ|`ZjL>B&_7ztAu|9lPSo{%Fb*_L%uk^>#y)X6Qdp-xRf%PV^ z>T2pkmQ``~>|dYN@(b1UcavL_-glGvQ=iLMH}xd(PjxjokaA|VR9{Y6JMCpK(@qJR ziXZwKG^J64*Q1=&>r+nr^(v?B^{J1tu-Bn|;`W1w4e_=kiVFAY0$T` zt8r8NBBVOws{dZl!Tw7@Zx~VMUsv(*fR7*Tw@wZy4 z5zy~wQAJFpPm=ln-EDK2ke__OuXy84`s6RZY>Pkh7>_UJIpC|*P2ZyZlgBp(VGt0H zBTDVZ$Gz|g1^n$<_3JM6=VyoT9Xb9HE&k8@%pCtRtUc7Y9}5h+EhZHwKn2=o=TH)bX>Ir{C-|+B?oUJQ|Jf*|7@tXk`8$I8F9=p6Qnead7F1&8 zM<8j|_(4tC--<2&9Oi+2^Re=ik4wcTAFD9cYU|Bv@BjB;FPER8yS(%y3!cf{Uh_Rz z49}Gxhnp?G1Y0p#*f%q++9+^(nt#*T*OiDf&rlE!PAezTZGIpKr0cc*!gMcw_}R5& z0sl@s{{{~KzA!!@&JX&>J1~H$51uRt_(MC5hp-M3ydW-&|HKkpQZ_nTZnSk+4 z)TN+k=C>XXrB3(3En1CY{l326tyF*Yzf|?KyN9!9?>XtZU8(>5{qJ|bd#k#pAHkEi z$?VmF3Ap*)_kDuLaAxrt|sRT6n3T%TJo> z^x2zJ&GFM#rs#$Y{Y>^AeKNuszG9;9S*G9ReCGYs8-3;0yRS8o_!W_h_(N~+VKS|+ z)kod+k%z#C0Y}udS)-z_-`-MY7VoaW;!R%@Uh!^YdR}pnW>+Nr@)EsoqRasIOe^p7 zPD@>2-fn!V?;Y(_FTbNbe7Wt`%k7BbXuE}Hbf(Mv!u?YEgle8&tmZqHEm_&gS8((< zs>}FG>S)q-sax3eeHPQb4}%6)@bXK*7XI?`&iUPoyO%7OL$ChwE4zE9(T50TP^u;Q zLZy|@Kwaf8e7KDMMAg&HS2wf_%$*S_6~S3W9GR5)ztvs(0PaWvP8Yl!?g)9q-!tF{ zoOwMUzdx30G(SgXzHq90Zp;YXQz4Z$zbM=}uWP}=cRk+RyGy0z=iow*H!tVW@za;K zt~mXZORk)A6@OE7i+q%A4*iyZe5g&{e;u1jCn)-~F>O~aDsLnG8Nv50)AScL@8_2? z{GowmJ>82J@d+tB<_*a!dskesgns>%Z-_L6>4Bsqg_^2I97Z?w+hzh{IQDZhXT zHKU(>dpmRDGEm(ZTlFr2o z=JzaHxRQpYgYpzC!_}tiU(HN;k&H0$Qm{YtQAJ+NYMHv#G=U~vHF~^yP?}cL2r%(i zXn|6cO`I`(I{mKoobLRx&hj@pWLjxiGrv7C^%Gt6Js4BxwRESuQ;q3Vb8EW0sbzNS z-1OYW44F!&Gp*^SmZ{CvOPX2(;B@t^lqKB@yQY?ZDud1nYlwA*#nnygmg*(V&4Hv!SKmrmGSZ}K z*u$e$ImORL+7yc;E>f*c)rYrwNpn*myiMH7NIO`QxG`uZU5lBrxVj8&sD+oRKK^?Y zlv<5dk9aFfMmkoR>Uk~IOPXu3*wh$E&&KL4kVIdPrfYFklei&hCIxoUzLAe?O=1E^ zU82%eCu~dgl4h2SG{>9LfuV1#r9Bb{QmK~q($&YKA!s3$s>wqQ@ojCWIt4Nffe^M- z-^vIi%|UPzgGZcO!S>R%v{#e3fyfc3Z7S$H6!$t?5^1@%}rfX=FDF{wI+-=Qv8O) znwn}&){;iaK>S*`y(+V)dP#FFuA73c8>_dFu3pkmi|bS}R(sjDCKekFn(6<8+4<+M zR{rOx%BQiF^qlvNRr|B*xqUjuR%H9^l6TjisE&`T=g*Ly+svx{TOKky>SX^<$t3r4 zoIJPtkG=x)^K#r?s>7Op1nCifNcaUGe{aCwApE`LIJ;RUTB*YRt<6#FlT{x~a? zt@fWHJ@TLXt`S&1$B$#DPT^5xaL-?aekNBDI8KJ-b69E7AyD)!P_+Jd+uO>a>_X$5KdXE1gfPWwXc>MMJ z?d3+PaL)LP?DlrOLhCQepMo5Cp?9RI==^z^^vM5!`1jA>!>I{9et-B|%r8aczfO9@ z-wOCg0RH6;emgIgRA_mqD7ydi9Qa8vlCX)ACX-Jb#X*nvQ?#3d7g) zr(++Zn`AcLqok$xyvgCePxN8yf70Q<2;#?HwEv^8$MGu){{+vesp$9}L3$LwEr8E$ zY5uK?F@Go`|7Ox7{t)2T1OBLGm~V}S_5aPKn7lD}=D||Bd4pJ5<81ALBjPe?s(MGtGF9I{)`0J@Vf#{wY1mh5cu#rv0ZDVE;c6zUDE@ zU-s?7{)a`s+kY_C1MQI>`AF`dU$p;@IoN+m^y~d% zs%!t}lOFl+0sc=Xz4m|mT>KKPZZRu1ZYZ-YUoag8}jH&!01a|0g@K z|1k%dz`fsWyhrW7ZxN1vLi9I!N>kDPuO>aWnN`Qt|AX04ko}xLpP;&q|69UWVTx^z z=zlU@|4`F``v-%fpDTR3(f&W^xPICq{{8uXF7W>a;cNes1f+R7{>v9)|5*_}XpPHO zx0fe9iht}8Bj^_$$NxO4>-hgs_}c&L0t(BY4SDQ8ey{=GE_}Pu{@0Nn`R^D1IT5md zel4&4zaxC@zbO2$_2-t0vHwBQ|JoXtt@eKt>5>0&j~c-a(Xsyys%!saW*NTrzj=ZY zP7=M{Fw0*W9R7zy{{jEORM-BeIQ$Qb|D@>IKOGwI|A8eqezEr%A*EME(fp%HkKz}9 z%*fhA&3w944)F8B*YR5@e9hDGTk;v~zb~TyMWjdmJAnTj@c*Rnwg1;6`hVK-`l)=g zjd*e!hH9qhc>LyC-2b>t9C~?QQM7)T^r-(u>3>etJbrVjuJiv-D=`0N;ZLuin`8aE zeouPDk3DXVO3J~^r%S~Ee|aC~4+;M@()o+-f1dP+pA>#V^vv&}hIIemCOscAmj3z6 z;9=(3zlHF3_hEkf4~*W=zl?Tie(G|}AE-0>uH*~Ga2ycTHwTH)*b z&m3WdG7Qp+VR=uevc|!$d%_&$zkZ@yH39w{3JQvUyYN5nhf!UR|D~iy{VxdL-~T+| zKO=nI|Cb{6|0xGQ^Q1BE?>{Y-f&Ooz0Y?2V3I7e#jQ6Pfzk&3q{{zC$iIT^E8P)ap zrzpV8*ZqI>NFxlp{+LA<7|4I@D5Iy#+$Pz=(D0fAVmL@TQI*}^fq5@Gn#+vtyrHEy)2_v9`JuN=~4W5 zO8l*!^Y1!p#rm=P_eo=ZUqt_Vksk45PZ`UW!~7cn|J;d~Ul9Iyuji(sCs&A|Wh#6kHtDEuFW_)pMrh@TXGLG&EI)qubL)0khNxN`Zi*?5mSerrjO z_=Cdt&tFRQK>qwIt+S~AIf^TnUbFEYHUAaTBmOuEoIihPss{M4d;s%@g@3X~HWkgk z)}fCdV~!8gzu%$HioQ97-}^V%f1l{>_}gZ*|D~iy@k>bj{NuNV8qxFr%XFSb;}=gD zqWl@i;sfi?J)}qc9^qU6oWFMh{)Z)h^!avuk^fN>B7dWn8vd0_YA)_lB(DB=r!tu}5 z8{O9`<$?8ME9p`EV?Q!NfBbnZ((yl>I*7(U{vo692;om4J>s_s-#`Bz1pG;k{L2f! z7{Wisk$*Yi_llnLk7KF({|;S$!2X|TgtRcPD^oT!hs}UAN&+-2@;EzgRe*7dO+}CWp zN8SI|=mdlOCq(~oPiZPzpKZeWyy%||(SL;WsQ>Gv|2ss_{r@i2_4vdiKw`p#48Y3oMF%e2Ni5>0I@!u9SONyHLY)SKH&A|LpME*?DBYqz6w*mgs(=k8s zVPib3|4)z}@r!`}Q@}sxbj)uTzAQt=3_a@jpGkVe9|HWJ0sgkrFux#txr}!5w~`+5 zV;pQM{_%f~p6l_yXd31ZMvVW3q(}TD;6D%e&pG%d;ZF=%Kc02)bHXoh5U6ne|AMx< z|EEd*?NvT%&qMs9rmn}E`|2+*I_6J8^!5IOsjl05-Z6hlAbu~BUi<%c8y>%Q;n#)m zzfO8IehIno^XJb?fIt2M%+CvdZ3usF(j$I5;QtEnPdpRz<8)tu3oXwTMaTbm(j$H! z;15$vI{sa!Vt%{u`I_HfG=CQ95r07VIT3UIzCv})e_r@Hf5*^$NiLelEPpxlW7vN} z^kM$vq(}bayx3C7l^@Og`wi8#|J#MH{cn%x{~AaBl|)}haq|})kCl%6%Zq>i^}|l! z|F@3wM?7n&ll*Y1YyQ7D&L2C4pA@-<9ApVk6)Jda|z=goWlN zng0ghUwoY5>-qb2;cFhV{DtrHP}70@85F&4SGRW;y?#OZA<^4-S*F(C_aUqw7QMWT zvGTzDEs`F^ZyaCjQAwIj`TAow)vX^puG;Pk)}ND0=+faO8hp_-&%*{_h9)zdIMlKQH{S{JHOBtnU;3ofYnb`BNl4ir*IT z@6Z1Of&ZOTF+Xvd0f+H_O?t#nK4dadue+9KQI~d z+v#;Bm$3eCB0b_Kwi~^_|AzqnLj z=(DlDD0;aK<~5=9v(Cc$VbNdaDNRM|XObSpKh8Hxs3gs%eEo1F=>KOwf%)xpUCYIe zpLrIXf3ML3kK#8Z`mp)`wyXc5mzQA{A2|Qbp!+N=wasJMWel0idE4u$jI?mr&$-jbV`TQ}7{*9C8 zb+Y}-`>_A`$ISsZ$#Hh0`F|!o%KstZ+x%tz34p(=9rKIAKgfb*tNDK-J>s|hvk@dk z$NYN0U-L=K9}xaNA^aog0-u@uR$;%D=O z<2MDw?++iu{u8vWaG~&46g_^glOFL4fPXUJ-#`h1;@>Cy+bZb6_469iBYwZ|bE4<| zrvN`b0rTUuu5+1fHr}J||9+%L{Bgf95`X@t0sp&)V}76TFY(BxqWJ@)NBoTN{o~&R z_*XmDk741<#&eFcLY9m#_b( z0sd@ikYl9pKa_+&-Xoie=3hcPQ2+D7_s`$c0Dmxz`LS~iZ#$dw70rKy^oYM&_$ASD z{7(n`Cuo2fv6qj*)$Q$OhvqTMUmhhr;`hE_j>^fw%x?pH`lfQoe^vOjJX93TKjL5< zzqp+EuB%W7^Y;+aBmXSb$NwDQf8%D%?~lm8m-L9A0sMBrAJ~NXgThamZg`L5 zyqc+ejr54$F8rJv&;36S@Q*7RzRv%W@INJbyP+m3%7S~b|F#Zun2o;`Y5#LckNgjS z_+1G6f8$G-pA&vq{Q60c_*;eFCdN5_GXekcuVQ{7V*ei|J>tiISs6d(e;V-TQ2x{I zz<1F03qK+L?MBD{Qqm)SJ>XxA`0EUxE+Z?-R^e+Nv$)v(OC9+?6fu5_9r>RH{x1do zfADs9segb;P@vlGQzO=`zYyA{9`Xw>UsVD4DkQPdK~|Z@bz_>_J53H z{^cY3Ka%vwe;x48-^p#)FB_NN-;Lw9IwJoy(j$Hn@UH~?^BwcADEy;B#{WZ(`8OER z|0Ku!YXkmQ0{;_b{^{%I9TE9;q(||~0shs1|MQJ_{7S-4sQB6Ox4XBI9`SpHZ?E4t z|33%#S2@n#@rw;1jKAD*{_Y3;zZUR6FY{loA4%bdtsk%4gX5PIy`3jEUOIj+lODxy z5ct0y`2U{FKkdI)_-9zKY^jNgGNB*)AB^b#0MaA>JAnV2fd6fOhxt1q^0$&6@k@Ze z3h-BLG<+SulJL*0h?Y6lzw1Ot{$^*Hqr%4jct`$@d%052^XC@e|H1V*ejO3{n@Nx2 zmjL|R0Drg4AN~3vFZ{SaSX8(9Z}*Rr^;@rheG&bSCOz_>0sij*{-1mR$8TLk{$r#^ z{C2=!1Nbl8hxr2$`P)g4_&tDsC*Xh1G5@wkE# z|8BtlcgO3uT@m>^9k1U80RJAqU+9>>@k=V_znw;Q{@Ca99rJf9;IBt~Ie+Q(uU`0a z8EtWa>*u`V{Bxb?U+|QsLKZ5@klcUN{EIF%he~H^H=LzI*sHRPiZPz{{@+U zS|7`q<3}rjdK#M{^GEAPcNzW9&BlAw`Y{_#{%ig7qJP>`nu^wMlKj>B^|Q_KM=61N z8k@cW^S?L8=)>aQDEX`Tcg{8XADNB!sQImq{JW^zNKWyTrlR$yIP&jq(SI{Uzrm4z zGv*oL;R>jp#%4P5Z`ORH4~zfBj{IBEWAtZ)_@Cg&zsE%{e}?v2(f)r{#N&V00wa0R zQ<{p_U*X8V#g|3X#~u0CcDd32GK4=$@@KRNN9V6yfd3u9-%s*S^E-r}P;s&T?e4LpNBsD&D&xofe+cjob>wfa z@b&v|sELYlPZ6L0S4WKBU8G0;>w*981OGej#r$GK{&S>9{4C%<4ERq`{&7t7_-%>E z|A8ZaI{^O?z(3THze5rEaYz2<0sk?;zd*)czkb*${QW65{=%{1!ZGLa@@5>rU7}Bv zkJ|H~^M42F(fF+f{s)2ouA<@V^IzQ}!#rRQx?HpV?e3!FpVl8Q`uE!dWvlhCeF^u! zP4sr1vd!rJze;-4|03xBQ=tE=9Qm7z$iLo^zgqzRX~17Y6X#&tIp4N$jd^18hu5!< zQINT>T7Y+ZgqD5e^LNwUtZad4Tyf&zEBr|?*AW2kMd`y^nXw$HphP( c{j++Tm(Q;WzITIBMH#=kW&>v5=t<8 literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.d b/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.d new file mode 100644 index 0000000..361992b --- /dev/null +++ b/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.d @@ -0,0 +1,5 @@ +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc.d: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.189/build.rs + +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/libc-19124a20af635abc/build_script_build-19124a20af635abc: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.189/build.rs + +/home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.189/build.rs: diff --git a/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.1cb4wof2950oslyk39975h0vc.rcgu.o b/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.1cb4wof2950oslyk39975h0vc.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..1c0c0ef0fe09d59df842bb007b868017ee8624d9 GIT binary patch literal 2560 zcmbVNzi-n(6h1eF7N}6D7?8@~1q4HkogYoZfFc0}2~h5rIJ#8fdLF(8%q?0fOWJvS{+vhKal-@EU<^WAy(?!(m?MPXzL zyT#&&P{ux;iv4QnR@nt${*2+!J8Ardd0T#9seRk?+iq7iv`Ses)N3pCT0OtqEH>4= zo?p)C<(yHx4hk2!o-$l#${?#OEzHfofr7JuU*_+qRK#=%c1MD3Brw!*I3`;U{%SnD z1#g!9Iv4|jlVKSliroyON@EPY44az-c$8shQt)$!@YhiEqM%Dk?{TeW4$;}wkWVZ& zuIxz)|1(UnDAY-r`+&>lzZvr5`iYNgEaU$U>xuhJ_1$fS=}mIB0=A2$*KMosQiFr|Sl`c5m%z z&JSwVCeu9IscCl0Y`9+`<}1mC}eMgOZrAA3pae*{d{|8v06ruF{; zII{k~OZ4%tlGMk0O{U)i9BrEZ4dBT1|48)l_D|~Xr>y@7xM*0LXvy`*o{*!9B=BGG zIG4a_J{oa8@T1 SprE8Srs%xT5g&1oegA*LRwPdV literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.build_script_build.3e289dc730a2dac7-cgu.0.rcgu.o b/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.build_script_build.3e289dc730a2dac7-cgu.0.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..d8c6fca24d5545a4c0b2b8161375571a3f44dc01 GIT binary patch literal 199744 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 literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.d b/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.d new file mode 100644 index 0000000..0376636 --- /dev/null +++ b/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.d @@ -0,0 +1,5 @@ +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada.d: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/build.rs + +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/proc-macro2-28cd7e73fe64eada/build_script_build-28cd7e73fe64eada: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/build.rs + +/home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/build.rs: diff --git a/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.build_script_build.290c8a9cd7fd2ff3-cgu.0.rcgu.o b/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.build_script_build.290c8a9cd7fd2ff3-cgu.0.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..23764506bf8edfee3b6ba022c1db113d16a6eb04 GIT binary patch literal 119664 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 AnE(I) literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.cb5aizg7jyp8yonp8exnihyxb.rcgu.o b/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.cb5aizg7jyp8yonp8exnihyxb.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..b225a90f9e9c96c505da96d847437371f4c7f201 GIT binary patch literal 2560 zcmbVNF>ljQ5Poh-TOdNEVnBky3la=5Nt!kZ1BwI`Bv1vFkUAh4$9W+si5=N)NU{(% zMs~!)#Dw}2kPs4ofEhN#gcy(*kjkC?E`D)e64H~bclY_b_uZZS?B}=cZ7paTBh%P* z7Dt3K_TfbAS3Esp~tO0km@@Y6@|4%D+I>5|fGoYle+I!lLqVzE(W zHY@|X%C8e#I&yd&XQ6WJr-;2zW~LLCWT5`MTC0q+IOu-$f>JK#8Oz>VA64>NvX z*-x3_@s?%qhFSNlHWwkL80OA)_l947w)Md4+JjyHu30oqGdxwfvhYl+)pBifz&)4O z*tCoiX4G2a36c0rwF0SmJk*;iv@V2*JFiA<4h`b|r$(OonB7HA9v8E#8&-4hq`24b zl=`mIDe<1uZ1j6oX83^@Sh)7v;W}%W>l;*r6`lwyb{$GH@kWH1v;Mp z6%LO4KGQBQ1z4010oMm&u18he)^uE3=-`HbNGJxHFGFP|Oc<2PM44#~J@MFzn? z5OtpyIObtX_{~5gd})II{fIVpb`eVI4ry(IPD0;cN!G2m#k`hNo)mHsz{ zKK@mb`uJT_>30A}o27phI4b==3Vr| zT5&(}W$Jr5ko+X~Qk35j?T(lS!W)i|$f5oT*1=KDZ&jSXo*-hK_@APDdVjw|K}l^) O$%;aUV#P)E{Qm(1=Or2d literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.d b/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.d new file mode 100644 index 0000000..87edf70 --- /dev/null +++ b/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.d @@ -0,0 +1,5 @@ +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362.d: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/build.rs + +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/quote-6dff9724e4e81362/build_script_build-6dff9724e4e81362: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/build.rs + +/home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/build.rs: diff --git a/src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.93o87lhgng2qq8bftj8op6z8i.rcgu.o b/src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.93o87lhgng2qq8bftj8op6z8i.rcgu.o new file mode 100644 index 0000000000000000000000000000000000000000..609c32961f873552f16fd7d81e8ec02ce1ba68e1 GIT binary patch literal 2560 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 Q$F!nN4f+%9zC@7!e`p0l)c^nh literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.d b/src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.d new file mode 100644 index 0000000..d682a28 --- /dev/null +++ b/src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.d @@ -0,0 +1,5 @@ +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493.d: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.229/build.rs + +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/build/serde_core-e613c711ddfe8493/build_script_build-e613c711ddfe8493: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.229/build.rs + +/home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.229/build.rs: diff --git a/src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rlib b/src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rlib new file mode 100644 index 0000000000000000000000000000000000000000..cd8b166f9b60374c28e76e423689726f61218356 GIT binary patch literal 54846 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 MBM=+Y@k_=3KZ+W?g#Z8m literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rmeta b/src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rmeta new file mode 100644 index 0000000000000000000000000000000000000000..f3ef464d033c7e8181cc76b3a4a230d5999ea523 GIT binary patch literal 32893 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 K6*S{-Q2igsrNU_d literal 0 HcmV?d00001 diff --git a/src-tauri/target/debug/deps/unicode_ident-8443eb632a3fbe4c.d b/src-tauri/target/debug/deps/unicode_ident-8443eb632a3fbe4c.d new file mode 100644 index 0000000..457dbe2 --- /dev/null +++ b/src-tauri/target/debug/deps/unicode_ident-8443eb632a3fbe4c.d @@ -0,0 +1,8 @@ +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/deps/unicode_ident-8443eb632a3fbe4c.d: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/lib.rs /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/tables.rs + +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rlib: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/lib.rs /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/tables.rs + +/home/user/dev/ProxmoxDesktop/src-tauri/target/debug/deps/libunicode_ident-8443eb632a3fbe4c.rmeta: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/lib.rs /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/tables.rs + +/home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/lib.rs: +/home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/tables.rs: diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..cbd28a1 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "ProxmoxDesktop", + "version": "0.1.0", + "identifier": "com.proxmoxdesktop.app", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "title": "ProxmoxDesktop", + "windows": [ + { + "title": "ProxmoxDesktop", + "width": 1200, + "height": 800, + "resizable": true, + "fullscreen": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..e42c3cd --- /dev/null +++ b/src/App.css @@ -0,0 +1 @@ +/* App.css - intentionally empty, using Tailwind CSS */ diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..091232e --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,168 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { Sidebar } from '@/components/layout/Sidebar' +import { Dashboard } from '@/components/layout/Dashboard' +import { ConnectionDialog } from '@/components/connections/ConnectionDialog' +import { VMList } from '@/components/vms/VMList' +import { VMDetail } from '@/components/vms/VMDetail' +import { TaskList } from '@/components/tasks/TaskList' +import { BackupList } from '@/components/backups/BackupList' +import { CommandPalette } from '@/components/command/CommandPalette' +import { StorageOverview } from '@/components/storage/StorageOverview' +import { StorageDetail } from '@/components/storage/StorageDetail' +import { SettingsPage } from '@/components/settings/SettingsPage' +import { ErrorBoundary } from '@/components/ErrorBoundary' +import { ToastProvider } from '@/components/ui/toast' +import { useConnectionStore } from '@/stores/connectionStore' +import { useUIStore } from '@/stores/uiStore' +import { useWebSocket } from '@/hooks/useWebSocket' +import { useEffect, useState } from 'react' +import type { ProxmoxVM } from '@/types/proxmox' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 1, + refetchOnWindowFocus: false, + }, + }, +}) + +type View = + | { type: 'dashboard' } + | { type: 'vms' } + | { type: 'vm-detail'; vm: ProxmoxVM } + | { type: 'tasks' } + | { type: 'backups' } + | { type: 'storage' } + | { type: 'storage-detail'; storage: string } + | { type: 'settings' } + +function AppContent() { + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + const [connectionDialogOpen, setConnectionDialogOpen] = useState(false) + const [view, setView] = useState({ type: 'dashboard' }) + const commandPaletteOpen = useUIStore((s) => s.commandPaletteOpen) + const setCommandPaletteOpen = useUIStore((s) => s.setCommandPaletteOpen) + + // WebSocket integration – connects when a connection is active + useWebSocket(activeConnectionId) + + // Global keyboard shortcut: Cmd/Ctrl+K to open command palette + useEffect(() => { + const handleGlobalKeyDown = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault() + setCommandPaletteOpen(!commandPaletteOpen) + } + } + window.addEventListener('keydown', handleGlobalKeyDown) + return () => window.removeEventListener('keydown', handleGlobalKeyDown) + }, [commandPaletteOpen, setCommandPaletteOpen]) + + const handleNavigate = (newView: View) => { + setView(newView) + } + + const renderMainContent = () => { + if (view.type === 'settings') { + return + } + + if (!activeConnectionId) { + return ( +
+
+

Welcome to ProxmoxDesktop

+

+ Add a Proxmox server to get started +

+ +
+
+ ) + } + + switch (view.type) { + case 'dashboard': + return + case 'vms': + return ( + handleNavigate({ type: 'vm-detail', vm })} + /> + ) + case 'vm-detail': + return ( + handleNavigate({ type: 'vms' })} + /> + ) + case 'tasks': + return + case 'backups': + return + case 'storage': + return ( + + handleNavigate({ type: 'storage-detail', storage }) + } + /> + ) + case 'storage-detail': + return ( + handleNavigate({ type: 'storage' })} + /> + ) + } + } + + return ( +
+ setConnectionDialogOpen(true)} + activeView={view.type} + onNavigate={handleNavigate} + /> +
+ {renderMainContent()} +
+ + setConnectionDialogOpen(true)} + /> +
+ ) +} + +function App() { + return ( + + + + + + + + ) +} + +export default App diff --git a/src/assets/hero.png b/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 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 P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/vite.svg b/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..83c6d87 --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,57 @@ +import { Component } from 'react' +import type { ErrorInfo, ReactNode } from 'react' +import { Button } from '@/components/ui/button' +import { AlertCircle } from 'lucide-react' + +interface Props { + children: ReactNode + fallback?: ReactNode +} + +interface State { + hasError: boolean + error: Error | null +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props) + this.state = { hasError: false, error: null } + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error } + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('ErrorBoundary caught:', error, errorInfo) + } + + render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback + } + + return ( +
+
+ +

Something went wrong

+

+ {this.state.error?.message || 'An unexpected error occurred'} +

+ +
+
+ ) + } + + return this.props.children + } +} diff --git a/src/components/backups/BackupList.tsx b/src/components/backups/BackupList.tsx new file mode 100644 index 0000000..c376747 --- /dev/null +++ b/src/components/backups/BackupList.tsx @@ -0,0 +1,410 @@ +import { useState, useMemo } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { + Shield, + Plus, + Play, + Edit, + Trash, + RotateCcw, + Clock, + HardDrive, + CheckCircle, + XCircle, +} from 'lucide-react' +import { + useBackupJobs, + useBackups, + useDeleteBackupJob, + useRunBackup, + useDeleteBackup, + useStorage, +} from '@/hooks/useProxmox' +import { CreateBackupJobDialog } from './dialogs/CreateBackupJobDialog' +import { EditBackupJobDialog } from './dialogs/EditBackupJobDialog' +import { RestoreBackupDialog } from './dialogs/RestoreBackupDialog' +import type { ProxmoxBackupJob, ProxmoxBackup } from '@/types/proxmox' + +interface BackupListProps { + connectionId: string +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function formatTimestamp(seconds: number): string { + if (seconds === 0) return 'N/A' + const date = new Date(seconds * 1000) + return date.toLocaleString() +} + +export function BackupList({ connectionId }: BackupListProps) { + const { data: backupJobs, isLoading: jobsLoading, error: jobsError } = useBackupJobs(connectionId) + const { data: backups, isLoading: backupsLoading, error: backupsError } = useBackups(connectionId) + const { data: storage } = useStorage(connectionId) + + const deleteBackupJob = useDeleteBackupJob() + const runBackup = useRunBackup() + const deleteBackup = useDeleteBackup() + + const [storageFilter, setStorageFilter] = useState('all') + const [createDialogOpen, setCreateDialogOpen] = useState(false) + const [editDialog, setEditDialog] = useState(null) + const [restoreDialog, setRestoreDialog] = useState(null) + const [confirmDeleteJob, setConfirmDeleteJob] = useState(null) + const [confirmDeleteBackup, setConfirmDeleteBackup] = useState(null) + + const storageOptions = useMemo(() => { + if (!storage) return [] + return [...new Set(storage.map((s) => s.storage))].sort() + }, [storage]) + + const nodeOptions = useMemo(() => { + if (!storage) return [] + return [...new Set(storage.map((s) => s.node))].sort() + }, [storage]) + + const filteredBackups = useMemo(() => { + if (!backups) return [] + if (storageFilter === 'all') return backups + return backups.filter((b) => b.storage === storageFilter) + }, [backups, storageFilter]) + + if (jobsLoading || backupsLoading) { + return ( +
+

Loading backups...

+
+ ) + } + + if (jobsError || backupsError) { + return ( +
+

Failed to load backup data

+
+ ) + } + + return ( +
+
+ {/* Header */} +
+
+ +

Backups

+
+

+ Manage backup jobs and existing backups +

+
+ + {/* Storage Filter */} +
+ +
+ + {/* Backup Jobs Section */} +
+
+

Backup Jobs

+ +
+ + + + {!backupJobs || backupJobs.length === 0 ? ( +
+ +

No backup jobs configured

+

Create a backup job to get started

+
+ ) : ( +
+ + + + + + + + + + + + + {backupJobs.map((job) => ( + + + + + + + + + ))} + +
IDScheduleStorageModeStatusActions
{job.id} +
+ + {job.schedule} +
+
+
+ + {job.store} +
+
+ + {job.mode ?? 'snapshot'} + + + {job.enabled === 1 ? ( + + + Enabled + + ) : ( + + + Disabled + + )} + +
+ + + {confirmDeleteJob === job.id ? ( +
+ + +
+ ) : ( + + )} +
+
+
+ )} +
+
+
+ + {/* Existing Backups Section */} +
+

Existing Backups

+ + + + {!filteredBackups || filteredBackups.length === 0 ? ( +
+ +

No backups found

+
+ ) : ( +
+ + + + + + + + + + + + + {filteredBackups.map((backup) => ( + + + + + + + + + ))} + +
VMIDTypeDateSizeStorageActions
+ {backup['backup-id']} + + + {backup['backup-type']} + + + {formatTimestamp(backup['backup-time'])} + + {formatBytes(backup.size)} + +
+ + {backup.storage} +
+
+
+ + {confirmDeleteBackup === backup.volid ? ( +
+ + +
+ ) : ( + + )} +
+
+
+ )} +
+
+
+
+ + {/* Dialogs */} + + + {editDialog && ( + { + if (!open) setEditDialog(null) + }} + job={editDialog} + storageOptions={storageOptions} + /> + )} + + {restoreDialog && ( + { + if (!open) setRestoreDialog(null) + }} + backup={restoreDialog} + nodeOptions={nodeOptions} + storageOptions={storageOptions} + /> + )} +
+ ) +} diff --git a/src/components/backups/dialogs/CreateBackupJobDialog.tsx b/src/components/backups/dialogs/CreateBackupJobDialog.tsx new file mode 100644 index 0000000..fe5de77 --- /dev/null +++ b/src/components/backups/dialogs/CreateBackupJobDialog.tsx @@ -0,0 +1,198 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useCreateBackupJob } from '@/hooks/useProxmox' +import type { BackupJobConfig } from '@/types/proxmox' + +interface CreateBackupJobDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + storageOptions: string[] +} + +export function CreateBackupJobDialog({ + open, + onOpenChange, + storageOptions, +}: CreateBackupJobDialogProps) { + const [storage, setStorage] = useState(storageOptions[0] ?? '') + const [schedule, setSchedule] = useState('0 2 * * *') + const [mode, setMode] = useState('snapshot') + const [compression, setCompression] = useState('zstd') + const [all, setAll] = useState(true) + const [vmid, setVmid] = useState('') + const [enabled, setEnabled] = useState(true) + + const createBackupJob = useCreateBackupJob() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + const config: BackupJobConfig = { + storage, + schedule, + mode, + compression, + all, + vmid: all ? undefined : vmid, + enabled, + } + createBackupJob.mutate( + { config }, + { + onSuccess: () => { + onOpenChange(false) + resetForm() + }, + }, + ) + } + + const resetForm = () => { + setStorage(storageOptions[0] ?? '') + setSchedule('0 2 * * *') + setMode('snapshot') + setCompression('zstd') + setAll(true) + setVmid('') + setEnabled(true) + } + + return ( + + + + Create Backup Job + + Schedule a recurring backup job for your VMs and containers. + + +
+
+ + +
+ +
+ + setSchedule(e.target.value)} + placeholder="0 2 * * *" + required + /> +

+ Example: "0 2 * * *" = daily at 2:00 AM +

+
+ +
+ + +
+ +
+ + +
+ +
+ setAll(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+ + {!all && ( +
+ + setVmid(e.target.value)} + placeholder="100, 101, 102" + /> +
+ )} + +
+ setEnabled(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+ + {createBackupJob.isError && ( +

+ Failed to create backup job. Please try again. +

+ )} + + + + + +
+
+
+ ) +} diff --git a/src/components/backups/dialogs/EditBackupJobDialog.tsx b/src/components/backups/dialogs/EditBackupJobDialog.tsx new file mode 100644 index 0000000..dc29636 --- /dev/null +++ b/src/components/backups/dialogs/EditBackupJobDialog.tsx @@ -0,0 +1,205 @@ +import { useState, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useUpdateBackupJob } from '@/hooks/useProxmox' +import type { BackupJobConfig, ProxmoxBackupJob } from '@/types/proxmox' + +interface EditBackupJobDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + job: ProxmoxBackupJob + storageOptions: string[] +} + +export function EditBackupJobDialog({ + open, + onOpenChange, + job, + storageOptions, +}: EditBackupJobDialogProps) { + const [storage, setStorage] = useState(job.store) + const [schedule, setSchedule] = useState(job.schedule) + const [mode, setMode] = useState( + (job.mode as BackupJobConfig['mode']) || 'snapshot', + ) + const [compression, setCompression] = useState( + (job.compress as BackupJobConfig['compression']) || 'zstd', + ) + const [all, setAll] = useState(job.all === 1) + const [vmid, setVmid] = useState(job.vmid ?? '') + const [enabled, setEnabled] = useState(job.enabled === 1) + + const updateBackupJob = useUpdateBackupJob() + + useEffect(() => { + if (open) { + setStorage(job.store) + setSchedule(job.schedule) + setMode((job.mode as BackupJobConfig['mode']) || 'snapshot') + setCompression((job.compress as BackupJobConfig['compression']) || 'zstd') + setAll(job.all === 1) + setVmid(job.vmid ?? '') + setEnabled(job.enabled === 1) + } + }, [open, job]) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + const config: BackupJobConfig = { + storage, + schedule, + mode, + compression, + all, + vmid: all ? undefined : vmid, + enabled, + } + updateBackupJob.mutate( + { id: job.id, config }, + { + onSuccess: () => { + onOpenChange(false) + }, + }, + ) + } + + return ( + + + + Edit Backup Job + + Modify the backup job configuration. + + +
+
+ + +
+ +
+ + setSchedule(e.target.value)} + placeholder="0 2 * * *" + required + /> +

+ Example: "0 2 * * *" = daily at 2:00 AM +

+
+ +
+ + +
+ +
+ + +
+ +
+ setAll(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+ + {!all && ( +
+ + setVmid(e.target.value)} + placeholder="100, 101, 102" + /> +
+ )} + +
+ setEnabled(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+ + {updateBackupJob.isError && ( +

+ Failed to update backup job. Please try again. +

+ )} + + + + + +
+
+
+ ) +} diff --git a/src/components/backups/dialogs/RestoreBackupDialog.tsx b/src/components/backups/dialogs/RestoreBackupDialog.tsx new file mode 100644 index 0000000..02c6ad4 --- /dev/null +++ b/src/components/backups/dialogs/RestoreBackupDialog.tsx @@ -0,0 +1,139 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useRestoreBackup } from '@/hooks/useProxmox' +import type { ProxmoxBackup } from '@/types/proxmox' + +interface RestoreBackupDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + backup: ProxmoxBackup | null + nodeOptions: string[] + storageOptions: string[] +} + +export function RestoreBackupDialog({ + open, + onOpenChange, + backup, + nodeOptions, + storageOptions, +}: RestoreBackupDialogProps) { + const [node, setNode] = useState(nodeOptions[0] ?? '') + const [targetStorage, setTargetStorage] = useState(storageOptions[0] ?? '') + const [vmid, setVmid] = useState('') + + const restoreBackup = useRestoreBackup() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!backup) return + restoreBackup.mutate( + { + volid: backup.volid, + config: { + volid: backup.volid, + node, + storage: targetStorage, + vmid: vmid ? parseInt(vmid, 10) : undefined, + }, + }, + { + onSuccess: () => { + onOpenChange(false) + setVmid('') + }, + }, + ) + } + + return ( + + + + Restore Backup + + Restore backup {backup?.volid ?? ''}. This will create a new VM from the backup. + + +
+
+ + +
+ +
+ + +
+ +
+ + setVmid(e.target.value)} + placeholder="Auto-assign if empty" + /> +

+ Leave empty to auto-assign the next available VMID. +

+
+ +

+ Warning: Restoring will overwrite any existing VM with the same VMID. +

+ + {restoreBackup.isError && ( +

+ Failed to restore backup. Please try again. +

+ )} + + + + + +
+
+
+ ) +} diff --git a/src/components/command/CommandPalette.tsx b/src/components/command/CommandPalette.tsx new file mode 100644 index 0000000..063d409 --- /dev/null +++ b/src/components/command/CommandPalette.tsx @@ -0,0 +1,607 @@ +import { useState, useEffect, useRef, useMemo, useCallback } from 'react' +import { useConnectionStore } from '@/stores/connectionStore' +import { useVMs } from '@/hooks/useProxmox' +import { + useStartVM, + useStopVM, + useShutdownVM, + useRebootVM, +} from '@/hooks/useProxmox' +import { Dialog, DialogContent } from '@/components/ui/dialog' +import { ScrollArea } from '@/components/ui/scroll-area' +import { + Server, + Play, + Square, + Power, + RotateCw, + LayoutDashboard, + Box, + ListTodo, + Shield, + HardDrive, + Plus, + X, + Search, +} from 'lucide-react' +import { cn } from '@/lib/utils' +import type { ProxmoxVM } from '@/types/proxmox' + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +type View = + | { type: 'dashboard' } + | { type: 'vms' } + | { type: 'vm-detail'; vm: ProxmoxVM } + | { type: 'tasks' } + | { type: 'backups' } + | { type: 'storage' } + | { type: 'storage-detail'; storage: string } + +type CommandCategory = 'recent' | 'vms' | 'actions' | 'navigation' | 'connections' + +interface CommandItem { + id: string + label: string + description?: string + icon: React.ComponentType<{ className?: string }> + category: CommandCategory + shortcut?: string + keywords: string[] + onExecute: () => void +} + +interface CommandPaletteProps { + open: boolean + onOpenChange: (open: boolean) => void + onNavigate: (view: View) => void + onAddConnection: () => void +} + +// --------------------------------------------------------------------------- +// Recent commands – localStorage persistence +// --------------------------------------------------------------------------- + +const RECENT_STORAGE_KEY = 'proxmox-command-palette-recent' +const MAX_RECENT = 10 + +function loadRecent(): string[] { + try { + const raw = localStorage.getItem(RECENT_STORAGE_KEY) + return raw ? (JSON.parse(raw) as string[]) : [] + } catch { + return [] + } +} + +function saveRecent(ids: string[]) { + localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(ids)) +} + +function pushRecent(id: string) { + const current = loadRecent().filter((r) => r !== id) + current.unshift(id) + saveRecent(current.slice(0, MAX_RECENT)) +} + +function clearRecent() { + localStorage.removeItem(RECENT_STORAGE_KEY) +} + +// --------------------------------------------------------------------------- +// Fuzzy match +// --------------------------------------------------------------------------- + +function fuzzyMatch(query: string, text: string): boolean { + const lowerQuery = query.toLowerCase() + const lowerText = text.toLowerCase() + + // Substring match + if (lowerText.includes(lowerQuery)) return true + + // Character-by-character fuzzy + let qi = 0 + for (let ti = 0; ti < lowerText.length && qi < lowerQuery.length; ti++) { + if (lowerText[ti] === lowerQuery[qi]) qi++ + } + return qi === lowerQuery.length +} + +// --------------------------------------------------------------------------- +// Highlighted text component +// --------------------------------------------------------------------------- + +function HighlightedText({ text, query }: { text: string; query: string }) { + if (!query) return <>{text} + + const lowerText = text.toLowerCase() + const lowerQuery = query.toLowerCase() + const idx = lowerText.indexOf(lowerQuery) + + if (idx !== -1) { + return ( + <> + {text.slice(0, idx)} + + {text.slice(idx, idx + query.length)} + + {text.slice(idx + query.length)} + + ) + } + + // Fuzzy: highlight individual matched characters + const chars = text.split('') + let qi = 0 + return ( + <> + {chars.map((char, i) => { + if (qi < lowerQuery.length && char.toLowerCase() === lowerQuery[qi]) { + qi++ + return ( + + {char} + + ) + } + return char + })} + + ) +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function categoryHeading(category: CommandCategory): string { + switch (category) { + case 'recent': + return 'Recent' + case 'vms': + return 'VMs & Containers' + case 'actions': + return 'Actions' + case 'navigation': + return 'Navigation' + case 'connections': + return 'Connections' + } +} + +// --------------------------------------------------------------------------- +// CommandPalette +// --------------------------------------------------------------------------- + +export function CommandPalette({ + open, + onOpenChange, + onNavigate, + onAddConnection, +}: CommandPaletteProps) { + const [query, setQuery] = useState('') + const [selectedIndex, setSelectedIndex] = useState(0) + const [recentIds, setRecentIds] = useState(loadRecent) + + const inputRef = useRef(null) + const listRef = useRef(null) + + // Data from stores + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + const connections = useConnectionStore((s) => s.connections) + const setActiveConnection = useConnectionStore((s) => s.setActiveConnection) + + const { data: vms = [] } = useVMs(activeConnectionId) + + // VM mutation hooks + const startVM = useStartVM() + const stopVM = useStopVM() + const shutdownVM = useShutdownVM() + const rebootVM = useRebootVM() + + // ----------------------------------------------------------------------- + // Build all command items + // ----------------------------------------------------------------------- + const buildItems = useCallback((): CommandItem[] => { + const items: CommandItem[] = [] + + // -- Navigation -- + items.push( + { + id: 'nav-dashboard', + label: 'Go to Dashboard', + icon: LayoutDashboard, + category: 'navigation', + shortcut: '⌘1', + keywords: ['dashboard', 'home', 'overview'], + onExecute: () => onNavigate({ type: 'dashboard' }), + }, + { + id: 'nav-vms', + label: 'Go to VMs', + icon: Box, + category: 'navigation', + shortcut: '⌘2', + keywords: ['vm', 'vms', 'virtual machines', 'containers'], + onExecute: () => onNavigate({ type: 'vms' }), + }, + { + id: 'nav-tasks', + label: 'Go to Tasks', + icon: ListTodo, + category: 'navigation', + shortcut: '⌘3', + keywords: ['tasks', 'jobs', 'queue'], + onExecute: () => onNavigate({ type: 'tasks' }), + }, + { + id: 'nav-backups', + label: 'Go to Backups', + icon: Shield, + category: 'navigation', + shortcut: '⌘4', + keywords: ['backups', 'restore', 'backup'], + onExecute: () => onNavigate({ type: 'backups' }), + }, + { + id: 'nav-storage', + label: 'Go to Storage', + icon: HardDrive, + category: 'navigation', + shortcut: '⌘5', + keywords: ['storage', 'disks', 'volumes'], + onExecute: () => onNavigate({ type: 'storage' }), + }, + { + id: 'nav-add-connection', + label: 'Add Connection', + icon: Plus, + category: 'navigation', + keywords: ['add', 'connection', 'server', 'proxmox', 'new'], + onExecute: () => onAddConnection(), + }, + ) + + // -- VMs -- + for (const vm of vms) { + items.push({ + id: `vm-detail-${vm.vmid}`, + label: vm.name, + description: `${vm.type.toUpperCase()} · VMID ${vm.vmid} · ${vm.node} · ${vm.status}`, + icon: Server, + category: 'vms', + keywords: [vm.name, String(vm.vmid), vm.node, vm.type, vm.status], + onExecute: () => onNavigate({ type: 'vm-detail', vm }), + }) + } + + // -- VM Actions -- + for (const vm of vms) { + if (vm.status === 'running') { + items.push( + { + id: `action-stop-${vm.vmid}`, + label: `Stop ${vm.name}`, + description: `Force stop ${vm.type.toUpperCase()} VMID ${vm.vmid}`, + icon: Square, + category: 'actions', + keywords: ['stop', 'halt', 'power off', vm.name, String(vm.vmid)], + onExecute: () => stopVM.mutate({ node: vm.node, vmid: vm.vmid }), + }, + { + id: `action-shutdown-${vm.vmid}`, + label: `Shutdown ${vm.name}`, + description: `Gracefully shutdown ${vm.type.toUpperCase()} VMID ${vm.vmid}`, + icon: Power, + category: 'actions', + keywords: ['shutdown', 'graceful', 'power', vm.name, String(vm.vmid)], + onExecute: () => shutdownVM.mutate({ node: vm.node, vmid: vm.vmid }), + }, + { + id: `action-reboot-${vm.vmid}`, + label: `Reboot ${vm.name}`, + description: `Reboot ${vm.type.toUpperCase()} VMID ${vm.vmid}`, + icon: RotateCw, + category: 'actions', + keywords: ['reboot', 'restart', vm.name, String(vm.vmid)], + onExecute: () => rebootVM.mutate({ node: vm.node, vmid: vm.vmid }), + }, + ) + } + if (vm.status === 'stopped' || vm.status === 'paused') { + items.push({ + id: `action-start-${vm.vmid}`, + label: `Start ${vm.name}`, + description: `Start ${vm.type.toUpperCase()} VMID ${vm.vmid}`, + icon: Play, + category: 'actions', + keywords: ['start', 'boot', 'power on', vm.name, String(vm.vmid)], + onExecute: () => startVM.mutate({ node: vm.node, vmid: vm.vmid }), + }) + } + } + + // -- Connections -- + for (const conn of connections) { + items.push({ + id: `conn-${conn.id}`, + label: conn.name, + description: `Switch to ${conn.name} (${conn.status})`, + icon: Server, + category: 'connections', + keywords: [conn.name, conn.status, 'switch', 'connection'], + onExecute: () => setActiveConnection(conn.id), + }) + } + + return items + }, [ + vms, + connections, + onNavigate, + onAddConnection, + startVM, + stopVM, + shutdownVM, + rebootVM, + setActiveConnection, + ]) + + const allItems = useMemo(() => buildItems(), [buildItems]) + + // ----------------------------------------------------------------------- + // Filter & group + // ----------------------------------------------------------------------- + const filteredItems = useMemo(() => { + const q = query.trim() + + if (!q) { + // Empty query: recent → navigation → everything else + const recentItems = recentIds + .map((id) => allItems.find((item) => item.id === id)) + .filter((item): item is CommandItem => item !== undefined) + .map((item) => ({ ...item, category: 'recent' as const })) + + const navItems = allItems.filter((item) => item.category === 'navigation') + const otherItems = allItems.filter( + (item) => item.category !== 'navigation' && item.category !== 'recent', + ) + + return [...recentItems, ...navItems, ...otherItems] + } + + return allItems.filter((item) => { + const haystack = [item.label, item.description ?? '', ...item.keywords].join(' ') + return fuzzyMatch(q, haystack) + }) + }, [query, allItems, recentIds]) + + const groupedItems = useMemo(() => { + const groups: { heading: string; items: CommandItem[] }[] = [] + + if (query.trim()) { + const buckets: Record = {} + for (const item of filteredItems) { + const heading = item.category === 'recent' ? 'Recent' : categoryHeading(item.category) + if (!buckets[heading]) buckets[heading] = [] + buckets[heading].push(item) + } + for (const [heading, items] of Object.entries(buckets)) { + groups.push({ heading, items }) + } + } else { + let currentHeading = '' + let currentItems: CommandItem[] = [] + for (const item of filteredItems) { + const heading = item.category === 'recent' ? 'Recent' : categoryHeading(item.category) + if (heading !== currentHeading) { + if (currentItems.length > 0) groups.push({ heading: currentHeading, items: currentItems }) + currentHeading = heading + currentItems = [item] + } else { + currentItems.push(item) + } + } + if (currentItems.length > 0) groups.push({ heading: currentHeading, items: currentItems }) + } + + return groups + }, [filteredItems, query]) + + const flatItems = useMemo(() => groupedItems.flatMap((g) => g.items), [groupedItems]) + + // ----------------------------------------------------------------------- + // Reset on open + // ----------------------------------------------------------------------- + useEffect(() => { + if (open) { + setQuery('') + setSelectedIndex(0) + setRecentIds(loadRecent()) + requestAnimationFrame(() => inputRef.current?.focus()) + } + }, [open]) + + // ----------------------------------------------------------------------- + // Reset selection on query change + // ----------------------------------------------------------------------- + useEffect(() => { + setSelectedIndex(0) + }, [query]) + + // ----------------------------------------------------------------------- + // Keep selected item in view + // ----------------------------------------------------------------------- + useEffect(() => { + const el = listRef.current?.querySelector(`[data-cmd-idx="${selectedIndex}"]`) + el?.scrollIntoView({ block: 'nearest' }) + }, [selectedIndex]) + + // ----------------------------------------------------------------------- + // Execute item + // ----------------------------------------------------------------------- + const executeItem = useCallback( + (item: CommandItem) => { + pushRecent(item.id) + setRecentIds(loadRecent()) + item.onExecute() + onOpenChange(false) + }, + [onOpenChange], + ) + + // ----------------------------------------------------------------------- + // Keyboard handling + // ----------------------------------------------------------------------- + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case 'ArrowDown': + e.preventDefault() + setSelectedIndex((i) => Math.min(i + 1, flatItems.length - 1)) + break + case 'ArrowUp': + e.preventDefault() + setSelectedIndex((i) => Math.max(i - 1, 0)) + break + case 'Enter': + e.preventDefault() + if (flatItems[selectedIndex]) executeItem(flatItems[selectedIndex]) + break + case 'Escape': + e.preventDefault() + onOpenChange(false) + break + } + }, + [flatItems, selectedIndex, executeItem, onOpenChange], + ) + + // ----------------------------------------------------------------------- + // Flat index helper + // ----------------------------------------------------------------------- + function flatIndex(groupIdx: number, itemIdx: number): number { + let offset = 0 + for (let g = 0; g < groupIdx; g++) offset += groupedItems[g].items.length + return offset + itemIdx + } + + // ----------------------------------------------------------------------- + // Render + // ----------------------------------------------------------------------- + return ( + + + {/* Search bar */} +
+ + setQuery(e.target.value)} + placeholder="Type a command or search..." + className="flex h-11 w-full rounded-md bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground" + /> + {query && ( + + )} +
+ + {/* Results list */} + +
+ {flatItems.length === 0 ? ( +
+ No results found. +
+ ) : ( + groupedItems.map((group, gIdx) => ( +
+
+ {group.heading} +
+ {group.items.map((item, iIdx) => { + const idx = flatIndex(gIdx, iIdx) + const isActive = idx === selectedIndex + const Icon = item.icon + return ( + + ) + })} +
+ )) + )} +
+
+ + {/* Footer with keyboard hints */} +
+
+ + + + navigate + + + + select + + + esc + close + +
+ {recentIds.length > 0 && ( + + )} +
+
+
+ ) +} diff --git a/src/components/connections/ConnectionDialog.tsx b/src/components/connections/ConnectionDialog.tsx new file mode 100644 index 0000000..cc1b0c3 --- /dev/null +++ b/src/components/connections/ConnectionDialog.tsx @@ -0,0 +1,132 @@ +import { useState } from 'react' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { useConnectionStore } from '@/stores/connectionStore' +import type { ConnectionConfig } from '@/types/connection' + +interface ConnectionDialogProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +export function ConnectionDialog({ open, onOpenChange }: ConnectionDialogProps) { + const addConnection = useConnectionStore((s) => s.addConnection) + const [name, setName] = useState('') + const [url, setUrl] = useState('') + const [apiToken, setApiToken] = useState('') + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + try { + // Validate URL + if (!url.startsWith('https://')) { + throw new Error('URL must start with https://') + } + + // Create connection config + const config: ConnectionConfig = { + id: crypto.randomUUID(), + name: name || 'New Connection', + primary: { + url: url.replace(/\/$/, ''), // Remove trailing slash + token: apiToken, + }, + fallbacks: [], + trusted: false, + status: 'disconnected', + isCluster: false, + } + + // Add connection to store + addConnection(config) + + // Reset form and close dialog + setName('') + setUrl('') + setApiToken('') + onOpenChange(false) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to add connection') + } finally { + setIsLoading(false) + } + } + + return ( + + + + Add Proxmox Connection + + Connect to a Proxmox VE server or cluster + + +
+
+ + setName(e.target.value)} + /> +
+
+ + setUrl(e.target.value)} + required + /> +

+ The URL of your Proxmox server (must use HTTPS) +

+
+
+ + setApiToken(e.target.value)} + required + /> +

+ Format: user@realm!tokenid=secret +

+
+ {error && ( +
+

{error}

+
+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/console/TerminalConsole.tsx b/src/components/console/TerminalConsole.tsx new file mode 100644 index 0000000..864ec81 --- /dev/null +++ b/src/components/console/TerminalConsole.tsx @@ -0,0 +1,255 @@ +import { useEffect, useRef, useCallback } from 'react' +import { Terminal } from 'xterm' +import { FitAddon } from 'xterm-addon-fit' +import 'xterm/css/xterm.css' + +interface TerminalConsoleProps { + connectionId: string + node: string + vmid: number + onError?: (message: string) => void +} + +export function TerminalConsole({ connectionId, node, vmid, onError }: TerminalConsoleProps) { + const containerRef = useRef(null) + const termRef = useRef(null) + const fitAddonRef = useRef(null) + const wsRef = useRef(null) + + const cleanup = useCallback(() => { + if (wsRef.current) { + wsRef.current.close() + wsRef.current = null + } + if (termRef.current) { + termRef.current.dispose() + termRef.current = null + } + fitAddonRef.current = null + }, []) + + useEffect(() => { + if (!containerRef.current) return + + let cancelled = false + + const connect = async () => { + try { + // Create terminal + const terminal = new Terminal({ + cursorBlink: true, + fontSize: 14, + fontFamily: 'Menlo, Monaco, "Courier New", monospace', + theme: { + background: '#000000', + foreground: '#ffffff', + cursor: '#ffffff', + selectionBackground: '#264f78', + }, + allowProposedApi: true, + }) + + const fitAddon = new FitAddon() + terminal.loadAddon(fitAddon) + + terminal.open(containerRef.current!) + fitAddon.fit() + + termRef.current = terminal + fitAddonRef.current = fitAddon + + if (cancelled) { + terminal.dispose() + return + } + + // Get WebSocket URL for terminal proxy + let wsUrl: string + + try { + const { isTauri, createTermProxy, getWebSocketURL } = await import('@/lib/tauri') + + if (isTauri()) { + const [proxyInfo, baseUrl] = await Promise.all([ + createTermProxy(connectionId, node, vmid), + getWebSocketURL(connectionId, node), + ]) + + wsUrl = `${baseUrl}/api2/json/nodes/${node}/lxc/${vmid}/proxy?port=${proxyInfo.port}&ticket=${encodeURIComponent(proxyInfo.ticket)}` + } else { + // Dev mode: construct a mock URL + wsUrl = `wss://localhost:8006/api2/json/nodes/${node}/lxc/${vmid}/proxy?port=6100&ticket=mock-ticket` + } + } catch { + // Fallback for dev mode + wsUrl = `wss://localhost:8006/api2/json/nodes/${node}/lxc/${vmid}/proxy?port=6100&ticket=mock-ticket` + } + + if (cancelled) return + + // In dev mode, show a mock terminal since we can't connect to real WebSocket + let isTauriMode = false + try { + const { isTauri } = await import('@/lib/tauri') + isTauriMode = isTauri() + } catch { + // not in tauri + } + + if (!isTauriMode && !cancelled) { + // Dev mode mock terminal + terminal.writeln('\x1b[1;32m╔══════════════════════════════════════════╗\x1b[0m') + terminal.writeln('\x1b[1;32m║ ProxmoxDesktop - Terminal Console ║\x1b[0m') + terminal.writeln('\x1b[1;32m╚══════════════════════════════════════════╝\x1b[0m') + terminal.writeln('') + terminal.writeln(`\x1b[33mNode: ${node} | VMID: ${vmid} | Type: LXC\x1b[0m`) + terminal.writeln('') + terminal.writeln('\x1b[90m[Dev mode - WebSocket connection mocked]\x1b[0m') + terminal.writeln('') + + // Mock shell interaction + const prompt = () => { + terminal.write(`\x1b[1;34mroot@${node}\x1b[0m:\x1b[1;35m~\x1b[0m# `) + } + prompt() + + let inputBuffer = '' + terminal.onData((data: string) => { + if (data === '\r') { + terminal.writeln('') + if (inputBuffer.trim()) { + if (inputBuffer.trim() === 'clear') { + terminal.clear() + } else if (inputBuffer.trim() === 'help') { + terminal.writeln('Available mock commands: clear, help, ls, whoami') + } else if (inputBuffer.trim() === 'ls') { + terminal.writeln('bin boot dev etc home lib lib64 media mnt opt') + terminal.writeln('proc root run sbin srv sys tmp usr var') + } else if (inputBuffer.trim() === 'whoami') { + terminal.writeln('root') + } else { + terminal.writeln(`\x1b[31m${inputBuffer.trim()}: command not found\x1b[0m`) + } + } + inputBuffer = '' + prompt() + } else if (data === '\x7f') { + // Backspace + if (inputBuffer.length > 0) { + inputBuffer = inputBuffer.slice(0, -1) + terminal.write('\b \b') + } + } else if (data >= ' ') { + inputBuffer += data + terminal.write(data) + } + }) + + // Handle resize + const resizeObserver = new ResizeObserver(() => { + if (!cancelled && fitAddonRef.current) { + try { + fitAddonRef.current.fit() + } catch { + // ignore resize errors + } + } + }) + resizeObserver.observe(containerRef.current!) + + return () => { + resizeObserver.disconnect() + } + } + + if (cancelled) return + + // Production: connect via WebSocket + const ws = new WebSocket(wsUrl) + ws.binaryType = 'arraybuffer' + wsRef.current = ws + + ws.onopen = () => { + if (!cancelled) { + terminal.writeln(`\x1b[32mConnected to LXC container ${vmid} on ${node}\x1b[0m`) + } + } + + ws.onmessage = (ev: MessageEvent) => { + if (!cancelled && typeof ev.data === 'string') { + terminal.write(ev.data) + } + } + + ws.onerror = () => { + if (!cancelled) { + onError?.('WebSocket connection error') + } + } + + ws.onclose = () => { + if (!cancelled) { + terminal.writeln('\r\n\x1b[33mConnection closed\x1b[0m') + } + } + + // Forward terminal input to WebSocket + terminal.onData((data: string) => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(data) + } + }) + + // Handle terminal resize + terminal.onResize(({ cols, rows }: { cols: number; rows: number }) => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'resize', cols, rows })) + } + }) + + // Handle container resize + const resizeObserver = new ResizeObserver(() => { + if (!cancelled && fitAddonRef.current) { + try { + fitAddonRef.current.fit() + } catch { + // ignore resize errors + } + } + }) + resizeObserver.observe(containerRef.current!) + + return () => { + resizeObserver.disconnect() + } + } catch (err) { + if (!cancelled) { + onError?.(err instanceof Error ? err.message : 'Failed to create terminal') + } + } + } + + let cleanupResize: (() => void) | undefined + + const run = async () => { + cleanupResize = await connect() ?? undefined + } + + run() + + return () => { + cancelled = true + cleanupResize?.() + cleanup() + } + }, [connectionId, node, vmid, onError, cleanup]) + + return ( +
+ ) +} + +export type { TerminalConsoleProps } diff --git a/src/components/console/VNCConsole.tsx b/src/components/console/VNCConsole.tsx new file mode 100644 index 0000000..2453896 --- /dev/null +++ b/src/components/console/VNCConsole.tsx @@ -0,0 +1,117 @@ +import { useEffect, useRef, useCallback } from 'react' + +interface VNCConsoleProps { + connectionId: string + node: string + vmid: number + onError?: (message: string) => void +} + +type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'error' + +export function VNCConsole({ connectionId, node, vmid, onError }: VNCConsoleProps) { + const containerRef = useRef(null) + const rfbRef = useRef(null) + const stateRef = useRef('connecting') + + const cleanup = useCallback(() => { + if (rfbRef.current) { + const rfb = rfbRef.current as { disconnect: () => void } + rfb.disconnect() + rfbRef.current = null + } + }, []) + + useEffect(() => { + if (!containerRef.current) return + + let cancelled = false + + const connect = async () => { + try { + stateRef.current = 'connecting' + + // Get WebSocket URL for VNC + let wsUrl: string + + try { + // Try real IPC first + const { isTauri, createVNCProxy, getWebSocketURL } = await import('@/lib/tauri') + + if (isTauri()) { + const [proxyInfo, baseUrl] = await Promise.all([ + createVNCProxy(connectionId, node, vmid), + getWebSocketURL(connectionId, node), + ]) + + wsUrl = `${baseUrl}/api2/json/nodes/${node}/qemu/${vmid}/vncwebsocket?port=${proxyInfo.port}&vncticket=${encodeURIComponent(proxyInfo.ticket)}` + } else { + // Dev mode: construct a mock URL + wsUrl = `wss://localhost:8006/api2/json/nodes/${node}/qemu/${vmid}/vncwebsocket?port=6000&vncticket=mock-ticket` + } + } catch { + // Fallback for dev mode + wsUrl = `wss://localhost:8006/api2/json/nodes/${node}/qemu/${vmid}/vncwebsocket?port=6000&vncticket=mock-ticket` + } + + if (cancelled) return + + // Dynamically import noVNC RFB class + const { default: RFB } = await import('@novnc/novnc/core/rfb.js') + + if (cancelled || !containerRef.current) return + + // Create RFB connection + const rfb = new RFB(containerRef.current, wsUrl, { + shared: true, + wsProtocols: ['binary'], + }) + + rfbRef.current = rfb + + rfb.addEventListener('connect', () => { + if (!cancelled) { + stateRef.current = 'connected' + } + }) + + rfb.addEventListener('disconnect', (ev: unknown) => { + if (!cancelled) { + const detail = (ev as CustomEvent<{ reason?: string }>).detail + stateRef.current = 'disconnected' + if (detail?.reason) { + onError?.(`VNC disconnected: ${detail.reason}`) + } + } + }) + + rfb.addEventListener('credentialsrequired', () => { + if (!cancelled) { + onError?.('VNC credentials required') + } + }) + } catch (err) { + if (!cancelled) { + stateRef.current = 'error' + onError?.(err instanceof Error ? err.message : 'Failed to connect VNC') + } + } + } + + connect() + + return () => { + cancelled = true + cleanup() + } + }, [connectionId, node, vmid, onError, cleanup]) + + return ( +
+ ) +} + +export type { VNCConsoleProps } diff --git a/src/components/dashboard/ActivityFeed.tsx b/src/components/dashboard/ActivityFeed.tsx new file mode 100644 index 0000000..29d91ea --- /dev/null +++ b/src/components/dashboard/ActivityFeed.tsx @@ -0,0 +1,133 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { ScrollArea } from '@/components/ui/scroll-area' +import { Activity, Clock } from 'lucide-react' +import type { ProxmoxTask } from '@/types/proxmox' + +interface ActivityFeedProps { + tasks: ProxmoxTask[] | undefined + isLoading: boolean +} + +function getStatusBadgeClass(status?: string): string { + switch (status) { + case 'OK': + return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' + case 'unknown': + return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400' + default: + if (!status || status === 'Running') { + return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400' + } + return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400' + } +} + +function getStatusLabel(status?: string): string { + if (!status || status === 'Running') return 'Running' + return status +} + +function formatDuration(starttime: number, endtime?: number): string { + const end = endtime ?? Math.floor(Date.now() / 1000) + const durationSeconds = Math.max(0, end - starttime) + if (durationSeconds < 60) return `${durationSeconds}s` + const minutes = Math.floor(durationSeconds / 60) + const seconds = durationSeconds % 60 + if (minutes < 60) return `${minutes}m ${seconds}s` + const hours = Math.floor(minutes / 60) + const remainingMinutes = minutes % 60 + return `${hours}h ${remainingMinutes}m` +} + +function formatTime(timestamp: number): string { + return new Date(timestamp * 1000).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + }) +} + +function getTaskTypeLabel(type: string): string { + // Capitalize and format Proxmox task types + return type + .replace(/qm/gi, 'VM') + .replace(/vz/gi, 'CT') + .replace(/storage/gi, 'Storage') + .replace(/backup/gi, 'Backup') + .replace(/restore/gi, 'Restore') + .replace(/snapshot/gi, 'Snapshot') + .split(/[\s/]/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' ') +} + +export function ActivityFeed({ tasks, isLoading }: ActivityFeedProps) { + if (isLoading) { + return ( + + +
+ + Recent Activity +
+
+ +

Loading tasks...

+
+
+ ) + } + + const recentTasks = tasks?.slice(0, 15) ?? [] + + return ( + + +
+ + Recent Activity +
+
+ + {recentTasks.length > 0 ? ( + +
+ {recentTasks.map((task) => ( +
+
+
+
+

+ {getTaskTypeLabel(task.type)} +

+ + {getStatusLabel(task.status)} + +
+

+ {task.node} · {task.user} +

+
+
+
+
+ + {formatDuration(task.starttime, task.endtime)} +
+ {formatTime(task.starttime)} +
+
+ ))} +
+
+ ) : ( +

No recent activity

+ )} +
+
+ ) +} diff --git a/src/components/dashboard/NodeHealthGrid.tsx b/src/components/dashboard/NodeHealthGrid.tsx new file mode 100644 index 0000000..c38711f --- /dev/null +++ b/src/components/dashboard/NodeHealthGrid.tsx @@ -0,0 +1,192 @@ +import { useMemo } from 'react' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Cpu, MemoryStick, HardDrive, Clock, Server, Box } from 'lucide-react' +import { AreaChart, Area, ResponsiveContainer } from 'recharts' +import type { ProxmoxNode, ProxmoxVM } from '@/types/proxmox' + +interface NodeHealthGridProps { + nodes: ProxmoxNode[] | undefined + vms: ProxmoxVM[] | undefined +} + +function formatUptime(seconds: number): string { + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + if (days > 0) return `${days}d ${hours}h` + return `${hours}h` +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function getStatusColor(status: 'online' | 'offline'): string { + return status === 'online' ? 'bg-green-500' : 'bg-red-500' +} + +function getStatusLabel(status: 'online' | 'offline'): string { + return status === 'online' ? 'Online' : 'Offline' +} + +/** Generate deterministic mock sparkline data from node stats */ +function generateSparklineData(baseValue: number, points: number = 12): { value: number }[] { + const data: { value: number }[] = [] + for (let i = 0; i < points; i++) { + // Deterministic pseudo-random variation around the base value + const seed = Math.sin(i * 2.1 + baseValue * 0.01) * 0.3 + const variation = baseValue * seed + data.push({ value: Math.max(0, Math.min(1, baseValue + variation)) }) + } + return data +} + +function NodeCard({ node, vmCount }: { node: ProxmoxNode; vmCount: number }) { + const cpuPercent = node.maxcpu > 0 ? node.cpu : 0 + const memPercent = node.maxmem > 0 ? node.mem / node.maxmem : 0 + const diskPercent = node.maxdisk > 0 ? node.disk / node.maxdisk : 0 + + const cpuData = useMemo(() => generateSparklineData(cpuPercent), [cpuPercent]) + const memData = useMemo(() => generateSparklineData(memPercent), [memPercent]) + + return ( + + +
+
+ {node.node} +
+ {getStatusLabel(node.status)} + + + {/* Sparklines */} +
+
+
+ + CPU +
+
+ + + + + +
+
+
+
+ + RAM +
+
+ + + + + +
+
+
+ + {/* Stats row */} +
+
+ + {(cpuPercent * 100).toFixed(0)}% +
+
+ + {(memPercent * 100).toFixed(0)}% +
+
+ + {(diskPercent * 100).toFixed(0)}% +
+
+ + {/* Bottom info */} +
+
+ + {formatUptime(node.uptime)} +
+
+ + {vmCount} VMs +
+
+ + {formatBytes(node.disk)} +
+
+
+ + ) +} + +export function NodeHealthGrid({ nodes, vms }: NodeHealthGridProps) { + // Count VMs per node + const vmCounts = useMemo(() => { + const counts: Record = {} + vms?.forEach((vm) => { + counts[vm.node] = (counts[vm.node] ?? 0) + 1 + }) + return counts + }, [vms]) + + if (!nodes || nodes.length === 0) { + return ( + + +
+ + Node Health +
+
+ +

No nodes available

+
+
+ ) + } + + return ( + + +
+ + Node Health +
+
+ +
+ {nodes.map((node) => ( + + ))} +
+
+
+ ) +} diff --git a/src/components/dashboard/QuickActions.tsx b/src/components/dashboard/QuickActions.tsx new file mode 100644 index 0000000..0b2c21a --- /dev/null +++ b/src/components/dashboard/QuickActions.tsx @@ -0,0 +1,66 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { RefreshCw, Server, Box, HardDrive, ListTodo, Shield } from 'lucide-react' + +interface QuickActionsProps { + onRefresh: () => void + isRefreshing: boolean +} + +export function QuickActions({ onRefresh, isRefreshing }: QuickActionsProps) { + return ( + + + Quick Actions + + +
+ + + + + + +
+
+
+ ) +} diff --git a/src/components/dashboard/ResourceGauge.tsx b/src/components/dashboard/ResourceGauge.tsx new file mode 100644 index 0000000..f665a55 --- /dev/null +++ b/src/components/dashboard/ResourceGauge.tsx @@ -0,0 +1,106 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Cpu, MemoryStick, HardDrive } from 'lucide-react' + +interface ResourceGaugeProps { + label: string + used: number + total: number + icon: 'cpu' | 'memory' | 'disk' + formatValue?: (value: number) => string +} + +const iconMap = { + cpu: Cpu, + memory: MemoryStick, + disk: HardDrive, +} as const + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function formatCores(cores: number): string { + return `${cores.toFixed(1)} cores` +} + +function getGaugeColor(percent: number): string { + if (percent >= 0.9) return 'stroke-destructive' + if (percent >= 0.7) return 'stroke-yellow-500' + return 'stroke-primary' +} + +function getTrackColor(): string { + return 'stroke-muted' +} + +export function ResourceGauge({ label, used, total, icon, formatValue }: ResourceGaugeProps) { + const percent = total > 0 ? used / total : 0 + const clampedPercent = Math.min(Math.max(percent, 0), 1) + const displayPercent = (clampedPercent * 100).toFixed(1) + + const Icon = iconMap[icon] + + // SVG circle parameters + const radius = 40 + const strokeWidth = 8 + const circumference = 2 * Math.PI * radius + const strokeDashoffset = circumference * (1 - clampedPercent) + + const defaultFormat = + icon === 'cpu' + ? formatCores + : formatBytes + + const formatter = formatValue ?? defaultFormat + + return ( + + + {label} + + + +
+ + {/* Background track */} + + {/* Progress arc */} + + +
+ {displayPercent}% +
+
+
+ {formatter(used)} / {formatter(total)} +
+
+
+ ) +} diff --git a/src/components/layout/Dashboard.tsx b/src/components/layout/Dashboard.tsx new file mode 100644 index 0000000..5a368f3 --- /dev/null +++ b/src/components/layout/Dashboard.tsx @@ -0,0 +1,161 @@ +import { useCallback } from 'react' +import { useQueryClient } from '@tanstack/react-query' +import { useNodes, useVMs, useTasks, queryKeys } from '@/hooks/useProxmox' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Server, Cpu, HardDrive, MemoryStick } from 'lucide-react' +import { ResourceGauge } from '@/components/dashboard/ResourceGauge' +import { NodeHealthGrid } from '@/components/dashboard/NodeHealthGrid' +import { ActivityFeed } from '@/components/dashboard/ActivityFeed' +import { QuickActions } from '@/components/dashboard/QuickActions' + +interface DashboardProps { + connectionId: string +} + +export function Dashboard({ connectionId }: DashboardProps) { + const queryClient = useQueryClient() + const { data: nodes, isLoading: nodesLoading } = useNodes(connectionId) + const { data: vms, isLoading: vmsLoading } = useVMs(connectionId) + const { data: tasks, isLoading: tasksLoading } = useTasks(connectionId) + + const totalCPU = nodes?.reduce((acc, n) => acc + n.maxcpu, 0) ?? 0 + const usedCPU = nodes?.reduce((acc, n) => acc + n.cpu * n.maxcpu, 0) ?? 0 + const totalMem = nodes?.reduce((acc, n) => acc + n.maxmem, 0) ?? 0 + const usedMem = nodes?.reduce((acc, n) => acc + n.mem, 0) ?? 0 + const totalDisk = nodes?.reduce((acc, n) => acc + n.maxdisk, 0) ?? 0 + const usedDisk = nodes?.reduce((acc, n) => acc + n.disk, 0) ?? 0 + + const formatBytes = (bytes: number) => { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` + } + + const formatPercent = (value: number) => `${(value * 100).toFixed(1)}%` + + const handleRefresh = useCallback(() => { + queryClient.invalidateQueries({ queryKey: queryKeys.nodes(connectionId) }) + queryClient.invalidateQueries({ queryKey: queryKeys.vms(connectionId) }) + queryClient.invalidateQueries({ queryKey: queryKeys.tasks(connectionId) }) + }, [queryClient, connectionId]) + + if (nodesLoading || vmsLoading) { + return ( +
+

Loading...

+
+ ) + } + + return ( +
+
+
+

Dashboard

+

Cluster overview and resource usage

+
+ + {/* Summary Stats Row */} +
+ + + Nodes + + + +
+ {nodes?.filter((n) => n.status === 'online').length ?? 0} / {nodes?.length ?? 0} +
+

Online

+
+
+ + + + CPU Usage + + + +
+ {totalCPU > 0 ? formatPercent(usedCPU / totalCPU) : '0%'} +
+

+ {usedCPU.toFixed(1)} / {totalCPU} cores +

+
+
+ + + + Memory + + + +
+ {totalMem > 0 ? formatPercent(usedMem / totalMem) : '0%'} +
+

+ {formatBytes(usedMem)} / {formatBytes(totalMem)} +

+
+
+ + + + Storage + + + +
+ {totalDisk > 0 ? formatPercent(usedDisk / totalDisk) : '0%'} +
+

+ {formatBytes(usedDisk)} / {formatBytes(totalDisk)} +

+
+
+
+ + {/* Resource Gauges */} +
+ `${v.toFixed(1)} cores`} + /> + + +
+ + {/* Node Health Grid */} + + + {/* Activity Feed + Quick Actions */} +
+
+ +
+
+ +
+
+
+
+ ) +} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..edc97af --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,125 @@ +import { useConnectionStore } from '@/stores/connectionStore' +import { ScrollArea } from '@/components/ui/scroll-area' +import { Button } from '@/components/ui/button' +import { Plus, Server, LayoutDashboard, HardDrive, Box, ListTodo, Shield, Settings } from 'lucide-react' +import { cn } from '@/lib/utils' + +type ViewType = 'dashboard' | 'vms' | 'vm-detail' | 'tasks' | 'backups' | 'storage' | 'storage-detail' | 'settings' + +interface SidebarProps { + onAddConnection: () => void + activeView?: ViewType + onNavigate?: (view: { type: ViewType }) => void +} + +export function Sidebar({ onAddConnection, activeView, onNavigate }: SidebarProps) { + const connections = useConnectionStore((s) => s.connections) + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + const setActiveConnection = useConnectionStore((s) => s.setActiveConnection) + + const getStatusColor = (status: string) => { + switch (status) { + case 'connected': + return 'bg-green-500' + case 'connecting': + case 'failover': + return 'bg-yellow-500' + case 'failed': + return 'bg-red-500' + default: + return 'bg-gray-500' + } + } + + return ( +
+
+

ProxmoxDesktop

+
+ +
+ {connections.map((connection) => ( +
+ + {activeConnectionId === connection.id && ( +
+ onNavigate?.({ type: 'dashboard' })} + /> + + onNavigate?.({ type: 'vms' })} + /> + + onNavigate?.({ type: 'storage' })} /> + onNavigate?.({ type: 'tasks' })} /> + onNavigate?.({ type: 'backups' })} /> +
+ )} +
+ ))} +
+
+
+ onNavigate?.({ type: 'settings' })} + /> + +
+
+ ) +} + +function SidebarItem({ + icon: Icon, + label, + active, + onClick, +}: { + icon: React.ComponentType<{ className?: string }> + label: string + active?: boolean + onClick?: () => void +}) { + return ( + + ) +} diff --git a/src/components/nodes/NodeDetail.tsx b/src/components/nodes/NodeDetail.tsx new file mode 100644 index 0000000..98c9a1e --- /dev/null +++ b/src/components/nodes/NodeDetail.tsx @@ -0,0 +1,225 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { StatusBadge } from '@/components/ui/status-badge' +import { Server, Cpu, MemoryStick, HardDrive } from 'lucide-react' +import { useVMs } from '@/hooks/useProxmox' +import type { ProxmoxNode } from '@/types/proxmox' + +interface NodeDetailProps { + node: ProxmoxNode + connectionId: string +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function formatUptime(seconds: number): string { + if (seconds === 0) return 'N/A' + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const parts: string[] = [] + if (days > 0) parts.push(`${days}d`) + if (hours > 0) parts.push(`${hours}h`) + if (minutes > 0) parts.push(`${minutes}m`) + return parts.join(' ') || '< 1m' +} + +function ResourceBar({ label, used, total, icon: Icon }: { + label: string + used: number + total: number + icon: React.ComponentType<{ className?: string }> +}) { + const percent = total > 0 ? (used / total) * 100 : 0 + const color = + percent > 90 ? 'bg-red-500' : + percent > 70 ? 'bg-yellow-500' : + 'bg-green-500' + + return ( +
+
+
+ + {label} +
+ + {percent.toFixed(1)}% + +
+
+
+
+
+ {formatBytes(used)} / {formatBytes(total)} +
+
+ ) +} + +export function NodeDetail({ node, connectionId }: NodeDetailProps) { + const { data: vms, isLoading: vmsLoading } = useVMs(connectionId) + + const nodeVMs = vms?.filter((vm) => vm.node === node.node) ?? [] + const runningVMs = nodeVMs.filter((vm) => vm.status === 'running') + + return ( +
+
+ {/* Header */} +
+
+
+ +

{node.node}

+ +
+

+ Uptime: {formatUptime(node.uptime)} +

+
+
+ + {/* Resource Usage */} +
+ + + +
+ {node.cpu.toFixed(1)} / {node.maxcpu} cores +
+
+
+ + + + + + + + + + + + +
+ + {/* VMs/Containers on this node */} + + +
+ Virtual Machines & Containers + + {runningVMs.length} running / {nodeVMs.length} total + +
+
+ + {vmsLoading ? ( +
+ Loading... +
+ ) : nodeVMs.length === 0 ? ( +
+ No VMs or containers found on this node +
+ ) : ( +
+ {nodeVMs.map((vm) => ( +
+
+
+ + {vm.vmid} + + {vm.name} +
+ + {vm.type} + +
+
+ +
+ {vm.maxmem > 0 ? formatBytes(vm.mem) : 'N/A'} +
+
+
+ ))} +
+ )} +
+
+ + {/* Node Info */} + + + System Information + + +
+
+ Node Name +

{node.node}

+
+
+ Status +

{node.status}

+
+
+ Uptime +

{formatUptime(node.uptime)}

+
+
+ Support Level +

{node.level || 'None'}

+
+
+ Total CPUs +

{node.maxcpu}

+
+
+ Total Memory +

{formatBytes(node.maxmem)}

+
+
+ Total Disk +

{formatBytes(node.maxdisk)}

+
+
+ ID +

{node.id}

+
+
+
+
+
+
+ ) +} diff --git a/src/components/settings/ConnectionManager.tsx b/src/components/settings/ConnectionManager.tsx new file mode 100644 index 0000000..ef51858 --- /dev/null +++ b/src/components/settings/ConnectionManager.tsx @@ -0,0 +1,121 @@ +import { useState } from 'react' +import { useConnectionStore } from '@/stores/connectionStore' +import { useToast } from '@/components/ui/toast' +import { Button } from '@/components/ui/button' +import { ConnectionDialog } from '@/components/connections/ConnectionDialog' +import { Plus, Pencil, Trash2 } from 'lucide-react' +import { cn } from '@/lib/utils' + +export function ConnectionManager() { + const connections = useConnectionStore((s) => s.connections) + const removeConnection = useConnectionStore((s) => s.removeConnection) + const { addToast } = useToast() + const [dialogOpen, setDialogOpen] = useState(false) + const [deleteConfirmId, setDeleteConfirmId] = useState(null) + + const getStatusColor = (status: string) => { + switch (status) { + case 'connected': + return 'bg-green-500' + case 'connecting': + case 'failover': + return 'bg-yellow-500' + case 'failed': + return 'bg-red-500' + default: + return 'bg-gray-500' + } + } + + const handleDelete = (id: string, name: string) => { + removeConnection(id) + setDeleteConfirmId(null) + addToast(`Connection "${name}" removed`, 'success') + } + + return ( +
+
+

+ {connections.length} connection{connections.length !== 1 ? 's' : ''} +

+ +
+ + {connections.length === 0 ? ( +

+ No connections configured. Add a server to get started. +

+ ) : ( +
+ {connections.map((connection) => ( +
+
+
+
+

{connection.name}

+

+ {connection.primary.url} +

+
+
+
+ {deleteConfirmId === connection.id ? ( + <> + + + + ) : ( + <> + + + + )} +
+
+ ))} +
+ )} + + +
+ ) +} diff --git a/src/components/settings/SettingsPage.tsx b/src/components/settings/SettingsPage.tsx new file mode 100644 index 0000000..b2febd4 --- /dev/null +++ b/src/components/settings/SettingsPage.tsx @@ -0,0 +1,79 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { ThemeSwitcher } from '@/components/settings/ThemeSwitcher' +import { ConnectionManager } from '@/components/settings/ConnectionManager' +import { ExternalLink } from 'lucide-react' + +export function SettingsPage() { + return ( +
+ ) +} diff --git a/src/components/settings/ThemeSwitcher.tsx b/src/components/settings/ThemeSwitcher.tsx new file mode 100644 index 0000000..b79aaaa --- /dev/null +++ b/src/components/settings/ThemeSwitcher.tsx @@ -0,0 +1,39 @@ +import { useUIStore } from '@/stores/uiStore' +import { cn } from '@/lib/utils' +import { Sun, Moon, Monitor } from 'lucide-react' + +type Theme = 'light' | 'dark' | 'system' + +const themeOptions: { value: Theme; label: string; icon: React.ComponentType<{ className?: string }> }[] = [ + { value: 'light', label: 'Light', icon: Sun }, + { value: 'dark', label: 'Dark', icon: Moon }, + { value: 'system', label: 'System', icon: Monitor }, +] + +export function ThemeSwitcher() { + const theme = useUIStore((s) => s.theme) + const setTheme = useUIStore((s) => s.setTheme) + + return ( +
+ {themeOptions.map((option) => { + const Icon = option.icon + return ( + + ) + })} +
+ ) +} diff --git a/src/components/storage/StorageCard.tsx b/src/components/storage/StorageCard.tsx new file mode 100644 index 0000000..c94b19d --- /dev/null +++ b/src/components/storage/StorageCard.tsx @@ -0,0 +1,134 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { HardDrive, Database, Archive, Disc, Box } from 'lucide-react' +import type { ProxmoxStorage } from '@/types/proxmox' + +interface StorageCardProps { + storage: ProxmoxStorage + onClick: () => void +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function getUsageColor(percent: number): string { + if (percent >= 90) return 'bg-red-500' + if (percent >= 70) return 'bg-yellow-500' + return 'bg-green-500' +} + +function getStorageIcon(type: string) { + switch (type.toLowerCase()) { + case 'lvm': + case 'lvmthin': + return Database + case 'zfs': + case 'zfspool': + return Database + case 'nfs': + case 'cifs': + case 'glusterfs': + return HardDrive + case 'local': + return HardDrive + default: + return HardDrive + } +} + +function getContentBadges(content: string): string[] { + if (!content) return [] + return content.split(',').map((c) => c.trim()) +} + +const contentTypeIcons: Record> = { + images: Box, + rootdir: Box, + backup: Archive, + iso: Disc, + snippets: HardDrive, + 'vztmpl': HardDrive, +} + +function getContentTypeLabel(type: string): string { + switch (type) { + case 'images': return 'VM Images' + case 'rootdir': return 'Containers' + case 'backup': return 'Backups' + case 'iso': return 'ISOs' + case 'snippets': return 'Snippets' + case 'vztmpl': return 'Templates' + default: return type + } +} + +export function StorageCard({ storage, onClick }: StorageCardProps) { + const percent = storage.total > 0 ? (storage.used / storage.total) * 100 : 0 + const usageColor = getUsageColor(percent) + const Icon = getStorageIcon(storage.type) + const contentTypes = getContentBadges(storage.content) + + return ( + + + + + {storage.storage} + +
+ {storage.type} + {storage.node && {storage.node}} +
+
+ + {/* Usage Bar */} +
+
+ Usage + {percent.toFixed(1)}% +
+
+
+
+
+ + {/* Size Info */} +
+ {formatBytes(storage.used)} used + {formatBytes(storage.total)} total +
+
+ {formatBytes(storage.avail)} available +
+ + {/* Content Types */} + {contentTypes.length > 0 && ( +
+ {contentTypes.map((type) => { + const TypeIcon = contentTypeIcons[type] || HardDrive + return ( + + + {getContentTypeLabel(type)} + + ) + })} +
+ )} + + + ) +} diff --git a/src/components/storage/StorageDetail.tsx b/src/components/storage/StorageDetail.tsx new file mode 100644 index 0000000..7f26ede --- /dev/null +++ b/src/components/storage/StorageDetail.tsx @@ -0,0 +1,281 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { ArrowLeft, HardDrive, Database, Archive, Disc, Box, Clock, File } from 'lucide-react' +import { useStorageDetail, useStorageContent } from '@/hooks/useProxmox' +import type { ProxmoxStorageContent } from '@/types/proxmox' + +interface StorageDetailProps { + connectionId: string + storage: string + onBack: () => void +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function getUsageColor(percent: number): string { + if (percent >= 90) return 'bg-red-500' + if (percent >= 70) return 'bg-yellow-500' + return 'bg-green-500' +} + +function getStorageIcon(type: string) { + switch (type?.toLowerCase()) { + case 'lvm': + case 'lvmthin': + return Database + case 'zfs': + case 'zfspool': + return Database + default: + return HardDrive + } +} + +function getContentIcon(contentType: string) { + switch (contentType) { + case 'images': return Box + case 'backup': return Archive + case 'iso': return Disc + default: return File + } +} + +function getContentTypeLabel(type: string): string { + switch (type) { + case 'images': return 'VM Images' + case 'rootdir': return 'Container Root Disks' + case 'backup': return 'Backups' + case 'iso': return 'ISO Images' + case 'snippets': return 'Snippets' + case 'vztmpl': return 'Container Templates' + default: return type + } +} + +function formatTimestamp(seconds: number): string { + if (!seconds) return 'N/A' + return new Date(seconds * 1000).toLocaleString() +} + +function formatContentItem(item: ProxmoxStorageContent): string { + const parts = item.volid.split('/') + return parts[parts.length - 1] || item.volid +} + +export function StorageDetail({ connectionId, storage, onBack }: StorageDetailProps) { + const { data: detail, isLoading: detailLoading } = useStorageDetail( + connectionId, + null, + storage + ) + const { data: contentList, isLoading: contentLoading } = useStorageContent( + connectionId, + storage + ) + + const percent = detail && detail.total > 0 + ? (detail.used / detail.total) * 100 + : 0 + const usageColor = getUsageColor(percent) + const Icon = detail ? getStorageIcon(detail.type) : HardDrive + + // Group content by type + const contentByType = contentList?.reduce((acc, item) => { + const type = item.content + if (!acc[type]) acc[type] = [] + acc[type].push(item) + return acc + }, {} as Record) ?? {} + + if (detailLoading || contentLoading) { + return ( +
+

Loading storage details...

+
+ ) + } + + if (!detail) { + return ( +
+

Failed to load storage details

+
+ ) + } + + return ( +
+
+ {/* Header */} +
+ +
+
+ +

{detail.storage}

+
+

+ {detail.type.toUpperCase()} storage on {detail.node} +

+
+
+ + {/* Resource Usage */} + + + Resource Usage + + + {/* Large Usage Bar */} +
+
+ Disk Usage + {percent.toFixed(1)}% +
+
+
+
+
+ {formatBytes(detail.used)} used + {formatBytes(detail.total)} total +
+
+ + {/* Stats Grid */} +
+
+

Total Size

+

{formatBytes(detail.total)}

+
+
+

Used

+

{formatBytes(detail.used)}

+
+
+

Available

+

{formatBytes(detail.avail)}

+
+
+

Status

+

+ {detail.active ? ( + Active + ) : ( + Inactive + )} +

+
+
+ + + + {/* Storage Info */} + + + Storage Information + + +
+
+

Name

+

{detail.storage}

+
+
+

Type

+

{detail.type}

+
+
+

Node

+

{detail.node}

+
+
+

Enabled

+

{detail.enabled ? 'Yes' : 'No'}

+
+
+

Shared

+

{detail.shared ? 'Yes' : 'No'}

+
+
+

Content Types

+

{detail.content || 'None'}

+
+
+
+
+ + {/* Content List */} + + + + Content + {contentList && ( + + ({contentList.length} items) + + )} + + + + {!contentList || contentList.length === 0 ? ( +

No content found

+ ) : ( +
+ {Object.entries(contentByType).map(([type, items]) => { + const TypeIcon = getContentIcon(type) + return ( +
+
+ + {getContentTypeLabel(type)} + ({items.length}) +
+
+ {items.map((item, idx) => ( +
+
+ + + {formatContentItem(item)} + +
+
+ {item.format && ( + + {item.format} + + )} + {item.size && {formatBytes(item.size)}} +
+ + {formatTimestamp(item.ctime)} +
+
+
+ ))} +
+
+ ) + })} +
+ )} +
+
+
+
+ ) +} diff --git a/src/components/storage/StorageOverview.tsx b/src/components/storage/StorageOverview.tsx new file mode 100644 index 0000000..883aef0 --- /dev/null +++ b/src/components/storage/StorageOverview.tsx @@ -0,0 +1,90 @@ +import { useState, useMemo } from 'react' +import { useStorage } from '@/hooks/useProxmox' +import { StorageCard } from './StorageCard' +import { Search, HardDrive } from 'lucide-react' +import { Input } from '@/components/ui/input' + +interface StorageOverviewProps { + connectionId: string + onStorageClick?: (storage: string) => void +} + +export function StorageOverview({ connectionId, onStorageClick }: StorageOverviewProps) { + const { data: storageList, isLoading, error } = useStorage(connectionId) + const [search, setSearch] = useState('') + + const filteredStorage = useMemo(() => { + if (!storageList) return [] + if (!search) return storageList + const query = search.toLowerCase() + return storageList.filter( + (s) => + s.storage.toLowerCase().includes(query) || + s.type.toLowerCase().includes(query) || + s.node.toLowerCase().includes(query) + ) + }, [storageList, search]) + + if (isLoading) { + return ( +
+

Loading storage pools...

+
+ ) + } + + if (error) { + return ( +
+

Failed to load storage pools

+
+ ) + } + + return ( +
+
+ {/* Header */} +
+

Storage Pools

+

+ {storageList?.length ?? 0} storage pools across the cluster +

+
+ + {/* Search */} +
+ + setSearch(e.target.value)} + className="pl-9" + /> +
+ + {/* Storage Grid */} + {filteredStorage.length === 0 ? ( +
+ +

+ {storageList?.length === 0 + ? 'No storage pools found' + : 'No storage pools match your search'} +

+
+ ) : ( +
+ {filteredStorage.map((storage) => ( + onStorageClick?.(storage.storage)} + /> + ))} +
+ )} +
+
+ ) +} diff --git a/src/components/tasks/TaskList.tsx b/src/components/tasks/TaskList.tsx new file mode 100644 index 0000000..dfb9451 --- /dev/null +++ b/src/components/tasks/TaskList.tsx @@ -0,0 +1,346 @@ +import { useState, useMemo, Fragment } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Search, ListTodo, ChevronDown, ChevronRight, Play, CheckCircle, XCircle, Clock } from 'lucide-react' +import { useTasks } from '@/hooks/useProxmox' +import type { ProxmoxTask } from '@/types/proxmox' + +interface TaskListProps { + connectionId: string +} + +type TaskStatusFilter = 'all' | 'running' | 'completed' | 'failed' + +function formatTimestamp(seconds: number): string { + if (seconds === 0) return 'N/A' + const date = new Date(seconds * 1000) + return date.toLocaleString() +} + +function formatDuration(start: number, end?: number): string { + if (start === 0) return 'N/A' + const endTime = end ?? Math.floor(Date.now() / 1000) + const duration = endTime - start + if (duration < 0) return 'N/A' + const hours = Math.floor(duration / 3600) + const minutes = Math.floor((duration % 3600) / 60) + const seconds = duration % 60 + const parts: string[] = [] + if (hours > 0) parts.push(`${hours}h`) + if (minutes > 0) parts.push(`${minutes}m`) + if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`) + return parts.join(' ') +} + +function getTaskStatus(task: ProxmoxTask): 'running' | 'completed' | 'failed' { + if (task.status === 'running') return 'running' + if (task.exitstatus === 'OK') return 'completed' + if (task.exitstatus && task.exitstatus !== 'OK') return 'failed' + // If no endtime, consider it running + if (!task.endtime) return 'running' + return 'completed' +} + +function TaskStatusIcon({ status }: { status: 'running' | 'completed' | 'failed' }) { + switch (status) { + case 'running': + return + case 'completed': + return + case 'failed': + return + } +} + +function TaskStatusBadge({ status }: { status: 'running' | 'completed' | 'failed' }) { + const config = { + running: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', + completed: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', + failed: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300', + } + + const label = { + running: 'Running', + completed: 'Completed', + failed: 'Failed', + } + + return ( + + + {label[status]} + + ) +} + +export function TaskList({ connectionId }: TaskListProps) { + const { data: tasks, isLoading, error } = useTasks(connectionId) + + const [search, setSearch] = useState('') + const [statusFilter, setStatusFilter] = useState('all') + const [nodeFilter, setNodeFilter] = useState('all') + const [userFilter, setUserFilter] = useState('all') + const [expandedTasks, setExpandedTasks] = useState>(new Set()) + + const filteredTasks = useMemo(() => { + if (!tasks) return [] + return tasks + .filter((task) => { + const taskStatus = getTaskStatus(task) + // Status filter + if (statusFilter !== 'all' && taskStatus !== statusFilter) return false + // Node filter + if (nodeFilter !== 'all' && task.node !== nodeFilter) return false + // User filter + if (userFilter !== 'all' && task.user !== userFilter) return false + // Search filter + if (search) { + const query = search.toLowerCase() + const matchesUPID = task.upid.toLowerCase().includes(query) + const matchesType = task.type.toLowerCase().includes(query) + const matchesId = task.id.toLowerCase().includes(query) + if (!matchesUPID && !matchesType && !matchesId) return false + } + return true + }) + .sort((a, b) => { + // Sort by start time descending (newest first) + return b.starttime - a.starttime + }) + }, [tasks, statusFilter, nodeFilter, userFilter, search]) + + const uniqueNodes = useMemo(() => { + if (!tasks) return [] + return [...new Set(tasks.map((t) => t.node))].sort() + }, [tasks]) + + const uniqueUsers = useMemo(() => { + if (!tasks) return [] + return [...new Set(tasks.map((t) => t.user))].sort() + }, [tasks]) + + const runningCount = useMemo(() => { + if (!tasks) return 0 + return tasks.filter((t) => getTaskStatus(t) === 'running').length + }, [tasks]) + + const toggleExpanded = (upid: string) => { + setExpandedTasks((prev) => { + const next = new Set(prev) + if (next.has(upid)) { + next.delete(upid) + } else { + next.add(upid) + } + return next + }) + } + + if (isLoading) { + return ( +
+

Loading tasks...

+
+ ) + } + + if (error) { + return ( +
+

Failed to load tasks

+
+ ) + } + + return ( +
+
+ {/* Header */} +
+
+ +

Tasks

+
+

+ {tasks?.length ?? 0} total tasks + {runningCount > 0 && ( + + ({runningCount} running) + + )} +

+
+ + {/* Filters */} +
+
+ + setSearch(e.target.value)} + className="pl-9" + /> +
+ + + + + + +
+ + {/* Task Table */} + + + {filteredTasks.length === 0 ? ( +
+ {tasks?.length === 0 ? ( +
+ +

No tasks found

+
+ ) : ( +
+ +

No tasks match your filters

+
+ )} +
+ ) : ( +
+ + + + + + + + + + + + + + {filteredTasks.map((task) => { + const taskStatus = getTaskStatus(task) + const isExpanded = expandedTasks.has(task.upid) + return ( + + toggleExpanded(task.upid)} + > + + + + + + + + + + {isExpanded && ( + + + + )} + + ) + })} + +
+ UPIDTypeNodeUserStatusStart TimeDuration
+ {isExpanded ? ( + + ) : ( + + )} + {task.upid} + + {task.type} + + {task.node}{task.user} + + +
+ + {formatTimestamp(task.starttime)} +
+
+ {formatDuration(task.starttime, task.endtime)} +
+
+
+ UPID: +

{task.upid}

+
+
+ Task ID: +

{task.id}

+
+
+ PID: +

{task.pid}

+
+
+ PStart: +

{task.pstart}

+
+ {task.endtime && ( +
+ End Time: +

{formatTimestamp(task.endtime)}

+
+ )} + {task.exitstatus && ( +
+ Exit Status: +

+ {task.exitstatus} +

+
+ )} +
+
+
+ )} +
+
+
+
+ ) +} diff --git a/src/components/tasks/TaskStatusBar.tsx b/src/components/tasks/TaskStatusBar.tsx new file mode 100644 index 0000000..88c7a4c --- /dev/null +++ b/src/components/tasks/TaskStatusBar.tsx @@ -0,0 +1,47 @@ +import { useMemo } from 'react' +import { ListTodo, Play } from 'lucide-react' +import { useTasks } from '@/hooks/useProxmox' +import type { ProxmoxTask } from '@/types/proxmox' + +interface TaskStatusBarProps { + connectionId: string + onClick?: () => void +} + +function getTaskStatus(task: ProxmoxTask): 'running' | 'completed' | 'failed' { + if (task.status === 'running') return 'running' + if (task.exitstatus === 'OK') return 'completed' + if (task.exitstatus && task.exitstatus !== 'OK') return 'failed' + if (!task.endtime) return 'running' + return 'completed' +} + +export function TaskStatusBar({ connectionId, onClick }: TaskStatusBarProps) { + const { data: tasks } = useTasks(connectionId) + + const runningCount = useMemo(() => { + if (!tasks) return 0 + return tasks.filter((t) => getTaskStatus(t) === 'running').length + }, [tasks]) + + return ( + + ) +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..9e6e1e2 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,52 @@ +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-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button' + return ( + + ) + } +) +Button.displayName = 'Button' + +export { Button, buttonVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..85849b0 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,75 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = 'Card' + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = 'CardHeader' + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = 'CardTitle' + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = 'CardDescription' + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = 'CardContent' + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = 'CardFooter' + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..20a3d94 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,116 @@ +import * as React from 'react' +import * as DialogPrimitive from '@radix-ui/react-dialog' +import { X } from 'lucide-react' +import { cn } from '@/lib/utils' + +const Dialog = DialogPrimitive.Root +const DialogTrigger = DialogPrimitive.Trigger +const DialogPortal = DialogPrimitive.Portal +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = 'DialogHeader' + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = 'DialogFooter' + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..3bd89ba --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = 'Input' + +export { Input } diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..3fbe658 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' +import * as LabelPrimitive from '@radix-ui/react-label' +import { cva, type VariantProps } from 'class-variance-authority' +import { cn } from '@/lib/utils' + +const labelVariants = cva( + 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx new file mode 100644 index 0000000..9530110 --- /dev/null +++ b/src/components/ui/scroll-area.tsx @@ -0,0 +1,45 @@ +import * as React from 'react' +import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' +import { cn } from '@/lib/utils' + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = 'vertical', ...props }, ref) => ( + + + +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/src/components/ui/status-badge.tsx b/src/components/ui/status-badge.tsx new file mode 100644 index 0000000..5d8f210 --- /dev/null +++ b/src/components/ui/status-badge.tsx @@ -0,0 +1,60 @@ +import { cn } from '@/lib/utils' +import { CheckCircle, XCircle, Pause, Clock } from 'lucide-react' + +type BadgeStatus = 'running' | 'stopped' | 'paused' | 'suspended' | 'online' | 'offline' + +interface StatusBadgeProps { + status: BadgeStatus + className?: string +} + +const statusConfig: Record }> = { + running: { + label: 'Running', + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', + icon: CheckCircle, + }, + stopped: { + label: 'Stopped', + color: 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300', + icon: XCircle, + }, + paused: { + label: 'Paused', + color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300', + icon: Pause, + }, + suspended: { + label: 'Suspended', + color: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300', + icon: Clock, + }, + online: { + label: 'Online', + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', + icon: CheckCircle, + }, + offline: { + label: 'Offline', + color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300', + icon: XCircle, + }, +} + +export function StatusBadge({ status, className }: StatusBadgeProps) { + const config = statusConfig[status] + const Icon = config.icon + + return ( + + + {config.label} + + ) +} diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..c99280c --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,52 @@ +import * as React from 'react' +import * as TabsPrimitive from '@radix-ui/react-tabs' +import { cn } from '@/lib/utils' + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx new file mode 100644 index 0000000..ae58a7c --- /dev/null +++ b/src/components/ui/toast.tsx @@ -0,0 +1,120 @@ +import { createContext, useCallback, useContext, useState } from 'react' +import { cn } from '@/lib/utils' +import { X, CheckCircle, AlertTriangle, Info, AlertCircle } from 'lucide-react' + +type ToastVariant = 'success' | 'error' | 'warning' | 'info' + +interface Toast { + id: string + message: string + variant: ToastVariant +} + +interface ToastContextValue { + toasts: Toast[] + addToast: (message: string, variant?: ToastVariant) => void + removeToast: (id: string) => void +} + +const ToastContext = createContext(null) + +export function useToast() { + const context = useContext(ToastContext) + if (!context) { + throw new Error('useToast must be used within a ToastProvider') + } + return context +} + +export function ToastProvider({ children }: { children: React.ReactNode }) { + const [toasts, setToasts] = useState([]) + + const removeToast = useCallback((id: string) => { + setToasts((prev) => prev.filter((t) => t.id !== id)) + }, []) + + const addToast = useCallback( + (message: string, variant: ToastVariant = 'info') => { + const id = crypto.randomUUID() + setToasts((prev) => [...prev, { id, message, variant }]) + setTimeout(() => removeToast(id), 5000) + }, + [removeToast] + ) + + return ( + + {children} + + + ) +} + +function ToastContainer({ + toasts, + onRemove, +}: { + toasts: Toast[] + onRemove: (id: string) => void +}) { + if (toasts.length === 0) return null + + return ( +
+ {toasts.map((toast) => ( + + ))} +
+ ) +} + +const variantStyles: Record; container: string }> = { + success: { + icon: CheckCircle, + container: 'bg-green-50 border-green-200 text-green-800 dark:bg-green-950 dark:border-green-800 dark:text-green-200', + }, + error: { + icon: AlertCircle, + container: 'bg-red-50 border-red-200 text-red-800 dark:bg-red-950 dark:border-red-800 dark:text-red-200', + }, + warning: { + icon: AlertTriangle, + container: 'bg-yellow-50 border-yellow-200 text-yellow-800 dark:bg-yellow-950 dark:border-yellow-800 dark:text-yellow-200', + }, + info: { + icon: Info, + container: 'bg-blue-50 border-blue-200 text-blue-800 dark:bg-blue-950 dark:border-blue-800 dark:text-blue-200', + }, +} + +function ToastItem({ + toast, + onRemove, +}: { + toast: Toast + onRemove: (id: string) => void +}) { + const config = variantStyles[toast.variant] + const Icon = config.icon + + return ( +
+ +

{toast.message}

+ +
+ ) +} diff --git a/src/components/vms/ContainerList.tsx b/src/components/vms/ContainerList.tsx new file mode 100644 index 0000000..7f15b44 --- /dev/null +++ b/src/components/vms/ContainerList.tsx @@ -0,0 +1,211 @@ +import { useState, useMemo } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { StatusBadge } from '@/components/ui/status-badge' +import { Search, Server, Box } from 'lucide-react' +import { useVMs } from '@/hooks/useProxmox' +import type { ProxmoxVM } from '@/types/proxmox' + +interface ContainerListProps { + connectionId: string + onContainerClick?: (container: ProxmoxVM) => void +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function formatUptime(seconds: number): string { + if (seconds === 0) return 'N/A' + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const parts: string[] = [] + if (days > 0) parts.push(`${days}d`) + if (hours > 0) parts.push(`${hours}h`) + if (minutes > 0) parts.push(`${minutes}m`) + return parts.join(' ') || '< 1m' +} + +type ContainerStatusFilter = 'all' | ProxmoxVM['status'] + +export function ContainerList({ connectionId, onContainerClick }: ContainerListProps) { + const { data: vms, isLoading, error } = useVMs(connectionId) + + const [search, setSearch] = useState('') + const [statusFilter, setStatusFilter] = useState('all') + const [nodeFilter, setNodeFilter] = useState('all') + + // Filter to only LXC containers + const containers = useMemo(() => { + return vms?.filter((vm) => vm.type === 'lxc') ?? [] + }, [vms]) + + const filteredContainers = useMemo(() => { + return containers.filter((container) => { + // Status filter + if (statusFilter !== 'all' && container.status !== statusFilter) return false + // Node filter + if (nodeFilter !== 'all' && container.node !== nodeFilter) return false + // Search filter + if (search) { + const query = search.toLowerCase() + const matchesName = container.name.toLowerCase().includes(query) + const matchesVMID = container.vmid.toString().includes(query) + if (!matchesName && !matchesVMID) return false + } + return true + }) + }, [containers, statusFilter, nodeFilter, search]) + + const uniqueNodes = useMemo(() => { + return [...new Set(containers.map((c) => c.node))].sort() + }, [containers]) + + if (isLoading) { + return ( +
+

Loading containers...

+
+ ) + } + + if (error) { + return ( +
+

Failed to load containers

+
+ ) + } + + return ( +
+
+ {/* Header */} +
+

Containers

+

+ {containers.length} total containers across the cluster +

+
+ + {/* Filters */} +
+
+ + setSearch(e.target.value)} + className="pl-9" + /> +
+ + + + +
+ + {/* Container Table */} + + + {filteredContainers.length === 0 ? ( +
+ {containers.length === 0 ? ( +
+ +

No containers found

+
+ ) : ( +
+ +

No containers match your filters

+
+ )} +
+ ) : ( +
+ + + + + + + + + + + + + + + {filteredContainers.map((container) => ( + onContainerClick?.(container)} + > + + + + + + + + + + ))} + +
VMIDNameStatusNodeCPUMemoryDiskUptime
{container.vmid}{container.name} + + +
+ + {container.node} +
+
+ {container.cpus} cores + + {container.maxmem > 0 ? formatBytes(container.mem) : 'N/A'} + / {formatBytes(container.maxmem)} + + {container.maxdisk > 0 ? formatBytes(container.disk) : 'N/A'} + / {formatBytes(container.maxdisk)} + + {formatUptime(container.uptime)} +
+
+ )} +
+
+
+
+ ) +} diff --git a/src/components/vms/VMDetail.tsx b/src/components/vms/VMDetail.tsx new file mode 100644 index 0000000..0048854 --- /dev/null +++ b/src/components/vms/VMDetail.tsx @@ -0,0 +1,288 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { StatusBadge } from '@/components/ui/status-badge' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { + ArrowLeft, + Play, + Square, + Power, + RotateCw, + Pause, + PlayCircle, + ArrowRightLeft, +} from 'lucide-react' +import { useStartVM, useStopVM, useShutdownVM, useRebootVM, useSuspendVM, useResumeVM, useClusterStatus } from '@/hooks/useProxmox' +import { OverviewTab } from '@/components/vms/tabs/OverviewTab' +import { HardwareTab } from '@/components/vms/tabs/HardwareTab' +import { DisksTab } from '@/components/vms/tabs/DisksTab' +import { NetworkTab } from '@/components/vms/tabs/NetworkTab' +import { SnapshotsTab } from '@/components/vms/tabs/SnapshotsTab' +import { ConsoleTab } from '@/components/vms/tabs/ConsoleTab' +import { MigrateDialog } from '@/components/vms/dialogs/MigrateDialog' +import type { ProxmoxVM } from '@/types/proxmox' + +interface VMDetailProps { + vm: ProxmoxVM + connectionId: string + onBack: () => void +} + +type ConfirmAction = { + type: 'stop' | 'shutdown' | 'reboot' + title: string + description: string +} + +export function VMDetail({ vm, connectionId, onBack }: VMDetailProps) { + const [confirmAction, setConfirmAction] = useState(null) + const [migrateDialogOpen, setMigrateDialogOpen] = useState(false) + + const startVM = useStartVM() + const stopVM = useStopVM() + const shutdownVM = useShutdownVM() + const rebootVM = useRebootVM() + const suspendVM = useSuspendVM() + const resumeVM = useResumeVM() + const clusterStatus = useClusterStatus(connectionId) + + const isRunning = vm.status === 'running' + const isStopped = vm.status === 'stopped' + const isBusy = startVM.isPending || stopVM.isPending || shutdownVM.isPending || rebootVM.isPending || suspendVM.isPending || resumeVM.isPending + + const isCluster = clusterStatus.data?.type === 'cluster' && (clusterStatus.data?.nodes?.length ?? 0) > 1 + + const handleStart = () => { + startVM.mutate({ node: vm.node, vmid: vm.vmid }) + } + + const handleStop = () => { + setConfirmAction({ + type: 'stop', + title: 'Force Stop VM', + description: `Are you sure you want to force stop "${vm.name}"? This is equivalent to pulling the power plug and may cause data loss.`, + }) + } + + const handleShutdown = () => { + setConfirmAction({ + type: 'shutdown', + title: 'Shutdown VM', + description: `Are you sure you want to gracefully shutdown "${vm.name}"? The VM will have a chance to save its state.`, + }) + } + + const handleReboot = () => { + setConfirmAction({ + type: 'reboot', + title: 'Reboot VM', + description: `Are you sure you want to reboot "${vm.name}"? The VM will be restarted gracefully.`, + }) + } + + const handleSuspend = () => { + suspendVM.mutate({ node: vm.node, vmid: vm.vmid }) + } + + const handleResume = () => { + resumeVM.mutate({ node: vm.node, vmid: vm.vmid }) + } + + const executeConfirmAction = () => { + if (!confirmAction) return + + switch (confirmAction.type) { + case 'stop': + stopVM.mutate({ node: vm.node, vmid: vm.vmid }) + break + case 'shutdown': + shutdownVM.mutate({ node: vm.node, vmid: vm.vmid }) + break + case 'reboot': + rebootVM.mutate({ node: vm.node, vmid: vm.vmid }) + break + } + + setConfirmAction(null) + } + + return ( +
+
+ {/* Header */} +
+
+ +
+
+

{vm.name}

+ +
+

+ VMID {vm.vmid} · {vm.type.toUpperCase()} · {vm.node} +

+
+
+ + {/* Lifecycle Actions */} +
+ {isStopped || !isRunning ? ( + + ) : null} + + {isRunning && ( + <> + + + + + )} + + {isRunning && ( + + )} + + {vm.status === 'paused' && ( + + )} + + {isCluster && ( + + )} +
+
+ + {/* Tabs */} + + + Overview + Console + Hardware + Disks + Network + Snapshots + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {/* Confirmation Dialog */} + setConfirmAction(null)}> + + + {confirmAction?.title} + {confirmAction?.description} + + + + + + + + + {/* Migrate Dialog */} + +
+ ) +} diff --git a/src/components/vms/VMList.tsx b/src/components/vms/VMList.tsx new file mode 100644 index 0000000..85bd41e --- /dev/null +++ b/src/components/vms/VMList.tsx @@ -0,0 +1,228 @@ +import { useState, useMemo } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { StatusBadge } from '@/components/ui/status-badge' +import { Search, Server, Box } from 'lucide-react' +import { useVMs } from '@/hooks/useProxmox' +import type { ProxmoxVM } from '@/types/proxmox' + +interface VMListProps { + connectionId: string + onVMClick?: (vm: ProxmoxVM) => void +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function formatUptime(seconds: number): string { + if (seconds === 0) return 'N/A' + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const parts: string[] = [] + if (days > 0) parts.push(`${days}d`) + if (hours > 0) parts.push(`${hours}h`) + if (minutes > 0) parts.push(`${minutes}m`) + return parts.join(' ') || '< 1m' +} + +type VMTypeFilter = 'all' | 'qemu' | 'lxc' +type VMStatusFilter = 'all' | ProxmoxVM['status'] + +export function VMList({ connectionId, onVMClick }: VMListProps) { + const { data: vms, isLoading: vmsLoading, error: vmsError } = useVMs(connectionId) + + const [search, setSearch] = useState('') + const [typeFilter, setTypeFilter] = useState('all') + const [statusFilter, setStatusFilter] = useState('all') + const [nodeFilter, setNodeFilter] = useState('all') + + const filteredVMs = useMemo(() => { + if (!vms) return [] + return vms.filter((vm) => { + // Type filter + if (typeFilter !== 'all' && vm.type !== typeFilter) return false + // Status filter + if (statusFilter !== 'all' && vm.status !== statusFilter) return false + // Node filter + if (nodeFilter !== 'all' && vm.node !== nodeFilter) return false + // Search filter + if (search) { + const query = search.toLowerCase() + const matchesName = vm.name.toLowerCase().includes(query) + const matchesVMID = vm.vmid.toString().includes(query) + if (!matchesName && !matchesVMID) return false + } + return true + }) + }, [vms, typeFilter, statusFilter, nodeFilter, search]) + + const uniqueNodes = useMemo(() => { + if (!vms) return [] + return [...new Set(vms.map((vm) => vm.node))].sort() + }, [vms]) + + if (vmsLoading) { + return ( +
+

Loading VMs...

+
+ ) + } + + if (vmsError) { + return ( +
+

Failed to load VMs

+
+ ) + } + + return ( +
+
+ {/* Header */} +
+

Virtual Machines

+

+ {vms?.length ?? 0} total VMs across the cluster +

+
+ + {/* Filters */} +
+
+ + setSearch(e.target.value)} + className="pl-9" + /> +
+ + + + + + +
+ + {/* VM Table */} + + + {filteredVMs.length === 0 ? ( +
+ {vms?.length === 0 ? ( +
+ +

No VMs found

+
+ ) : ( +
+ +

No VMs match your filters

+
+ )} +
+ ) : ( +
+ + + + + + + + + + + + + + + + {filteredVMs.map((vm) => ( + onVMClick?.(vm)} + > + + + + + + + + + + + ))} + +
VMIDNameStatusNodeTypeCPUMemoryDiskUptime
{vm.vmid}{vm.name} + + +
+ + {vm.node} +
+
+ + {vm.type} + + + {vm.cpus} cores + + {vm.maxmem > 0 ? formatBytes(vm.mem) : 'N/A'} + / {formatBytes(vm.maxmem)} + + {vm.maxdisk > 0 ? formatBytes(vm.disk) : 'N/A'} + / {formatBytes(vm.maxdisk)} + + {formatUptime(vm.uptime)} +
+
+ )} +
+
+
+
+ ) +} diff --git a/src/components/vms/dialogs/AddDiskDialog.tsx b/src/components/vms/dialogs/AddDiskDialog.tsx new file mode 100644 index 0000000..df550f6 --- /dev/null +++ b/src/components/vms/dialogs/AddDiskDialog.tsx @@ -0,0 +1,111 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useAddDisk } from '@/hooks/useProxmox' +import type { ProxmoxVM, AddDiskConfig } from '@/types/proxmox' + +interface AddDiskDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + vm: ProxmoxVM +} + +export function AddDiskDialog({ open, onOpenChange, vm }: AddDiskDialogProps) { + const [storage, setStorage] = useState('local-lvm') + const [size, setSize] = useState('32') + const [busType, setBusType] = useState('scsi') + const addDisk = useAddDisk() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + const sizeBytes = parseFloat(size) * 1024 * 1024 * 1024 + addDisk.mutate( + { + node: vm.node, + vmid: vm.vmid, + config: { storage, size: sizeBytes, busType }, + }, + { + onSuccess: () => { + onOpenChange(false) + setStorage('local-lvm') + setSize('32') + setBusType('scsi') + }, + }, + ) + } + + return ( + + + + Add Disk + + Add a new disk to {vm.name} (VMID {vm.vmid}) + + +
+
+ + setStorage(e.target.value)} + placeholder="local-lvm" + required + /> +
+
+ + setSize(e.target.value)} + placeholder="32" + required + /> +
+
+ + +
+ {addDisk.isError && ( +

+ Failed to add disk. Please try again. +

+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/vms/dialogs/AddNICDialog.tsx b/src/components/vms/dialogs/AddNICDialog.tsx new file mode 100644 index 0000000..ca58d8d --- /dev/null +++ b/src/components/vms/dialogs/AddNICDialog.tsx @@ -0,0 +1,140 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useAddNIC } from '@/hooks/useProxmox' +import type { ProxmoxVM } from '@/types/proxmox' + +interface AddNICDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + vm: ProxmoxVM +} + +export function AddNICDialog({ open, onOpenChange, vm }: AddNICDialogProps) { + const [bridge, setBridge] = useState('vmbr0') + const [model, setModel] = useState('virtio') + const [macaddr, setMacaddr] = useState('') + const [tag, setTag] = useState('') + const [firewall, setFirewall] = useState(false) + const addNIC = useAddNIC() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + addNIC.mutate( + { + node: vm.node, + vmid: vm.vmid, + config: { + bridge, + model, + macaddr: macaddr || undefined, + tag: tag ? parseInt(tag, 10) : undefined, + firewall, + }, + }, + { + onSuccess: () => { + onOpenChange(false) + setBridge('vmbr0') + setModel('virtio') + setMacaddr('') + setTag('') + setFirewall(false) + }, + }, + ) + } + + return ( + + + + Add Network Interface + + Add a new NIC to {vm.name} (VMID {vm.vmid}) + + +
+
+ + setBridge(e.target.value)} + placeholder="vmbr0" + required + /> +
+
+ + +
+
+ + setMacaddr(e.target.value)} + placeholder="auto-generated if empty" + /> +
+
+ + setTag(e.target.value)} + placeholder="none" + /> +
+
+ setFirewall(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+ {addNIC.isError && ( +

+ Failed to add network interface. Please try again. +

+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/vms/dialogs/CreateSnapshotDialog.tsx b/src/components/vms/dialogs/CreateSnapshotDialog.tsx new file mode 100644 index 0000000..2c61d31 --- /dev/null +++ b/src/components/vms/dialogs/CreateSnapshotDialog.tsx @@ -0,0 +1,113 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useCreateSnapshot } from '@/hooks/useProxmox' +import type { ProxmoxVM } from '@/types/proxmox' + +interface CreateSnapshotDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + vm: ProxmoxVM +} + +export function CreateSnapshotDialog({ open, onOpenChange, vm }: CreateSnapshotDialogProps) { + const [name, setName] = useState('') + const [description, setDescription] = useState('') + const [vmstate, setVmstate] = useState(false) + const createSnapshot = useCreateSnapshot() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + createSnapshot.mutate( + { + node: vm.node, + vmid: vm.vmid, + config: { + name, + description: description || undefined, + vmstate, + }, + }, + { + onSuccess: () => { + onOpenChange(false) + setName('') + setDescription('') + setVmstate(false) + }, + }, + ) + } + + return ( + + + + Create Snapshot + + Create a new snapshot for {vm.name} (VMID {vm.vmid}) + + +
+
+ + setName(e.target.value)} + placeholder="before-update" + required + /> +
+
+ + setDescription(e.target.value)} + placeholder="Before applying system updates" + /> +
+
+ setVmstate(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+

+ Including VM state captures the running memory, allowing rollback to the exact running + state. This requires more disk space. +

+ {createSnapshot.isError && ( +

+ Failed to create snapshot. Please try again. +

+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/vms/dialogs/EditNICDialog.tsx b/src/components/vms/dialogs/EditNICDialog.tsx new file mode 100644 index 0000000..8b8fdd3 --- /dev/null +++ b/src/components/vms/dialogs/EditNICDialog.tsx @@ -0,0 +1,126 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useEditNIC } from '@/hooks/useProxmox' +import type { ProxmoxVM, ProxmoxNetwork } from '@/types/proxmox' + +interface EditNICDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + vm: ProxmoxVM + nic: ProxmoxNetwork +} + +export function EditNICDialog({ open, onOpenChange, vm, nic }: EditNICDialogProps) { + const [bridge, setBridge] = useState(nic.bridge ?? '') + const [model, setModel] = useState(nic.model) + const [tag, setTag] = useState(nic.tag != null ? String(nic.tag) : '') + const [firewall, setFirewall] = useState(nic.firewall === 1) + const editNIC = useEditNIC() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + editNIC.mutate( + { + node: vm.node, + vmid: vm.vmid, + nic: nic.name, + config: { + bridge: bridge || undefined, + model, + tag: tag ? parseInt(tag, 10) : undefined, + firewall, + }, + }, + { + onSuccess: () => { + onOpenChange(false) + }, + }, + ) + } + + return ( + + + + Edit Network Interface + + Edit {nic.name} ({nic.model}) on {vm.name} (VMID {vm.vmid}) + + +
+
+ + setBridge(e.target.value)} + placeholder="vmbr0" + required + /> +
+
+ + +
+
+ + setTag(e.target.value)} + placeholder="none" + /> +
+
+ setFirewall(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+ {editNIC.isError && ( +

+ Failed to edit network interface. Please try again. +

+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/vms/dialogs/MigrateDialog.tsx b/src/components/vms/dialogs/MigrateDialog.tsx new file mode 100644 index 0000000..d74f32c --- /dev/null +++ b/src/components/vms/dialogs/MigrateDialog.tsx @@ -0,0 +1,133 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { AlertTriangle } from 'lucide-react' +import { useMigrateVM, useNodes } from '@/hooks/useProxmox' +import type { ProxmoxVM } from '@/types/proxmox' + +interface MigrateDialogProps { + vm: ProxmoxVM + connectionId: string + open: boolean + onOpenChange: (open: boolean) => void +} + +export function MigrateDialog({ vm, connectionId, open, onOpenChange }: MigrateDialogProps) { + const [targetNode, setTargetNode] = useState('') + const [online, setOnline] = useState(true) + const migrateVM = useMigrateVM() + const { data: nodes } = useNodes(connectionId) + + const otherNodes = (nodes ?? []).filter((n) => n.node !== vm.node && n.status === 'online') + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!targetNode.trim()) return + + migrateVM.mutate( + { + node: vm.node, + vmid: vm.vmid, + targetNode, + online, + }, + { + onSuccess: () => { + onOpenChange(false) + setTargetNode('') + setOnline(true) + }, + }, + ) + } + + return ( + + + + Migrate VM + + Migrate {vm.name} (VMID {vm.vmid}) to another node + + +
+
+ +
+

Warning

+

+ Migration may cause temporary downtime. Online migration keeps the VM running but + requires shared storage. Offline migration requires the VM to be stopped. +

+
+
+
+ + {otherNodes.length > 0 ? ( + + ) : ( + setTargetNode(e.target.value)} + placeholder="pve2" + required + /> + )} +
+
+ setOnline(e.target.checked)} + className="h-4 w-4 rounded border-input" + /> + +
+

+ Online migration requires shared storage between nodes. If storage is not shared, the + migration will transfer disk data over the network. +

+ {migrateVM.isError && ( +

+ Failed to migrate VM. Please try again. +

+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/vms/dialogs/MoveDiskDialog.tsx b/src/components/vms/dialogs/MoveDiskDialog.tsx new file mode 100644 index 0000000..22954d8 --- /dev/null +++ b/src/components/vms/dialogs/MoveDiskDialog.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useMoveDisk } from '@/hooks/useProxmox' +import type { ProxmoxVM, ProxmoxDisk } from '@/types/proxmox' + +interface MoveDiskDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + vm: ProxmoxVM + disk: ProxmoxDisk +} + +export function MoveDiskDialog({ open, onOpenChange, vm, disk }: MoveDiskDialogProps) { + const [targetStorage, setTargetStorage] = useState(disk.storage) + const moveDisk = useMoveDisk() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + moveDisk.mutate( + { + node: vm.node, + vmid: vm.vmid, + disk: disk.device, + storage: targetStorage, + }, + { + onSuccess: () => { + onOpenChange(false) + }, + }, + ) + } + + return ( + + + + Move Disk + + Move {disk.device} from {disk.storage} to a different storage + + +
+
+ + setTargetStorage(e.target.value)} + placeholder="local-lvm" + required + /> +
+ {moveDisk.isError && ( +

+ Failed to move disk. Please try again. +

+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/vms/dialogs/ResizeDiskDialog.tsx b/src/components/vms/dialogs/ResizeDiskDialog.tsx new file mode 100644 index 0000000..8d76650 --- /dev/null +++ b/src/components/vms/dialogs/ResizeDiskDialog.tsx @@ -0,0 +1,95 @@ +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useResizeDisk } from '@/hooks/useProxmox' +import type { ProxmoxVM, ProxmoxDisk } from '@/types/proxmox' + +interface ResizeDiskDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + vm: ProxmoxVM + disk: ProxmoxDisk +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +export function ResizeDiskDialog({ open, onOpenChange, vm, disk }: ResizeDiskDialogProps) { + const currentSizeGB = Math.round(disk.size / (1024 * 1024 * 1024)) + const [newSize, setNewSize] = useState(String(currentSizeGB)) + const resizeDisk = useResizeDisk() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + const sizeBytes = parseFloat(newSize) * 1024 * 1024 * 1024 + resizeDisk.mutate( + { + node: vm.node, + vmid: vm.vmid, + disk: disk.device, + size: sizeBytes, + }, + { + onSuccess: () => { + onOpenChange(false) + }, + }, + ) + } + + return ( + + + + Resize Disk + + Resize {disk.device} on {vm.name} (currently {formatBytes(disk.size)}) + + +
+
+ + setNewSize(e.target.value)} + required + /> +

+ Must be larger than current size ({currentSizeGB} GB) +

+
+ {resizeDisk.isError && ( +

+ Failed to resize disk. Please try again. +

+ )} + + + + +
+
+
+ ) +} diff --git a/src/components/vms/tabs/ConsoleTab.tsx b/src/components/vms/tabs/ConsoleTab.tsx new file mode 100644 index 0000000..aaaea1e --- /dev/null +++ b/src/components/vms/tabs/ConsoleTab.tsx @@ -0,0 +1,257 @@ +import { useState, useCallback, useRef } from 'react' +import { Button } from '@/components/ui/button' +import { + Maximize2, + Minimize2, + Monitor, + TerminalSquare, + RotateCw, + Loader2, + AlertCircle, +} from 'lucide-react' +import { VNCConsole } from '@/components/console/VNCConsole' +import { TerminalConsole } from '@/components/console/TerminalConsole' +import type { ProxmoxVM } from '@/types/proxmox' + +interface ConsoleTabProps { + vm: ProxmoxVM + connectionId: string +} + +type ConsoleStatus = 'idle' | 'connecting' | 'connected' | 'error' + +export function ConsoleTab({ vm, connectionId }: ConsoleTabProps) { + const [isFullscreen, setIsFullscreen] = useState(false) + const [status, setStatus] = useState('idle') + const [errorMessage, setErrorMessage] = useState(null) + const containerRef = useRef(null) + + const isVM = vm.type === 'qemu' + const canConnect = vm.status === 'running' + + const handleConnect = useCallback(() => { + setStatus('connecting') + setErrorMessage(null) + }, []) + + const handleError = useCallback((message: string) => { + setStatus('error') + setErrorMessage(message) + }, []) + + const handleDisconnect = useCallback(() => { + setStatus('idle') + setErrorMessage(null) + }, []) + + const handleRetry = useCallback(() => { + setStatus('idle') + setErrorMessage(null) + // Short delay then reconnect + setTimeout(() => { + setStatus('connecting') + }, 100) + }, []) + + const toggleFullscreen = useCallback(async () => { + if (!containerRef.current) return + + try { + if (!document.fullscreenElement) { + await containerRef.current.requestFullscreen() + setIsFullscreen(true) + } else { + await document.exitFullscreen() + setIsFullscreen(false) + } + } catch (err) { + console.error('Fullscreen toggle failed:', err) + } + }, []) + + const sendCtrlAltDel = useCallback(() => { + // Access the VNC RFB instance through the container + const container = containerRef.current?.querySelector('[data-vnc]') + if (container) { + // Dispatch a custom event that VNCConsole can listen for + container.dispatchEvent(new CustomEvent('vnc-ctrl-alt-del')) + } + }, []) + + return ( +
+ {/* Toolbar */} +
+
+ {isVM ? ( + + ) : ( + + )} + + {isVM ? 'VNC Console' : 'Terminal Console'} + + + ({vm.name} - {vm.type.toUpperCase()}) + +
+ +
+ {status === 'connecting' && ( +
+ + Connecting... +
+ )} + + {status === 'connected' && isVM && ( + + )} + + + + {status === 'connected' ? ( + + ) : status === 'error' ? ( + + ) : ( + + )} +
+
+ + {/* Console Area */} +
+ {/* Idle state - show placeholder */} + {status === 'idle' && ( +
+ {isVM ? ( + + ) : ( + + )} +
+

+ {isVM ? 'VNC Console' : 'Terminal Console'} +

+ {!canConnect ? ( +

+ VM must be running to access console +

+ ) : ( +

+ Click Connect to start a console session +

+ )} +
+
+ )} + + {/* Error state - show error overlay */} + {status === 'error' && ( +
+ +
+

+ Connection Failed +

+ {errorMessage && ( +

+ {errorMessage} +

+ )} + +
+
+ )} + + {/* Connecting state - show loading overlay */} + {(status === 'connecting' || status === 'connected') && ( + <> + {isVM ? ( + + ) : ( + + )} + + )} + + {status === 'connecting' && ( +
+ +
+

Connecting...

+

+ Establishing console connection to {vm.node} +

+
+
+ )} +
+
+ ) +} + +export type { ConsoleTabProps } diff --git a/src/components/vms/tabs/DisksTab.tsx b/src/components/vms/tabs/DisksTab.tsx new file mode 100644 index 0000000..89afe95 --- /dev/null +++ b/src/components/vms/tabs/DisksTab.tsx @@ -0,0 +1,217 @@ +import { useState } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { HardDrive, Plus, Pencil, Trash, ArrowRightLeft } from 'lucide-react' +import { useDisks, useRemoveDisk } from '@/hooks/useProxmox' +import { AddDiskDialog } from '@/components/vms/dialogs/AddDiskDialog' +import { ResizeDiskDialog } from '@/components/vms/dialogs/ResizeDiskDialog' +import { MoveDiskDialog } from '@/components/vms/dialogs/MoveDiskDialog' +import type { ProxmoxVM, ProxmoxDisk } from '@/types/proxmox' + +interface DisksTabProps { + vm: ProxmoxVM + connectionId: string +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +export function DisksTab({ vm, connectionId }: DisksTabProps) { + const { data: disks, isLoading, error } = useDisks(connectionId, vm.node, vm.vmid) + const removeDisk = useRemoveDisk() + + const [addDialogOpen, setAddDialogOpen] = useState(false) + const [resizeDisk, setResizeDisk] = useState(null) + const [moveDisk, setMoveDisk] = useState(null) + const [deleteDisk, setDeleteDisk] = useState(null) + + const handleDelete = () => { + if (!deleteDisk) return + removeDisk.mutate( + { node: vm.node, vmid: vm.vmid, disk: deleteDisk.device }, + { + onSuccess: () => { + setDeleteDisk(null) + }, + }, + ) + } + + if (isLoading) { + return ( +
+

Loading disks...

+
+ ) + } + + if (error) { + return ( +
+

Failed to load disks

+
+ ) + } + + const diskList = disks ?? [] + + return ( +
+
+
+

Disks

+

+ {diskList.length} disk{diskList.length !== 1 ? 's' : ''} attached +

+
+ +
+ + + + {diskList.length === 0 ? ( +
+ +

No disks attached

+
+ ) : ( +
+ + + + + + + + + + + + + {diskList.map((disk) => ( + + + + + + + + + ))} + +
DeviceSizeStorageFormatUsageActions
{disk.device}{formatBytes(disk.size)}{disk.storage} + + {disk.format} + + + {disk.usage ?? '-'} + +
+ + + +
+
+
+ )} +
+
+ + {/* Dialogs */} + + + {resizeDisk && ( + { + if (!open) setResizeDisk(null) + }} + vm={vm} + disk={resizeDisk} + /> + )} + + {moveDisk && ( + { + if (!open) setMoveDisk(null) + }} + vm={vm} + disk={moveDisk} + /> + )} + + {/* Delete confirmation */} + { if (!open) setDeleteDisk(null) }}> + + + Remove Disk + + Are you sure you want to remove {deleteDisk?.device} from {vm.name}? This action cannot be undone. + + + + + + + + +
+ ) +} diff --git a/src/components/vms/tabs/HardwareTab.tsx b/src/components/vms/tabs/HardwareTab.tsx new file mode 100644 index 0000000..d45b895 --- /dev/null +++ b/src/components/vms/tabs/HardwareTab.tsx @@ -0,0 +1,186 @@ +import { useState } from 'react' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Cpu, MemoryStick, Pencil, Save, X } from 'lucide-react' +import type { ProxmoxVM } from '@/types/proxmox' + +interface HardwareTabProps { + vm: ProxmoxVM + connectionId: string +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +export function HardwareTab({ vm }: HardwareTabProps) { + const [editOpen, setEditOpen] = useState(false) + const [formData, setFormData] = useState({ + name: vm.name, + description: vm.tags ?? '', + cores: vm.cpus.toString(), + memory: (vm.maxmem / (1024 * 1024 * 1024)).toFixed(1), + }) + + const handleSave = () => { + // TODO: Wire up to backend save mutation + setEditOpen(false) + } + + return ( +
+ + + Basic Configuration + + + +
+
+

Name

+

{vm.name}

+
+
+

VMID

+

{vm.vmid}

+
+
+

Type

+

{vm.type}

+
+
+

Tags

+

{vm.tags || 'None'}

+
+
+
+
+ + + + Compute + + +
+
+ +
+

CPU Cores

+

{vm.cpus} cores

+
+
+
+ +
+

Memory

+

+ {formatBytes(vm.maxmem)} +

+
+
+
+
+
+ + + + Boot + + +
+
+

Boot Order

+

scsi0, net0

+
+
+

BIOS

+

SeaBIOS (default)

+
+
+
+
+ + {/* Edit Dialog */} + + + + Edit Configuration + + Modify the basic hardware configuration for this VM. + + +
+
+ + setFormData({ ...formData, name: e.target.value })} + /> +
+
+ + setFormData({ ...formData, description: e.target.value })} + placeholder="e.g. production, web-server" + /> +
+
+
+ + setFormData({ ...formData, cores: e.target.value })} + /> +
+
+ + setFormData({ ...formData, memory: e.target.value })} + /> +
+
+
+ + + + +
+
+
+ ) +} diff --git a/src/components/vms/tabs/NetworkTab.tsx b/src/components/vms/tabs/NetworkTab.tsx new file mode 100644 index 0000000..f0ba49e --- /dev/null +++ b/src/components/vms/tabs/NetworkTab.tsx @@ -0,0 +1,210 @@ +import { useState } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Network, Plus, Pencil, Trash } from 'lucide-react' +import { useNetworkInterfaces, useRemoveNIC } from '@/hooks/useProxmox' +import { AddNICDialog } from '@/components/vms/dialogs/AddNICDialog' +import { EditNICDialog } from '@/components/vms/dialogs/EditNICDialog' +import type { ProxmoxVM, ProxmoxNetwork } from '@/types/proxmox' + +interface NetworkTabProps { + vm: ProxmoxVM + connectionId: string +} + +function formatNetworkRate(bytes: number): string { + if (bytes === 0) return '0 B/s' + const k = 1024 + const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +export function NetworkTab({ vm, connectionId }: NetworkTabProps) { + const { data: interfaces, isLoading, error } = useNetworkInterfaces(connectionId, vm.node, vm.vmid) + const removeNIC = useRemoveNIC() + + const [addDialogOpen, setAddDialogOpen] = useState(false) + const [editNic, setEditNic] = useState(null) + const [deleteNic, setDeleteNic] = useState(null) + + const handleDelete = () => { + if (!deleteNic) return + removeNIC.mutate( + { node: vm.node, vmid: vm.vmid, nic: deleteNic.name }, + { + onSuccess: () => { + setDeleteNic(null) + }, + }, + ) + } + + if (isLoading) { + return ( +
+

Loading network interfaces...

+
+ ) + } + + if (error) { + return ( +
+

Failed to load network interfaces

+
+ ) + } + + const nicList = interfaces ?? [] + + return ( +
+
+
+

Network Interfaces

+

+ {nicList.length} NIC{nicList.length !== 1 ? 's' : ''} attached + + I/O: {formatNetworkRate(vm.netin)} in / {formatNetworkRate(vm.netout)} out + +

+
+ +
+ + + + {nicList.length === 0 ? ( +
+ +

No network interfaces attached

+
+ ) : ( +
+ + + + + + + + + + + + + + {nicList.map((nic) => ( + + + + + + + + + + ))} + +
DeviceModelMAC AddressBridgeVLANFirewallActions
{nic.name} + + {nic.model} + + {nic.macaddr}{nic.bridge ?? '-'} + {nic.tag != null ? ( + {nic.tag} + ) : ( + '-' + )} + + {nic.firewall === 1 ? ( + + ) : ( + + )} + +
+ + +
+
+
+ )} +
+
+ + {/* Dialogs */} + + + {editNic && ( + { + if (!open) setEditNic(null) + }} + vm={vm} + nic={editNic} + /> + )} + + {/* Delete confirmation */} + { if (!open) setDeleteNic(null) }}> + + + Remove Network Interface + + Are you sure you want to remove {deleteNic?.name} from {vm.name}? This action cannot be undone. + + + + + + + + +
+ ) +} diff --git a/src/components/vms/tabs/OverviewTab.tsx b/src/components/vms/tabs/OverviewTab.tsx new file mode 100644 index 0000000..a5712d5 --- /dev/null +++ b/src/components/vms/tabs/OverviewTab.tsx @@ -0,0 +1,184 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Cpu, MemoryStick, HardDrive, Clock, Server, Globe, Tag } from 'lucide-react' +import type { ProxmoxVM } from '@/types/proxmox' + +interface OverviewTabProps { + vm: ProxmoxVM + connectionId: string +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function formatUptime(seconds: number): string { + if (seconds === 0) return 'N/A' + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const parts: string[] = [] + if (days > 0) parts.push(`${days}d`) + if (hours > 0) parts.push(`${hours}h`) + if (minutes > 0) parts.push(`${minutes}m`) + return parts.join(' ') || '< 1m' +} + +function formatNetworkRate(bytes: number): string { + if (bytes === 0) return '0 B/s' + const k = 1024 + const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` +} + +function ResourceBar({ label, used, total, icon: Icon }: { + label: string + used: number + total: number + icon: React.ComponentType<{ className?: string }> +}) { + const percent = total > 0 ? (used / total) * 100 : 0 + const displayUsed = label === 'CPU' ? used.toFixed(1) : formatBytes(used) + const displayTotal = label === 'CPU' ? `${total} cores` : formatBytes(total) + + return ( +
+
+
+ + {label} +
+ + {displayUsed} / {displayTotal} + +
+
+
90 + ? 'hsl(var(--destructive))' + : percent > 70 + ? 'hsl(38, 92%, 50%)' + : 'hsl(142, 71%, 45%)', + }} + /> +
+

+ {percent.toFixed(1)}% +

+
+ ) +} + +export function OverviewTab({ vm }: OverviewTabProps) { + return ( +
+ {/* Resource Usage */} + + + Resource Usage + + + + + + + + + {/* Quick Stats */} +
+ + + VMID + + + +
{vm.vmid}
+
+
+ + + + Type + + + +
{vm.type}
+
+
+ + + + Uptime + + + +
{formatUptime(vm.uptime)}
+
+
+ + + + Node + + + +
{vm.node}
+
+
+ + + + IP Address + + + +
192.168.1.{100 + (vm.vmid % 100)}
+

Mock data

+
+
+ + + + Network I/O + + + +
+
+ In: + {formatNetworkRate(vm.netin)} +
+
+ Out: + {formatNetworkRate(vm.netout)} +
+
+
+
+
+
+ ) +} diff --git a/src/components/vms/tabs/SnapshotsTab.tsx b/src/components/vms/tabs/SnapshotsTab.tsx new file mode 100644 index 0000000..db79a10 --- /dev/null +++ b/src/components/vms/tabs/SnapshotsTab.tsx @@ -0,0 +1,231 @@ +import { useState } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Camera, Plus, Trash, RotateCcw } from 'lucide-react' +import { useSnapshots, useDeleteSnapshot, useRollbackSnapshot } from '@/hooks/useProxmox' +import { CreateSnapshotDialog } from '@/components/vms/dialogs/CreateSnapshotDialog' +import type { ProxmoxVM, ProxmoxSnapshot } from '@/types/proxmox' + +interface SnapshotsTabProps { + vm: ProxmoxVM + connectionId: string +} + +function formatDate(timestamp: number): string { + return new Date(timestamp * 1000).toLocaleString() +} + +export function SnapshotsTab({ vm, connectionId }: SnapshotsTabProps) { + const { data: snapshots, isLoading, error } = useSnapshots(connectionId, vm.node, vm.vmid) + const deleteSnapshot = useDeleteSnapshot() + const rollbackSnapshot = useRollbackSnapshot() + + const [createDialogOpen, setCreateDialogOpen] = useState(false) + const [deleteTarget, setDeleteTarget] = useState(null) + const [rollbackTarget, setRollbackTarget] = useState(null) + + const handleDelete = () => { + if (!deleteTarget) return + deleteSnapshot.mutate( + { node: vm.node, vmid: vm.vmid, name: deleteTarget.name }, + { + onSuccess: () => { + setDeleteTarget(null) + }, + }, + ) + } + + const handleRollback = () => { + if (!rollbackTarget) return + rollbackSnapshot.mutate( + { node: vm.node, vmid: vm.vmid, name: rollbackTarget.name }, + { + onSuccess: () => { + setRollbackTarget(null) + }, + }, + ) + } + + if (isLoading) { + return ( +
+

Loading snapshots...

+
+ ) + } + + if (error) { + return ( +
+

Failed to load snapshots

+
+ ) + } + + const snapshotList = snapshots ?? [] + + return ( +
+
+
+

Snapshots

+

+ {snapshotList.length} snapshot{snapshotList.length !== 1 ? 's' : ''} +

+
+ +
+ + + + {snapshotList.length === 0 ? ( +
+ +

No snapshots

+

Create a snapshot to save the current VM state

+
+ ) : ( +
+ + + + + + + + + + + + + {snapshotList.map((snapshot) => ( + + + + + + + + + ))} + +
NameDescriptionCreatedVM StateParentActions
{snapshot.name} + {snapshot.description || '-'} + + {formatDate(snapshot.snaptime)} + + + {snapshot.vmstate === 1 ? 'Included' : 'Not included'} + + + {snapshot.parent ?? '-'} + +
+ + +
+
+
+ )} +
+
+ + {/* Create Snapshot Dialog */} + + + {/* Delete confirmation */} + { if (!open) setDeleteTarget(null) }}> + + + Delete Snapshot + + Are you sure you want to delete snapshot "{deleteTarget?.name}"? This action cannot be undone. + + + + + + + + + + {/* Rollback confirmation */} + { if (!open) setRollbackTarget(null) }}> + + + Rollback Snapshot + + Are you sure you want to rollback {vm.name} to snapshot "{rollbackTarget?.name}"? + {vm.status === 'running' && ( + + The VM will be stopped during the rollback process. + + )} + + + + + + + + +
+ ) +} diff --git a/src/hooks/useProxmox.ts b/src/hooks/useProxmox.ts new file mode 100644 index 0000000..f26b6d2 --- /dev/null +++ b/src/hooks/useProxmox.ts @@ -0,0 +1,602 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import * as api from '@/lib/tauri' +import { useConnectionStore } from '@/stores/connectionStore' +import type { AddDiskConfig, AddNICConfig, EditNICConfig, CreateSnapshotConfig, BackupJobConfig, RestoreConfig } from '@/types/proxmox' + +// Query keys +export const queryKeys = { + nodes: (connectionId: string) => ['nodes', connectionId], + vms: (connectionId: string) => ['vms', connectionId], + storage: (connectionId: string) => ['storage', connectionId], + storageContent: (connectionId: string, storage: string) => ['storageContent', connectionId, storage], + storageDetail: (connectionId: string, node: string, storage: string) => ['storageDetail', connectionId, node, storage], + tasks: (connectionId: string) => ['tasks', connectionId], + cluster: (connectionId: string) => ['cluster', connectionId], + disks: (connectionId: string, node: string, vmid: number) => ['disks', connectionId, node, vmid], + networks: (connectionId: string, node: string, vmid: number) => ['networks', connectionId, node, vmid], + snapshots: (connectionId: string, node: string, vmid: number) => ['snapshots', connectionId, node, vmid], + vncProxy: (connectionId: string, node: string, vmid: number) => ['vncProxy', connectionId, node, vmid], + termProxy: (connectionId: string, node: string, vmid: number) => ['termProxy', connectionId, node, vmid], + backupJobs: (connectionId: string) => ['backupJobs', connectionId], + backups: (connectionId: string, storage?: string) => ['backups', connectionId, storage], +} + +// Hooks for fetching data +export const useNodes = (connectionId: string | null) => { + return useQuery({ + queryKey: queryKeys.nodes(connectionId!), + queryFn: () => api.getNodes(connectionId!), + enabled: !!connectionId, + refetchInterval: 10000, // 10 seconds + }) +} + +export const useVMs = (connectionId: string | null) => { + return useQuery({ + queryKey: queryKeys.vms(connectionId!), + queryFn: () => api.getVMs(connectionId!), + enabled: !!connectionId, + refetchInterval: 15000, // 15 seconds + }) +} + +export const useStorage = (connectionId: string | null) => { + return useQuery({ + queryKey: queryKeys.storage(connectionId!), + queryFn: () => api.getStorage(connectionId!), + enabled: !!connectionId, + refetchInterval: 30000, // 30 seconds + }) +} + +export const useStorageContent = (connectionId: string | null, storage: string | null) => { + return useQuery({ + queryKey: queryKeys.storageContent(connectionId!, storage!), + queryFn: () => api.getStorageContent(connectionId!, storage!), + enabled: !!connectionId && !!storage, + refetchInterval: 30000, + }) +} + +export const useStorageDetail = (connectionId: string | null, node: string | null, storage: string | null) => { + return useQuery({ + queryKey: queryKeys.storageDetail(connectionId!, node!, storage!), + queryFn: () => api.getStorageDetail(connectionId!, node!, storage!), + enabled: !!connectionId && !!node && !!storage, + refetchInterval: 30000, + }) +} + +export const useTasks = (connectionId: string | null) => { + return useQuery({ + queryKey: queryKeys.tasks(connectionId!), + queryFn: () => api.getTasks(connectionId!), + enabled: !!connectionId, + refetchInterval: 30000, // 30 seconds + }) +} + +export const useClusterStatus = (connectionId: string | null) => { + return useQuery({ + queryKey: queryKeys.cluster(connectionId!), + queryFn: () => api.getClusterStatus(connectionId!), + enabled: !!connectionId, + refetchInterval: 30000, // 30 seconds + }) +} + +// Mutations for VM lifecycle +export const useStartVM = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ node, vmid }: { node: string; vmid: number }) => + api.startVM(activeConnectionId!, node, vmid), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + } + }, + }) +} + +export const useStopVM = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ node, vmid }: { node: string; vmid: number }) => + api.stopVM(activeConnectionId!, node, vmid), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + } + }, + }) +} + +export const useShutdownVM = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ node, vmid }: { node: string; vmid: number }) => + api.shutdownVM(activeConnectionId!, node, vmid), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + } + }, + }) +} + +export const useRebootVM = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ node, vmid }: { node: string; vmid: number }) => + api.rebootVM(activeConnectionId!, node, vmid), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + } + }, + }) +} + +export const useSuspendVM = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ node, vmid }: { node: string; vmid: number }) => + api.suspendVM(activeConnectionId!, node, vmid), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + } + }, + }) +} + +export const useResumeVM = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ node, vmid }: { node: string; vmid: number }) => + api.resumeVM(activeConnectionId!, node, vmid), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + } + }, + }) +} + +// Disk management hooks +export const useDisks = (connectionId: string, node: string, vmid: number) => { + return useQuery({ + queryKey: queryKeys.disks(connectionId, node, vmid), + queryFn: () => api.getDisks(connectionId, node, vmid), + enabled: !!connectionId && !!node && vmid > 0, + }) +} + +export const useAddDisk = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + config, + }: { + node: string + vmid: number + config: AddDiskConfig + }) => api.addDisk(activeConnectionId!, node, vmid, config), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.disks(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +export const useResizeDisk = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + disk, + size, + }: { + node: string + vmid: number + disk: string + size: number + }) => api.resizeDisk(activeConnectionId!, node, vmid, disk, size), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.disks(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +export const useRemoveDisk = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + disk, + }: { + node: string + vmid: number + disk: string + }) => api.removeDisk(activeConnectionId!, node, vmid, disk), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.disks(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +export const useMoveDisk = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + disk, + storage, + }: { + node: string + vmid: number + disk: string + storage: string + }) => api.moveDisk(activeConnectionId!, node, vmid, disk, storage), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.disks(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +// Network management hooks +export const useNetworkInterfaces = (connectionId: string, node: string, vmid: number) => { + return useQuery({ + queryKey: queryKeys.networks(connectionId, node, vmid), + queryFn: () => api.getNetworkInterfaces(connectionId, node, vmid), + enabled: !!connectionId && !!node && vmid > 0, + }) +} + +export const useAddNIC = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + config, + }: { + node: string + vmid: number + config: AddNICConfig + }) => api.addNIC(activeConnectionId!, node, vmid, config), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.networks(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +export const useEditNIC = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + nic, + config, + }: { + node: string + vmid: number + nic: string + config: EditNICConfig + }) => api.editNIC(activeConnectionId!, node, vmid, nic, config), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.networks(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +export const useRemoveNIC = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + nic, + }: { + node: string + vmid: number + nic: string + }) => api.removeNIC(activeConnectionId!, node, vmid, nic), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.networks(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +// Snapshot management hooks +export const useSnapshots = (connectionId: string, node: string, vmid: number) => { + return useQuery({ + queryKey: queryKeys.snapshots(connectionId, node, vmid), + queryFn: () => api.getSnapshots(connectionId, node, vmid), + enabled: !!connectionId && !!node && vmid > 0, + }) +} + +export const useCreateSnapshot = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + config, + }: { + node: string + vmid: number + config: CreateSnapshotConfig + }) => api.createSnapshot(activeConnectionId!, node, vmid, config), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.snapshots(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +export const useDeleteSnapshot = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + name, + }: { + node: string + vmid: number + name: string + }) => api.deleteSnapshot(activeConnectionId!, node, vmid, name), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.snapshots(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +export const useRollbackSnapshot = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + name, + }: { + node: string + vmid: number + name: string + }) => api.rollbackSnapshot(activeConnectionId!, node, vmid, name), + onSuccess: (_data, variables) => { + if (activeConnectionId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.snapshots(activeConnectionId, variables.node, variables.vmid), + }) + } + }, + }) +} + +// VM migration hooks +export const useMigrateVM = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ + node, + vmid, + targetNode, + online, + }: { + node: string + vmid: number + targetNode: string + online: boolean + }) => api.migrateVM(activeConnectionId!, node, vmid, targetNode, online), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + } + }, + }) +} + +// Console proxy hooks +export const useVNCProxy = (connectionId: string, node: string, vmid: number) => { + return useQuery({ + queryKey: queryKeys.vncProxy(connectionId, node, vmid), + queryFn: () => api.createVNCProxy(connectionId, node, vmid), + enabled: false, // Manual trigger only + retry: false, + }) +} + +export const useTermProxy = (connectionId: string, node: string, vmid: number) => { + return useQuery({ + queryKey: queryKeys.termProxy(connectionId, node, vmid), + queryFn: () => api.createTermProxy(connectionId, node, vmid), + enabled: false, // Manual trigger only + retry: false, + }) +} + +// Backup management hooks +export const useBackupJobs = (connectionId: string | null) => { + return useQuery({ + queryKey: queryKeys.backupJobs(connectionId!), + queryFn: () => api.getBackupJobs(connectionId!), + enabled: !!connectionId, + }) +} + +export const useBackups = (connectionId: string | null, storage?: string) => { + return useQuery({ + queryKey: queryKeys.backups(connectionId!, storage), + queryFn: () => api.getBackups(connectionId!, storage), + enabled: !!connectionId, + }) +} + +export const useCreateBackupJob = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ config }: { config: BackupJobConfig }) => + api.createBackupJob(activeConnectionId!, config), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.backupJobs(activeConnectionId) }) + } + }, + }) +} + +export const useUpdateBackupJob = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ id, config }: { id: string; config: BackupJobConfig }) => + api.updateBackupJob(activeConnectionId!, id, config), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.backupJobs(activeConnectionId) }) + } + }, + }) +} + +export const useDeleteBackupJob = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ id }: { id: string }) => + api.deleteBackupJob(activeConnectionId!, id), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.backupJobs(activeConnectionId) }) + } + }, + }) +} + +export const useRunBackup = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ config }: { config: BackupJobConfig }) => + api.runBackup(activeConnectionId!, config), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.tasks(activeConnectionId) }) + } + }, + }) +} + +export const useRestoreBackup = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ volid, config }: { volid: string; config: RestoreConfig }) => + api.restoreBackup(activeConnectionId!, volid, config), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(activeConnectionId) }) + queryClient.invalidateQueries({ queryKey: queryKeys.tasks(activeConnectionId) }) + } + }, + }) +} + +export const useDeleteBackup = () => { + const queryClient = useQueryClient() + const activeConnectionId = useConnectionStore((s) => s.activeConnectionId) + + return useMutation({ + mutationFn: ({ volid }: { volid: string }) => + api.deleteBackup(activeConnectionId!, volid), + onSuccess: () => { + if (activeConnectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.backups(activeConnectionId) }) + } + }, + }) +} diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts new file mode 100644 index 0000000..afa72de --- /dev/null +++ b/src/hooks/useWebSocket.ts @@ -0,0 +1,116 @@ +import { useEffect, useCallback, useState } from 'react' +import { useQueryClient } from '@tanstack/react-query' +import { queryKeys } from '@/hooks/useProxmox' +import * as api from '@/lib/tauri' + +interface UseWebSocketReturn { + isConnected: boolean + connect: (url: string) => Promise + disconnect: () => Promise +} + +export const useWebSocket = (connectionId: string | null): UseWebSocketReturn => { + const queryClient = useQueryClient() + const [isConnected, setIsConnected] = useState(false) + + const connect = useCallback( + async (url: string) => { + if (!connectionId) return + try { + await api.connectWebSocket(connectionId, url) + setIsConnected(true) + } catch (err) { + console.error('[useWebSocket] connect failed:', err) + setIsConnected(false) + } + }, + [connectionId], + ) + + const disconnect = useCallback(async () => { + if (!connectionId) return + try { + await api.disconnectWebSocket(connectionId) + } finally { + setIsConnected(false) + } + }, [connectionId]) + + // Listen for Tauri WebSocket events and invalidate relevant queries + useEffect(() => { + if (!connectionId) return + + let unlistenFns: Array<() => void> = [] + + const setupListeners = async () => { + const { listen } = await import('@tauri-apps/api/event') + + const unlistenTask = await listen('task-update', (event: { payload: { connection_id: string } }) => { + if (event.payload.connection_id === connectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.tasks(connectionId) }) + queryClient.invalidateQueries({ queryKey: queryKeys.vms(connectionId) }) + } + }) + unlistenFns.push(unlistenTask) + + const unlistenNode = await listen('node-status-change', (event: { payload: { connection_id: string } }) => { + if (event.payload.connection_id === connectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.nodes(connectionId) }) + queryClient.invalidateQueries({ queryKey: queryKeys.cluster(connectionId) }) + } + }) + unlistenFns.push(unlistenNode) + + const unlistenVM = await listen('vm-status-change', (event: { payload: { connection_id: string } }) => { + if (event.payload.connection_id === connectionId) { + queryClient.invalidateQueries({ queryKey: queryKeys.vms(connectionId) }) + } + }) + unlistenFns.push(unlistenVM) + } + + setupListeners() + + return () => { + unlistenFns.forEach((fn) => fn()) + unlistenFns = [] + } + }, [connectionId, queryClient]) + + // Check WebSocket connection status periodically + useEffect(() => { + if (!connectionId) { + setIsConnected(false) + return + } + + let interval: ReturnType | null = null + + const checkStatus = async () => { + try { + const connected = await api.isWebSocketConnected(connectionId) + setIsConnected(connected) + } catch { + setIsConnected(false) + } + } + + checkStatus() + interval = setInterval(checkStatus, 5000) + + return () => { + if (interval) clearInterval(interval) + } + }, [connectionId]) + + // Auto-disconnect on unmount + useEffect(() => { + return () => { + if (connectionId) { + api.disconnectWebSocket(connectionId).catch(() => {}) + } + } + }, [connectionId]) + + return { isConnected, connect, disconnect } +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..0c0085e --- /dev/null +++ b/src/index.css @@ -0,0 +1,66 @@ +@import 'tailwindcss'; + +@theme { + --color-background: oklch(1 0 0); + --color-foreground: oklch(0.145 0 0); + --color-card: oklch(1 0 0); + --color-card-foreground: oklch(0.145 0 0); + --color-popover: oklch(1 0 0); + --color-popover-foreground: oklch(0.145 0 0); + --color-primary: oklch(0.205 0 0); + --color-primary-foreground: oklch(0.985 0 0); + --color-secondary: oklch(0.97 0 0); + --color-secondary-foreground: oklch(0.205 0 0); + --color-muted: oklch(0.97 0 0); + --color-muted-foreground: oklch(0.556 0 0); + --color-accent: oklch(0.97 0 0); + --color-accent-foreground: oklch(0.205 0 0); + --color-destructive: oklch(0.577 0.245 27.325); + --color-destructive-foreground: oklch(0.577 0.245 27.325); + --color-border: oklch(0.922 0 0); + --color-input: oklch(0.922 0 0); + --color-ring: oklch(0.708 0 0); + --radius: 0.625rem; +} + +.dark { + --color-background: oklch(0.145 0 0); + --color-foreground: oklch(0.985 0 0); + --color-card: oklch(0.145 0 0); + --color-card-foreground: oklch(0.985 0 0); + --color-popover: oklch(0.145 0 0); + --color-popover-foreground: oklch(0.985 0 0); + --color-primary: oklch(0.985 0 0); + --color-primary-foreground: oklch(0.205 0 0); + --color-secondary: oklch(0.269 0 0); + --color-secondary-foreground: oklch(0.985 0 0); + --color-muted: oklch(0.269 0 0); + --color-muted-foreground: oklch(0.708 0 0); + --color-accent: oklch(0.269 0 0); + --color-accent-foreground: oklch(0.985 0 0); + --color-destructive: oklch(0.577 0.245 27.325); + --color-destructive-foreground: oklch(0.577 0.245 27.325); + --color-border: oklch(0.269 0 0); + --color-input: oklch(0.269 0 0); + --color-ring: oklch(0.439 0 0); +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +@keyframes toast-slide-in { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} diff --git a/src/lib/tauri.ts b/src/lib/tauri.ts new file mode 100644 index 0000000..cd14496 --- /dev/null +++ b/src/lib/tauri.ts @@ -0,0 +1,521 @@ +// Tauri IPC commands interface +// These functions call into the Rust backend via Tauri's invoke mechanism + +import type { ConnectionConfig, CertificateInfo } from '@/types/connection' +import type { + ProxmoxNode, + ProxmoxVM, + ProxmoxStorage, + ProxmoxStorageContent, + ProxmoxStorageDetail, + ProxmoxTask, + ProxmoxClusterStatus, + ProxmoxDisk, + ProxmoxNetwork, + ProxmoxSnapshot, + ProxmoxBackup, + ProxmoxBackupJob, + AddDiskConfig, + AddNICConfig, + EditNICConfig, + CreateSnapshotConfig, + BackupJobConfig, + RestoreConfig, + VNCProxyResponse, + TermProxyResponse, +} from '@/types/proxmox' + +// Check if we're running in Tauri +export const isTauri = () => { + return '__TAURI__' in window +} + +// Mock responses for development without Tauri +const mockResponse = (data: T): Promise => { + return new Promise((resolve) => setTimeout(() => resolve(data), 300)) +} + +// Connection management +export const addConnection = async (config: ConnectionConfig): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('add_connection', { config }) +} + +export const removeConnection = async (id: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('remove_connection', { id }) +} + +export const connectToServer = async (id: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('connect_to_server', { id }) +} + +export const disconnectFromServer = async (id: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('disconnect_from_server', { id }) +} + +export const getCertificateInfo = async (url: string): Promise => { + if (!isTauri()) { + return mockResponse({ + fingerprint: 'AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90', + issuer: 'Proxmox Virtual Environment', + subject: 'pve.local', + validFrom: '2024-01-01', + validTo: '2034-01-01', + selfSigned: true, + }) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_certificate_info', { url }) +} + +export const trustCertificate = async (id: string, fingerprint: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('trust_certificate', { id, fingerprint }) +} + +// API calls +export const getNodes = async (connectionId: string): Promise => { + if (!isTauri()) { + return mockResponse([ + { + node: 'pve1', + status: 'online', + cpu: 0.42, + maxcpu: 8, + mem: 16_000_000_000, + maxmem: 32_000_000_000, + disk: 200_000_000_000, + maxdisk: 500_000_000_000, + uptime: 86400, + level: '', + id: 'node/pve1', + type: 'node', + }, + ]) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_nodes', { connectionId }) +} + +export const getVMs = async (connectionId: string): Promise => { + if (!isTauri()) return mockResponse([]) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_vms', { connectionId }) +} + +export const getStorage = async (connectionId: string): Promise => { + if (!isTauri()) return mockResponse([]) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_storage', { connectionId }) +} + +export const getStorageContent = async ( + connectionId: string, + storage: string, +): Promise => { + if (!isTauri()) { + return mockResponse([ + { content: 'images', ctime: Date.now() / 1000, format: 'qcow2', size: 32 * 1024 * 1024 * 1024, volid: 'local-lvm:vm-100-disk-0' }, + { content: 'images', ctime: Date.now() / 1000, format: 'raw', size: 64 * 1024 * 1024 * 1024, volid: 'local-lvm:vm-101-disk-0' }, + { content: 'backup', ctime: Date.now() / 1000 - 86400, size: 5 * 1024 * 1024 * 1024, volid: 'local:backup/vzdump-qemu-100-2024_01_01-120000.vma.zst' }, + { content: 'iso', ctime: Date.now() / 1000 - 172800, size: 4 * 1024 * 1024 * 1024, volid: 'local:iso/ubuntu-22.04-desktop-amd64.iso' }, + ]) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_storage_content', { connectionId, storage }) +} + +export const getStorageDetail = async ( + connectionId: string, + node: string, + storage: string, +): Promise => { + if (!isTauri()) { + return mockResponse({ + storage, + type: 'lvmthin', + content: 'images,rootdir', + active: 1, + enabled: 1, + shared: 0, + used: 96 * 1024 * 1024 * 1024, + total: 200 * 1024 * 1024 * 1024, + avail: 104 * 1024 * 1024 * 1024, + node, + }) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_storage_detail', { connectionId, node, storage }) +} + +export const getTasks = async (connectionId: string): Promise => { + if (!isTauri()) return mockResponse([]) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_tasks', { connectionId }) +} + +export const getClusterStatus = async (connectionId: string): Promise => { + if (!isTauri()) { + return mockResponse({ + type: 'cluster', + name: 'home-lab', + id: 'cluster/home-lab', + nodes: [], + }) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_cluster_status', { connectionId }) +} + +// VM lifecycle +export const startVM = async (connectionId: string, node: string, vmid: number): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('start_vm', { connectionId, node, vmid }) +} + +export const stopVM = async (connectionId: string, node: string, vmid: number): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('stop_vm', { connectionId, node, vmid }) +} + +export const shutdownVM = async (connectionId: string, node: string, vmid: number): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('shutdown_vm', { connectionId, node, vmid }) +} + +export const rebootVM = async (connectionId: string, node: string, vmid: number): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('reboot_vm', { connectionId, node, vmid }) +} + +export const suspendVM = async (connectionId: string, node: string, vmid: number): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('suspend_vm', { connectionId, node, vmid }) +} + +export const resumeVM = async (connectionId: string, node: string, vmid: number): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('resume_vm', { connectionId, node, vmid }) +} + +// Disk management +export const getDisks = async ( + connectionId: string, + node: string, + vmid: number, +): Promise => { + if (!isTauri()) { + return mockResponse([ + { device: 'scsi0', size: 32 * 1024 * 1024 * 1024, storage: 'local-lvm', format: 'qcow2', usage: 'Root' }, + { device: 'scsi1', size: 64 * 1024 * 1024 * 1024, storage: 'local-lvm', format: 'qcow2' }, + ]) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_disks', { connectionId, node, vmid }) +} + +export const addDisk = async ( + connectionId: string, + node: string, + vmid: number, + config: AddDiskConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('add_disk', { connectionId, node, vmid, config }) +} + +export const resizeDisk = async ( + connectionId: string, + node: string, + vmid: number, + disk: string, + size: number, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('resize_disk', { connectionId, node, vmid, disk, size }) +} + +export const removeDisk = async ( + connectionId: string, + node: string, + vmid: number, + disk: string, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('remove_disk', { connectionId, node, vmid, disk }) +} + +export const moveDisk = async ( + connectionId: string, + node: string, + vmid: number, + disk: string, + storage: string, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('move_disk', { connectionId, node, vmid, disk, storage }) +} + +// Network management +export const getNetworkInterfaces = async ( + connectionId: string, + node: string, + vmid: number, +): Promise => { + if (!isTauri()) { + return mockResponse([ + { name: 'net0', model: 'virtio', macaddr: 'BC:24:11:AA:BB:CC', bridge: 'vmbr0', firewall: 1 }, + ]) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_network_interfaces', { connectionId, node, vmid }) +} + +export const addNIC = async ( + connectionId: string, + node: string, + vmid: number, + config: AddNICConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('add_nic', { connectionId, node, vmid, config }) +} + +export const editNIC = async ( + connectionId: string, + node: string, + vmid: number, + nic: string, + config: EditNICConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('edit_nic', { connectionId, node, vmid, nic, config }) +} + +export const removeNIC = async ( + connectionId: string, + node: string, + vmid: number, + nic: string, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('remove_nic', { connectionId, node, vmid, nic }) +} + +// Snapshot management +export const getSnapshots = async ( + connectionId: string, + node: string, + vmid: number, +): Promise => { + if (!isTauri()) return mockResponse([]) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_snapshots', { connectionId, node, vmid }) +} + +export const createSnapshot = async ( + connectionId: string, + node: string, + vmid: number, + config: CreateSnapshotConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('create_snapshot', { connectionId, node, vmid, config }) +} + +export const deleteSnapshot = async ( + connectionId: string, + node: string, + vmid: number, + name: string, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('delete_snapshot', { connectionId, node, vmid, name }) +} + +export const rollbackSnapshot = async ( + connectionId: string, + node: string, + vmid: number, + name: string, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('rollback_snapshot', { connectionId, node, vmid, name }) +} + +// VM migration +export const migrateVM = async ( + connectionId: string, + node: string, + vmid: number, + targetNode: string, + online: boolean, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('migrate_vm', { connectionId, node, vmid, targetNode, online }) +} + +// Console proxy +export const createVNCProxy = async ( + connectionId: string, + node: string, + vmid: number, +): Promise => { + if (!isTauri()) { + return mockResponse({ + ticket: 'mock-vnc-ticket-' + Date.now(), + port: 6000 + (vmid % 1000), + cert: '', + }) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('create_vnc_proxy', { connectionId, node, vmid }) +} + +export const createTermProxy = async ( + connectionId: string, + node: string, + vmid: number, +): Promise => { + if (!isTauri()) { + return mockResponse({ + ticket: 'mock-term-ticket-' + Date.now(), + port: 6100 + (vmid % 1000), + }) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('create_term_proxy', { connectionId, node, vmid }) +} + +export const getWebSocketURL = async ( + connectionId: string, + node: string, +): Promise => { + if (!isTauri()) { + return mockResponse(`wss://localhost:8006`) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_websocket_url', { connectionId, node }) +} + +// WebSocket management +export const connectWebSocket = async (connectionId: string, url: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('connect_websocket', { connectionId, url }) +} + +export const disconnectWebSocket = async (connectionId: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('disconnect_websocket', { connectionId }) +} + +export const isWebSocketConnected = async (connectionId: string): Promise => { + if (!isTauri()) return mockResponse(false) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('is_websocket_connected', { connectionId }) +} + +// Backup management +export const getBackupJobs = async (connectionId: string): Promise => { + if (!isTauri()) { + return mockResponse([ + { + id: 'job-1', + store: 'local', + schedule: '0 2 * * *', + all: 1, + enabled: 1, + mode: 'snapshot', + compress: 'zstd', + }, + ]) + } + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_backup_jobs', { connectionId }) +} + +export const getBackups = async ( + connectionId: string, + storage?: string, +): Promise => { + if (!isTauri()) return mockResponse([]) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('get_backups', { connectionId, storage }) +} + +export const createBackupJob = async ( + connectionId: string, + config: BackupJobConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('create_backup_job', { connectionId, config }) +} + +export const updateBackupJob = async ( + connectionId: string, + id: string, + config: BackupJobConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('update_backup_job', { connectionId, id, config }) +} + +export const deleteBackupJob = async (connectionId: string, id: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('delete_backup_job', { connectionId, id }) +} + +export const runBackup = async ( + connectionId: string, + config: BackupJobConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('run_backup', { connectionId, config }) +} + +export const restoreBackup = async ( + connectionId: string, + volid: string, + config: RestoreConfig, +): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('restore_backup', { connectionId, volid, config }) +} + +export const deleteBackup = async (connectionId: string, volid: string): Promise => { + if (!isTauri()) return mockResponse(undefined) + const { invoke } = await import('@tauri-apps/api/core') + return invoke('delete_backup', { connectionId, volid }) +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..fed2fe9 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/stores/connectionStore.ts b/src/stores/connectionStore.ts new file mode 100644 index 0000000..4dfc945 --- /dev/null +++ b/src/stores/connectionStore.ts @@ -0,0 +1,57 @@ +import { create } from 'zustand' +import type { ConnectionConfig, ConnectionStatus } from '@/types/connection' + +interface ConnectionState { + connections: ConnectionConfig[] + activeConnectionId: string | null + isLoading: boolean + error: string | null + + // Actions + addConnection: (config: ConnectionConfig) => void + removeConnection: (id: string) => void + updateConnection: (id: string, updates: Partial) => void + setActiveConnection: (id: string | null) => void + setConnectionStatus: (id: string, status: ConnectionStatus) => void + setLoading: (loading: boolean) => void + setError: (error: string | null) => void +} + +export const useConnectionStore = create((set) => ({ + connections: [], + activeConnectionId: null, + isLoading: false, + error: null, + + addConnection: (config) => + set((state) => ({ + connections: [...state.connections, config], + })), + + removeConnection: (id) => + set((state) => ({ + connections: state.connections.filter((c) => c.id !== id), + activeConnectionId: state.activeConnectionId === id ? null : state.activeConnectionId, + })), + + updateConnection: (id, updates) => + set((state) => ({ + connections: state.connections.map((c) => + c.id === id ? { ...c, ...updates } : c + ), + })), + + setActiveConnection: (id) => + set({ activeConnectionId: id }), + + setConnectionStatus: (id, status) => + set((state) => ({ + connections: state.connections.map((c) => + c.id === id ? { ...c, status } : c + ), + })), + + setLoading: (loading) => set({ isLoading: loading }), + + setError: (error) => set({ error }), +})) diff --git a/src/stores/uiStore.ts b/src/stores/uiStore.ts new file mode 100644 index 0000000..7be8433 --- /dev/null +++ b/src/stores/uiStore.ts @@ -0,0 +1,63 @@ +import { create } from 'zustand' + +type Theme = 'light' | 'dark' | 'system' + +interface UIState { + theme: Theme + sidebarCollapsed: boolean + commandPaletteOpen: boolean + + // Actions + setTheme: (theme: Theme) => void + toggleSidebar: () => void + setSidebarCollapsed: (collapsed: boolean) => void + setCommandPaletteOpen: (open: boolean) => void +} + +function applyTheme(theme: Theme) { + const root = document.documentElement + root.classList.remove('light', 'dark') + + if (theme === 'system') { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches + root.classList.add(prefersDark ? 'dark' : 'light') + } else { + root.classList.add(theme) + } + + localStorage.setItem('proxmoxdesktop-theme', theme) +} + +function getInitialTheme(): Theme { + const stored = localStorage.getItem('proxmoxdesktop-theme') + if (stored === 'light' || stored === 'dark' || stored === 'system') { + return stored + } + return 'system' +} + +// Apply theme on load +const initialTheme = getInitialTheme() +applyTheme(initialTheme) + +// Listen for system theme changes +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const currentTheme = getInitialTheme() + if (currentTheme === 'system') { + applyTheme('system') + } +}) + +export const useUIStore = create((set) => ({ + theme: initialTheme, + sidebarCollapsed: false, + commandPaletteOpen: false, + + setTheme: (theme) => { + applyTheme(theme) + set({ theme }) + }, + toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })), + setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }), + setCommandPaletteOpen: (open) => set({ commandPaletteOpen: open }), +})) diff --git a/src/types/connection.ts b/src/types/connection.ts new file mode 100644 index 0000000..2cbf5f4 --- /dev/null +++ b/src/types/connection.ts @@ -0,0 +1,45 @@ +// Connection configuration types + +export interface ConnectionConfig { + id: string + name: string + // Primary endpoint + primary: EndpointConfig + // Fallback endpoints for cluster failover + fallbacks: EndpointConfig[] + // Certificate trust + certFingerprint?: string + trusted: boolean + // Connection state + status: ConnectionStatus + // Cluster info (populated after connection) + clusterName?: string + isCluster: boolean +} + +export interface EndpointConfig { + url: string // e.g., "https://192.168.1.10:8006" + node?: string // Node name (for display) + token?: string // API token (stored in keyring, not in config) +} + +export type ConnectionStatus = + | 'disconnected' + | 'connecting' + | 'connected' + | 'failed' + | 'failover' + +export interface CertificateInfo { + fingerprint: string + issuer: string + subject: string + validFrom: string + validTo: string + selfSigned: boolean +} + +export interface ConnectionCredentials { + connectionId: string + apiToken: string +} diff --git a/src/types/novnc.d.ts b/src/types/novnc.d.ts new file mode 100644 index 0000000..b027eb0 --- /dev/null +++ b/src/types/novnc.d.ts @@ -0,0 +1,48 @@ +// Custom type declarations for @novnc/novnc v1.7.0 +// The @types/novnc package is outdated and doesn't match the modern API + +declare module '@novnc/novnc/core/rfb.js' { + export default class RFB extends EventTarget { + constructor(target: HTMLElement, url: string, options?: RFBOptions); + disconnect(): void; + sendCtrlAltDel(): void; + clipboardPasteFrom(text: string): void; + addEventListener( + type: K, + listener: (ev: RFBEventMap[K]) => void, + options?: boolean | AddEventListenerOptions, + ): void; + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions, + ): void; + removeEventListener( + type: K, + listener: (ev: RFBEventMap[K]) => void, + options?: boolean | EventListenerOptions, + ): void; + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions, + ): void; + } + + export interface RFBOptions { + credentials?: { password?: string; target?: string; securityType?: string }; + shared?: boolean; + repeaterID?: string; + wsProtocols?: string[]; + } + + export interface RFBEventMap { + connect: Event; + disconnect: CustomEvent<{ clean: boolean; reason: string }>; + credentialsrequired: CustomEvent<{ types: string[] }>; + clipboard: CustomEvent<{ text: string }>; + bell: Event; + focus: Event; + blur: Event; + } +} diff --git a/src/types/proxmox.ts b/src/types/proxmox.ts new file mode 100644 index 0000000..7395509 --- /dev/null +++ b/src/types/proxmox.ts @@ -0,0 +1,229 @@ +// Proxmox API types + +export interface ProxmoxNode { + node: string + status: 'online' | 'offline' + cpu: number + maxcpu: number + mem: number + maxmem: number + disk: number + maxdisk: number + uptime: number + level: string + id: string + type: 'node' +} + +export interface ProxmoxVM { + vmid: number + name: string + status: 'running' | 'stopped' | 'paused' | 'suspended' + type: 'qemu' | 'lxc' + node: string + cpu: number + cpus: number + mem: number + maxmem: number + disk: number + maxdisk: number + uptime: number + netin: number + netout: number + diskread: number + diskwrite: number + pid?: number + template?: number + lock?: string + tags?: string +} + +export interface ProxmoxStorage { + storage: string + type: string + content: string + active: number + enabled: number + shared: number + used: number + total: number + avail: number + node: string +} + +export interface ProxmoxStorageContent { + content: string + ctime: number + format?: string + size?: number + subtype?: string + volid: string +} + +export interface ProxmoxStorageDetail { + storage: string + type: string + content: string + active: number + enabled: number + shared: number + used: number + total: number + avail: number + node: string +} + +export interface ProxmoxTask { + upid: string + node: string + pid: number + pstart: number + starttime: number + endtime?: number + type: string + id: string + user: string + status?: string + exitstatus?: string +} + +export interface ProxmoxClusterStatus { + type: 'node' | 'cluster' + name: string + id: string + nodeid?: number + version?: number + local?: number + online?: number + nodes?: ClusterNode[] +} + +export interface ClusterNode { + name: string + nodeid: number + online: number + local?: number + ip?: string +} + +export interface ProxmoxSnapshot { + name: string + description: string + snaptime: number + vmstate: number + parent?: string +} + +export interface ProxmoxBackup { + volid: string + backupid: string + 'backup-type': string + 'backup-id': string + 'backup-time': number + storage: string + size: number + ctime: number +} + +export interface ProxmoxBackupJob { + id: string + store: string + schedule: string + all: number + enabled: number + node?: string + vmid?: string + compress?: string + mode?: string + quiet?: number +} + +export interface BackupJobConfig { + id?: string + storage: string + schedule: string + mode: 'snapshot' | 'stop' | 'suspend' + compression: 'zstd' | 'lz4' | 'gzip' | 'none' + all: boolean + vmid?: string + enabled: boolean + node?: string +} + +export interface RestoreConfig { + volid: string + node: string + storage: string + vmid?: number +} + +export interface ProxmoxDisk { + device: string + size: number + storage: string + format: string + usage?: string +} + +export interface AddDiskConfig { + storage: string + size: number + busType: 'scsi' | 'virtio' | 'ide' | 'sata' +} + +export interface ResizeDiskConfig { + disk: string + size: number +} + +export interface MoveDiskConfig { + disk: string + storage: string +} + +export interface ProxmoxNetwork { + name: string + model: string + macaddr: string + bridge?: string + tag?: number + firewall?: number + link_down?: number +} + +export interface AddNICConfig { + bridge: string + model: string + macaddr?: string + tag?: number + firewall?: boolean +} + +export interface EditNICConfig { + bridge?: string + model?: string + tag?: number + firewall?: boolean +} + +export interface CreateSnapshotConfig { + name: string + description?: string + vmstate?: boolean +} + +// Console proxy types +export interface VNCProxyResponse { + ticket: string + port: number + cert: string +} + +export interface TermProxyResponse { + ticket: string + port: number +} + +export interface WebSocketInfo { + url: string +} diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..dc04221 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Path aliases */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..8455dcb --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ae0dd89 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + // Tauri compatibility + clearScreen: false, + server: { + port: 1420, + strictPort: true, + }, + envPrefix: ['VITE_', 'TAURI_'], + build: { + target: process.env.TAURI_PLATFORM === 'windows' ? 'chrome105' : 'safari13', + minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, + sourcemap: !!process.env.TAURI_DEBUG, + }, +})