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:
+43
-15
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user