feat: Implement UVM Dashboard with frontend and backend integration

- Added dashboard.js for frontend functionality, including VM management and system health monitoring.
- Created dashboard route to serve the dashboard HTML.
- Introduced diagnostics route for token-protected host diagnostics.
- Enhanced VMs router with bulk actions, VM log retrieval, and port management.
- Updated lifecycle service to read VM log tails with truncation handling.
- Integrated static file serving for dashboard assets in the FastAPI server.
- Redirect root URL to the dashboard for improved user experience.
This commit is contained in:
2026-09-19 10:50:29 +05:30
parent 3d7af4adb8
commit b3b1b527e1
17 changed files with 2285 additions and 69 deletions
+43 -15
View File
@@ -13,8 +13,10 @@ general-purpose Python library or a multi-host control plane.
UVM installs host/guest assets, provisions private disks, and manages named
VMs and private IPv4 TCP allowlists on one host. The CLI offers retained-disk
start/stop/restart, deletion, fixed-target bulk operations, inspection, SSH,
logs, doctor, and explicit port edits/sync. The optional API exposes only the
existing health/install/create/list/inspect/stop/delete operations.
logs, doctor, and explicit port edits/sync. The optional API and local dashboard
expose health, installation, VM lifecycle controls, and managed port-policy
controls, host diagnostics, bounded VM-log tails, and fixed-target bulk
operations. Interactive SSH remains CLI-only.
There is no autostart or background reconciler, multi-host control plane,
tenancy/quotas, or UVM-provided Jailer/cgroup/namespace hardening. Private port
@@ -80,7 +82,10 @@ Packet integration tests must remain inside disposable isolated namespaces.
│ ├── lifecycle.py # Lifecycle, bulk operations, recovery, port intent
│ ├── api_models.py # Pydantic HTTP request/response models
│ ├── server.py # FastAPI factory and Uvicorn launcher
│ ├── dashboard/ # Packaged local dashboard HTML, CSS, and JS
│ ├── routers/
│ │ ├── dashboard.py # Dashboard document route
│ │ ├── diagnostics.py # Host diagnostic endpoint
│ │ ├── dependencies.py # API application/token dependencies
│ │ ├── health.py # Liveness endpoint
│ │ ├── installation.py # Installation endpoint
@@ -158,9 +163,12 @@ checks, not per-VM repair or guest application probes.
### HTTP Path
Routers validate the token and Pydantic request, invoke application services,
and render whitelisted response models. The CLI expansion does not imply HTTP
endpoint parity: start/restart, port edits/sync, bulk operations, logs, doctor,
and interactive SSH have no HTTP endpoints.
and render whitelisted response models. Lifecycle, managed port-policy,
diagnostics, and bounded log-tail operations have explicit HTTP routes for the
dashboard. Bulk operations have a dedicated fixed-target HTTP route; interactive
SSH has no HTTP endpoint. The public dashboard shell does not bypass token
validation: it stores a supplied token only for the active browser session and
sends it with management requests.
Use only `uvm --serve` or `./uvm.py --serve` to launch the API. The
`create_api()` factory requires an explicit host specifically to prevent it
@@ -645,13 +653,22 @@ or secret manager when that matters.
| Method | Route | Router | Notes |
|---|---|---|---|
| `GET` | `/` | `server.py` | Redirect to `/dashboard` |
| `GET` | `/dashboard` | `dashboard.py` | Public static dashboard shell |
| `GET` | `/health` | `health.py` | Unauthenticated liveness response |
| `GET` | `/diagnostics` | `diagnostics.py` | Host readiness checks |
| `POST` | `/install` | `installation.py` | `{ "force": false }` body |
| `GET` | `/vms` | `vms.py` | List persisted VM records and observed status |
| `POST` | `/vms` | `vms.py` | `cpu`, `ram`, `guest_ip`, `username`, `password`, `name`, `ports`, `no_default_ports` |
| `POST` | `/vms/actions` | `vms.py` | Fixed-ID snapshot bulk lifecycle action |
| `GET` | `/vms/{identifier}` | `vms.py` | Inspect by exact ID, name, or guest IP |
| `POST` | `/vms/{identifier}/stop` | `vms.py` | Stop while retaining private disk |
| `DELETE` | `/vms/{identifier}` | `vms.py` | Remove runtime files and state |
| `POST` | `/vms/{identifier}/start` | `vms.py` | Boot a retained private disk |
| `POST` | `/vms/{identifier}/restart` | `vms.py` | Restart; optional `timeout` query parameter |
| `POST` | `/vms/{identifier}/stop` | `vms.py` | Stop; optional `timeout` query parameter |
| `DELETE` | `/vms/{identifier}` | `vms.py` | Remove runtime files/state; optional `timeout` query parameter |
| `POST` | `/vms/{vm_id}/ports` | `vms.py` | Exact ID only; body has `mode` and `ports` |
| `POST` | `/vms/{vm_id}/ports/sync` | `vms.py` | Exact ID only; reapply managed policy |
| `GET` | `/vms/{identifier}/logs` | `vms.py` | Bounded non-following log tail |
All management routes use `get_authorized_application()` and require:
@@ -671,14 +688,24 @@ unmanaged VM from an omitted policy. Names share CLI validation.
VM responses include `name`, `ports`, `policy_status`, and `operation` alongside
resource/status fields and username. The schema excludes password and
disk/socket/config/log path fields; error messages may still contain paths.
`ports: null` in a legacy response still means unmanaged. The existing
stop/delete routes use service defaults; no timeout/dry-run/bulk HTTP schemas
were added. Typed not-found/conflict errors map to 404/409.
`ports: null` in a legacy response still means unmanaged. Start/restart/stop
and port-policy mutations report the resulting policy status. Restart, stop,
and delete accept an optional finite non-negative `timeout` query parameter;
there is no dry-run HTTP schema. Port mutation bodies use
`{"mode":"set"|"open"|"close", "ports":[...]}` and always resolve the
target by exact ID to prevent name/IP reuse. A legacy policy must be set while
stopped before it can be started or synced. Typed not-found/conflict errors map
to 404/409.
There are **no new start/restart, ports, or bulk HTTP endpoints**. The API also
does not expose logs, doctor, interactive SSH, raw Firecracker calls, arbitrary
command execution, or host path access. Public `/docs`, `/redoc`, and
`/openapi.json` are informational, not privileged management routes.
Log reads accept a `tail` query parameter from 0 through 1000 and never follow
the file. `LifecycleService.read_log_tail()` reads at most the final 256 KiB and
marks a response truncated when an oversized line or that bound prevents a full
tail. Bulk bodies use an action plus either explicit identifiers or `all_vms`;
the router resolves them before `run_many()` executes the fixed IDs under one
operation lock. The API does not expose interactive SSH, raw Firecracker calls,
arbitrary command execution, or host path access. Public `/`, `/dashboard`,
`/docs`, `/redoc`, and `/openapi.json` do not perform privileged management
requests.
## 14. Configuration Reference
@@ -810,7 +837,8 @@ on an isolated Linux host:
`get_authorized_application()`, and register new routers in `create_api()`.
Add API tests and update all route tables. New CLI commands do not implicitly
authorize new HTTP endpoints; `/health` remains the only unauthenticated
operational route.
operational API route, while `/` and `/dashboard` only serve the public
dashboard shell.
- Host primitives: use the specific network/firewall/install/process module
and `CommandRunner` for external commands. Define ownership, idempotence,
and compensation before invoking a primitive from lifecycle code. Use fake