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
+130 -19
View File
@@ -602,11 +602,12 @@ inventing identity fields in `state.json`.
## 10. Use the FastAPI Server
The API uses the same local lifecycle services, but exposes only the existing
health/install/create/list/inspect/stop/delete endpoints. There are no new
start/restart, port-management, or bulk HTTP endpoints; SSH, logs, and doctor
also remain CLI-only. The server does not autostart VMs or reconcile rules,
and is not safe to expose directly to the public internet.
The API uses the same local lifecycle services and exposes health, installation,
VM lifecycle, diagnostics, bounded VM-log tails, and managed private-port
controls. The local dashboard at `/dashboard` uses those same endpoints. There
API also supports fixed-target bulk actions; interactive SSH remains CLI-only.
The server does not autostart VMs or reconcile rules, and is not safe to expose
directly to the public internet.
### Start a Local Server
@@ -620,15 +621,18 @@ sudo env \
./uvm.py --serve --host 127.0.0.1 --port 8000
```
Liveness is at `http://127.0.0.1:8000/health`; OpenAPI docs are at
Liveness is at `http://127.0.0.1:8000/health`; the dashboard is at
`http://127.0.0.1:8000/dashboard`; OpenAPI docs are at
`http://127.0.0.1:8000/docs`. The default bind is `127.0.0.1:8000`.
The API token is required even for a loopback server. All VM and installation
routes require it. FastAPI's informational `/docs`, `/redoc`, and
`/openapi.json` endpoints are also available without a token; they do not
perform host mutations. Tokens must be visible ASCII with no whitespace.
Use a protected environment file or secret manager rather than recording a
real token in shell history; do not share tokens or guest passwords in reports.
The API token is required even for a loopback server. All VM, diagnostics, and
installation routes require it. The dashboard shell, liveness route, and FastAPI's
informational `/docs`, `/redoc`, and `/openapi.json` endpoints are available
without a token; they do not perform host mutations. The dashboard keeps an
entered token only for the active browser session. Tokens must be visible ASCII
with no whitespace. Use a protected environment file or secret manager rather
than recording a real token in shell history; do not share tokens or guest
passwords in reports.
### Start a TLS Server
@@ -675,16 +679,26 @@ curl --cacert /path/to/ca.pem \
| Method | Path | Operation |
|---|---|---|
| `GET` | `/` | Redirect to `/dashboard` |
| `GET` | `/dashboard` | Serve the local management dashboard |
| `GET` | `/health` | Unauthenticated liveness |
| `GET` | `/diagnostics` | Run host readiness checks |
| `POST` | `/install` | Install/refresh with a `force` boolean |
| `GET` | `/vms` | List VM responses |
| `POST` | `/vms` | Create and boot, HTTP 201 on success |
| `POST` | `/vms/actions` | Run selected/all VM lifecycle actions from a fixed snapshot |
| `GET` | `/vms/{identifier}` | Inspect by exact ID, name, or guest IP |
| `POST` | `/vms/{identifier}/stop` | Stop and retain disk, using the default TERM wait |
| `DELETE` | `/vms/{identifier}` | Delete VM and private disk |
| `POST` | `/vms/{identifier}/start` | Start a retained VM |
| `POST` | `/vms/{identifier}/restart` | Restart a VM; accepts `timeout` query parameter |
| `POST` | `/vms/{identifier}/stop` | Stop and retain disk; accepts `timeout` query parameter |
| `DELETE` | `/vms/{identifier}` | Delete VM and private disk; accepts `timeout` query parameter |
| `POST` | `/vms/{vm_id}/ports` | Set, open, or close private ports for an exact VM ID |
| `POST` | `/vms/{vm_id}/ports/sync` | Reapply the policy for an exact VM ID |
| `GET` | `/vms/{identifier}/logs` | Read a bounded, non-following log tail |
These stop/delete endpoints do not expose the CLI timeout, dry-run, or bulk
flags and do not prompt for confirmation.
Individual lifecycle requests do not expose a CLI dry-run flag and do not prompt
for confirmation. `timeout` is an optional finite, non-negative query parameter
on restart, stop, and delete; it has the same SIGTERM wait semantics as the CLI.
### Health, List, and Inspect
@@ -739,9 +753,19 @@ The response never includes the password. Omitting credentials still uses
labels return HTTP `422`. Example passwords above are placeholders; real
secrets in curl arguments can enter shell history and process listings.
### Stop and Delete
### Start, Restart, Stop, and Delete
```sh
# Start a retained private disk.
curl -X POST \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/api-web/start"
# Restart with an explicit SIGTERM wait.
curl -X POST \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/api-web/restart?timeout=10"
curl -X POST \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/api-web/stop"
@@ -753,8 +777,95 @@ curl -X DELETE \
```
Stop returns the VM response with `status: "stopped"`; delete returns its
exact `id` and `status: "terminated"`. Start the retained disk through the
local CLI, not a new HTTP route.
exact `id` and `status: "terminated"`. Start and restart return the updated VM
response. Start preserves the retained private disk and does not restore guest
memory.
### Bulk Lifecycle Actions
The dashboard batch toolbar uses this endpoint for selected VMs. The server
resolves the selected identifiers into a fixed ID snapshot before it invokes the
existing serialized bulk lifecycle service.
```sh
# Restart two named VMs with a shared TERM wait.
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"action":"restart","identifiers":["api-web","worker"],"timeout":10}' \
"$UVM_API_URL/vms/actions"
# Sync managed policies for every VM currently in the registry.
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"action":"ports-sync","all_vms":true}' \
"$UVM_API_URL/vms/actions"
```
Valid actions are `start`, `stop`, `restart`, `delete`, and `ports-sync`.
Provide `identifiers` or `all_vms: true`, but not both. The response contains
one `{id, action, outcome, error}` object per selected VM. HTTP bulk requests
do not prompt for confirmation or provide a dry-run; require an explicit
operator confirmation in a caller before destructive use.
### Manage Private Ports
The port mutation and sync routes require an **exact VM ID** returned by create
or list. They deliberately do not resolve a name or guest IP, preventing a
later name/IP reuse from changing the wrong policy. Port rules are strict JSON
objects, not CLI colon strings.
```sh
# Replace the full managed TCP allowlist.
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"mode":"set","ports":[{"port":22,"protocol":"TCP"},{"port":443,"protocol":"HTTPS"}]}' \
"$UVM_API_URL/vms/<vm-id>/ports"
# Add or remove one or more rules without replacing the other rules.
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"mode":"open","ports":[{"port":80,"protocol":"HTTP"}]}' \
"$UVM_API_URL/vms/<vm-id>/ports"
curl -X POST \
-H "Content-Type: application/json" \
-H "X-UVM-Token: $UVM_API_TOKEN" \
-d '{"mode":"close","ports":[{"port":80,"protocol":"HTTP"}]}' \
"$UVM_API_URL/vms/<vm-id>/ports"
# Reapply a policy after an external firewall change.
curl -X POST \
-H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/<vm-id>/ports/sync"
```
`set` can use an empty `ports` array to close every managed service. A legacy
VM with `ports: null` must be stopped and use `set` before it can start or use
the other policy operations. `open`, `close`, and `sync` reject a legacy policy.
Mutating a live policy is subject to the same containment and confirmation rules
as the CLI.
### Diagnostics and Log Tails
The dashboard's System view calls the protected diagnostics endpoint. It runs
the same host-readiness checks as `uvm doctor`, but does not repair a host or
change VM state.
```sh
curl -H "X-UVM-Token: $UVM_API_TOKEN" "$UVM_API_URL/diagnostics"
# Read the final 200 lines without opening a streaming follow request.
curl -H "X-UVM-Token: $UVM_API_TOKEN" \
"$UVM_API_URL/vms/api-web/logs?tail=200"
```
Log responses have `content` and `truncated` fields. `tail` is an integer from
0 through 1000, defaulting to 100. Reads are limited to the final 256 KiB of a
log; `truncated: true` means an oversized line or the read bound prevented a
complete requested tail. Use `uvm logs VM --follow` for continuous output.
### Install or Refresh Through the API