feat: add cluster discovery, failover, VM management, and console support
This commit is contained in:
@@ -1,53 +1,128 @@
|
||||
# ProxmoxDesktop
|
||||
|
||||
A cross-platform desktop application for managing Proxmox VE servers and clusters. Built with Tauri, React, and TypeScript.
|
||||
A cross-platform desktop client for managing Proxmox VE servers and clusters. The backend is Rust on Tauri 2; the UI is React 19 with TypeScript, Vite, and Tailwind CSS v4.
|
||||
|
||||
## 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
|
||||
### Connections and authentication
|
||||
|
||||
### 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
|
||||
- Manage multiple simultaneous connections to servers and clusters.
|
||||
- Authenticate with an API token or with a username and password. Password mode uses ticket-based auth (PVEAuthCookie plus CSRF token).
|
||||
- Credentials live in the OS keyring. API tokens never touch disk.
|
||||
- Connections persist to `{app-config-dir}/proxmoxdesktop/connections.json`. The last active connection reconnects on launch.
|
||||
|
||||
### Cluster support
|
||||
|
||||
- On connect, the backend queries `/nodes` and `/cluster/status` and derives an endpoint URL for every cluster node, keeping the connection's scheme and port (default 8006) with the node's cluster IP (or name) as the host. The connected node is marked primary and the node list is stored with the connection.
|
||||
- API requests route through an ordered, deduplicated endpoint list: the primary endpoint, configured fallbacks, then the discovered nodes. A connection refused, timeout, DNS, or transport failure retries the request on the next endpoint. Status is `connected` when the primary serves, `failover` while a fallback serves, and `failed` when every endpoint is unreachable.
|
||||
- The frontend polls connection status every 10 seconds. The sidebar lists the cluster nodes with a live status dot per node (primary marked), shows a "Failover: {url}" note with an amber banner while on a fallback, and "Offline" when disconnected or failed.
|
||||
- Connecting to another node of the same cluster (matched by cluster id) merges instead of duplicating: the new node's URL joins the existing connection as a fallback endpoint, the duplicate connection is removed, the active connection switches, and a toast explains what happened.
|
||||
|
||||
### Certificate handling (TOFU)
|
||||
|
||||
- On first connect, the app captures the server's TLS certificate and shows its SHA-256 fingerprint for confirmation.
|
||||
- The fingerprint is pinned and verified on every subsequent connect. A per-connection "accept anyway" option covers cases where you knowingly replaced the server certificate.
|
||||
|
||||
### Dashboard and monitoring
|
||||
|
||||
- Cluster overview with a node health grid, resource gauges, an activity feed, and quick actions.
|
||||
- Per-node view: resource usage, system information, and the VMs and containers on the node.
|
||||
|
||||
### VMs and containers (QEMU and LXC)
|
||||
|
||||
- List and filter VMs and containers.
|
||||
- Lifecycle actions: start, stop, shutdown, reboot, suspend, resume, migrate.
|
||||
- VM detail with overview, hardware, disks, network, snapshots, and console tabs.
|
||||
|
||||
### Storage and networking
|
||||
|
||||
- Disk management: list, add, resize, remove, move.
|
||||
- Network interface management: list, add, edit, remove.
|
||||
- Storage overview and per-storage detail.
|
||||
|
||||
### Snapshots and backups
|
||||
|
||||
- Snapshot list, create, delete, rollback.
|
||||
- Backup jobs: list, create, edit, delete, run now. Restore or delete existing backups.
|
||||
|
||||
### Console
|
||||
|
||||
- noVNC for VMs and an xterm.js terminal for containers, with fullscreen and Ctrl+Alt+Del support.
|
||||
|
||||
### Tasks and events
|
||||
|
||||
- Task list with status filtering and a running-task indicator.
|
||||
- A WebSocket relay in the backend forwards task, node, and VM events to the frontend. Polling remains the fallback.
|
||||
|
||||
### Interface
|
||||
|
||||
- Command palette (Ctrl/Cmd+K).
|
||||
- System tray with a quick menu: show/hide, connection list, quit.
|
||||
- Dark-first design with Geist for UI text, JetBrains Mono for data, and a single muted-orange accent. Light, dark, and system themes.
|
||||
|
||||
## 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
|
||||
- React 19, TypeScript
|
||||
- Vite, Tailwind CSS v4
|
||||
- Radix UI-based components (shadcn-style)
|
||||
- TanStack Query (server state), Zustand (client state)
|
||||
- lucide-react icons, Recharts gauges, noVNC, xterm.js
|
||||
|
||||
### Backend
|
||||
|
||||
- Tauri 2
|
||||
- reqwest with rustls for HTTP, keyring for the OS keyring, tokio for async
|
||||
- rustls, x509-cert, and sha2 for certificate capture and fingerprinting
|
||||
- tokio-tungstenite for the WebSocket relay
|
||||
|
||||
### Development and testing
|
||||
|
||||
- httpmock, rcgen, and tempfile for Rust tests
|
||||
- oxlint for frontend linting
|
||||
|
||||
## Architecture
|
||||
|
||||
### Frontend to backend
|
||||
|
||||
The React frontend calls Rust commands over Tauri IPC. The backend proxies them to the Proxmox API over HTTPS.
|
||||
|
||||
```
|
||||
React component
|
||||
→ Tauri invoke
|
||||
→ Rust command
|
||||
→ Proxmox API ({base}/api2/json{path})
|
||||
```
|
||||
|
||||
### API core
|
||||
|
||||
A Rust `ConnectionManager` owns one HTTP client per connection. A shared `api_request` core builds the `{base}/api2/json{path}` URL, injects auth (`PVEAPIToken` header for token mode; `PVEAuthCookie` and `CSRFPreventionToken` for password mode), unwraps the `{data}` envelope, and maps errors to the frontend.
|
||||
|
||||
The same core routes each request through an ordered, deduplicated endpoint list: the primary endpoint, configured fallbacks, then the discovered cluster nodes. On a connect, timeout, DNS, or transport failure it retries on the next endpoint, and the connection's runtime status tracks whether the primary (`connected`), a fallback (`failover`), or no endpoint (`failed`) served the last request.
|
||||
|
||||
Tauri commands cover: connection add/remove/update/load, connect/disconnect, set active; password and token login, logout, stored credentials; certificate info and trust; nodes, VMs, storage, storage content and detail, tasks, cluster status; QEMU and LXC lifecycle and migration; disks, NICs, snapshots; VNC and terminal proxies; websocket URL; backup jobs and restore; and the tray menu.
|
||||
|
||||
### TLS
|
||||
|
||||
Proxmox nodes typically run self-signed certificates, so the transport accepts them and the app enforces trust itself. On first connect the certificate is captured in `src-tauri/src/tls.rs`, its SHA-256 fingerprint is shown for confirmation, and the fingerprint is pinned and checked on every later connect.
|
||||
|
||||
Fingerprint pinning runs at connect time against the node you connected to. Fallback and discovered endpoints are used only in degraded failover mode and get no pin check of their own; the transport already accepts their self-signed certificates. This is a deliberate trade-off to keep failover automatic in home-lab setups.
|
||||
|
||||
### Real-time events
|
||||
|
||||
A WebSocket manager connects to the Proxmox event API and relays task, node, and VM events as Tauri events. The frontend falls back to polling when the socket is not connected.
|
||||
|
||||
### State management
|
||||
|
||||
TanStack Query handles server state (fetching, caching, refetching). Zustand handles client state (active connection, UI, preferences).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### System Dependencies
|
||||
### System dependencies
|
||||
|
||||
#### Linux (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
@@ -64,35 +139,30 @@ sudo apt install -y \
|
||||
```
|
||||
|
||||
#### macOS
|
||||
```bash
|
||||
# Install Xcode Command Line Tools
|
||||
xcode-select --install
|
||||
|
||||
# Install Homebrew dependencies
|
||||
```bash
|
||||
xcode-select --install
|
||||
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
|
||||
```
|
||||
- Install Visual Studio Build Tools and select "Desktop development with C++".
|
||||
- Install Rust via rustup: `winget install Rustlang.Rustup`
|
||||
|
||||
### Node.js
|
||||
|
||||
Node.js 20 or newer (nvm recommended):
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -100,166 +170,163 @@ source $HOME/.cargo/env
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd ProxmoxDesktop
|
||||
|
||||
# Install Node.js dependencies
|
||||
npm install
|
||||
|
||||
# Install Tauri CLI globally (optional)
|
||||
npm install -g @tauri-apps/cli
|
||||
```
|
||||
|
||||
The Tauri CLI is a devDependency, so `npm run tauri ...` works without a global install.
|
||||
|
||||
## Development
|
||||
|
||||
### Run in Development Mode
|
||||
### Run in the browser (mock data)
|
||||
|
||||
```bash
|
||||
# Start the Vite dev server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
# In a separate terminal, run the Tauri app
|
||||
Starts the Vite dev server. The frontend runs in a browser with mock data and no Tauri backend. Useful for UI work.
|
||||
|
||||
### Run the desktop app
|
||||
|
||||
```bash
|
||||
npm run tauri dev
|
||||
```
|
||||
|
||||
The app will automatically reload when you make changes to the frontend or backend code.
|
||||
Starts the full app. Frontend and backend changes reload automatically.
|
||||
|
||||
### Build for Production
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# Build the application
|
||||
npm run tauri build
|
||||
npm run build # type-check and bundle the frontend
|
||||
npm run tauri build # production bundle in src-tauri/target/release/bundle/
|
||||
```
|
||||
|
||||
The built application will be in `src-tauri/target/release/bundle/`.
|
||||
|
||||
### Run Tests
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
# Run linter
|
||||
npm run lint
|
||||
|
||||
# Type check
|
||||
npm run tsc --noEmit
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
64 Rust tests cover the API client (against mocked HTTP responses via httpmock), TLS capture and pinning, connection persistence, and every endpoint group. The frontend is exercised in the browser mock mode described above.
|
||||
|
||||
### Verifying against a real server
|
||||
|
||||
The backend was developed against the documented Proxmox VE API using mocked HTTP responses. There is no live-server test suite yet. To verify against real infrastructure:
|
||||
|
||||
1. Run `cargo test`.
|
||||
2. Run `npm run tauri dev`.
|
||||
3. Add a connection to a real Proxmox node (`https://host:8006`).
|
||||
4. Confirm the certificate fingerprint matches the one shown in the Proxmox web UI (Datacenter → Options, or the node's `/etc/pve/local/pve-ssl.pem`).
|
||||
5. Spot-check VM lifecycle actions, storage, backups, and the console.
|
||||
|
||||
Cluster discovery, failover, and same-cluster merging are covered by Rust tests using mocked HTTP (httpmock) and connection-refused simulations on closed ports. To verify them on real hardware, connect to one node of a multi-node cluster and confirm the sidebar lists the other nodes. Stop the connected node's API service, or block its port 8006 with a firewall rule, and check that the connection flips to failover and data keeps loading.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Proxmox API token
|
||||
|
||||
1. In the Proxmox web UI, go to Datacenter → Permissions → API Tokens.
|
||||
2. Click Add.
|
||||
3. Select a user (for example, `root@pam`).
|
||||
4. Enter a token ID (for example, `desktop`).
|
||||
5. Uncheck Privilege Separation for full access.
|
||||
6. Copy the token. It looks like `user@realm!tokenid=secret`.
|
||||
|
||||
### Connection settings
|
||||
|
||||
When adding a connection you need:
|
||||
|
||||
- **Name**: a friendly name for the connection
|
||||
- **Server URL**: for example `https://192.168.1.10:8006`
|
||||
- **Auth**: an API token, or a username and password (stored in the OS keyring; sessions use ticket auth)
|
||||
|
||||
## 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
|
||||
│ │ ├── ui/ # base components (buttons, dialogs, toasts)
|
||||
│ │ ├── layout/ # sidebar, dashboard shell
|
||||
│ │ ├── connections/ # connection manager and dialog
|
||||
│ │ ├── dashboard/ # node health grid, gauges, activity feed, quick actions
|
||||
│ │ ├── nodes/ # node list and detail
|
||||
│ │ ├── vms/ # VM/container lists, detail tabs
|
||||
│ │ ├── console/ # noVNC and xterm consoles
|
||||
│ │ ├── storage/ # storage overview and detail
|
||||
│ │ ├── backups/ # backup jobs and backups
|
||||
│ │ ├── tasks/ # task list and status bar
|
||||
│ │ ├── settings/ # settings page, theme switcher
|
||||
│ │ └── command/ # command palette
|
||||
│ ├── hooks/ # useProxmox, useWebSocket (TanStack Query)
|
||||
│ ├── stores/ # Zustand stores (connections, UI)
|
||||
│ ├── lib/ # tauri.ts IPC bindings, format helpers
|
||||
│ ├── types/ # TypeScript types
|
||||
│ ├── App.tsx # Main app component
|
||||
│ ├── main.tsx # Entry point
|
||||
│ └── index.css # Global styles
|
||||
│ ├── App.tsx
|
||||
│ ├── main.tsx
|
||||
│ └── index.css
|
||||
├── src-tauri/ # Tauri/Rust backend
|
||||
│ ├── src/
|
||||
│ │ ├── main.rs # Entry point
|
||||
│ │ ├── lib.rs # Tauri commands and app setup
|
||||
│ │ ├── connection.rs # Connection manager
|
||||
│ │ ├── main.rs # binary entry point
|
||||
│ │ ├── lib.rs # Tauri commands, app setup, tray menu
|
||||
│ │ ├── connection.rs # ConnectionManager, api_request core
|
||||
│ │ ├── 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
|
||||
│ │ ├── tls.rs # certificate capture and TOFU pinning
|
||||
│ │ ├── websocket.rs # WebSocket event relay
|
||||
│ │ └── error.rs # error mapping
|
||||
│ ├── tests/ # integration tests (httpmock, rcgen, tempfile)
|
||||
│ │ ├── api_client.rs
|
||||
│ │ ├── api_commands.rs
|
||||
│ │ ├── api_console.rs
|
||||
│ │ ├── api_disk_network.rs
|
||||
│ │ ├── api_snapshots_backups.rs
|
||||
│ │ ├── persistence.rs
|
||||
│ │ └── tls.rs
|
||||
│ ├── capabilities/ # Tauri permissions
|
||||
│ ├── Cargo.toml
|
||||
│ └── tauri.conf.json
|
||||
├── package.json
|
||||
├── vite.config.ts
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
## 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
|
||||
### 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
|
||||
### macOS: code signing
|
||||
|
||||
For development, you may need to allow the app in System Preferences → Security & Privacy.
|
||||
For development builds, allow the app in System Settings → Privacy & Security when prompted.
|
||||
|
||||
### Windows: Build Errors
|
||||
### Windows: build errors
|
||||
|
||||
Make sure you have:
|
||||
- Visual Studio Build Tools with "Desktop development with C++"
|
||||
- WebView2 (usually pre-installed on Windows 10/11)
|
||||
Make sure you have Visual Studio Build Tools with "Desktop development with C++" and WebView2 (preinstalled on Windows 10 and 11).
|
||||
|
||||
### Self-Signed Certificates
|
||||
### Self-signed certificates
|
||||
|
||||
The app will prompt you to trust self-signed certificates on first connection. The certificate fingerprint is stored for future connections.
|
||||
On first connect the app shows the server certificate's SHA-256 fingerprint. Compare it against the fingerprint in the Proxmox web UI before trusting it. If the certificate changes later, the app blocks the connection until you confirm the new fingerprint. Use the "accept anyway" option only when you intentionally replaced the server certificate.
|
||||
|
||||
## 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
|
||||
Contributions are welcome. Please:
|
||||
|
||||
1. Fork the repository.
|
||||
2. Create a feature branch.
|
||||
3. Make your changes.
|
||||
4. Run `npm run lint` and `cargo test`, 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
|
||||
|
||||
Reference in New Issue
Block a user