Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 17s
Kicks off the single-pane-of-glass command-center rewrite. Legacy /api/* handlers untouched — v2 is additive so cutover is safe. Doc: docs/dashboard-v2.md — design goals, endpoint spec, cutover plan. Backend: new serve_v2 module wired into serve.rs. Endpoints: GET /api/v2/node/local/status GET /api/v2/node/:name/status (fan-out: not-yet-impl) GET /api/v2/storage/blobs?limit&offset GET /api/v2/storage/tags?prefix GET /api/v2/storage/refs?limit&offset GET /api/v2/storage/snapshots GET /api/v2/storage/ref-tracking?repo V2State opens BlobStore / TagStore / RefStore / SnapshotStore / RefTracking under the daemon's blob_store_root + the conventional subdirs (tags-db, refs-db). All handlers are single-node reads; cross-node fan-out lands in PR 3. +6 tests: status w/ seeded blob+snapshot, cross-node returns 501, blobs pagination, tags prefix filter, snapshots list, ref-tracking repo filter. 395 tests pass. serve.rs merges v2 routes onto the axum router with shared CORS. Single-shot deploy on tank + architect will expose /api/v2/* alongside the existing /api/*. PR 2 = frontend rewrite consuming these endpoints. PR 3 = fleet fan-out (cross-node aggregation via QUIC RPC). PR 4 = action endpoints (POST scrub/gc/snapshot/pin). PR 5 = cutover (deprecate legacy /api/*).
99 lines
3.9 KiB
Markdown
99 lines
3.9 KiB
Markdown
# dashboard-v2 — single-pane-of-glass command center
|
|
|
|
Redesign of the legacy `claw-store serve` dashboard for the current
|
|
distributed architecture.
|
|
|
|
## Design goals
|
|
|
|
1. **One URL for the whole fleet.** Hit any node's `:7700`; that node
|
|
fans out to every peer via existing QUIC RPC and serves an
|
|
aggregated view. No "3 browser tabs" pattern.
|
|
2. **Command-center landing page.** At-a-glance health strip + key
|
|
metrics + recent-events feed. Operator answers "is anything on
|
|
fire?" in <2 s.
|
|
3. **Node-detail drill-down.** Click any node in the strip → detail
|
|
page with per-node metrics, timers, storage counts, journal tail.
|
|
4. **Cross-cutting content browsers.** Blobs / Tags / Refs / Snapshots
|
|
aggregated across the fleet, searchable, click-through to detail.
|
|
5. **Trigger actions from the UI.** Scrub, GC, snapshot-create,
|
|
pin/unpin — anything currently a CLI invocation.
|
|
|
|
## Non-goals (v2 scope)
|
|
|
|
- Real-time streaming metrics beyond SSE snapshots. Prometheus stays
|
|
the source of truth for graphs; this dashboard is for state +
|
|
actions, not observability.
|
|
- Auth beyond a shared bearer token (fleet is trust-perimeter — CA
|
|
auth for the UI is future work).
|
|
- Editing configs. Read + trigger, never write config.
|
|
|
|
## Backend shape
|
|
|
|
Additive `/api/v2/*` alongside the legacy `/api/*` handlers so the
|
|
cutover is safe:
|
|
|
|
```
|
|
/api/v2/fleet aggregated snapshot (all peers)
|
|
/api/v2/node/<name>/status this-peer or remote via gossip lookup
|
|
/api/v2/node/<name>/timers systemd timer state for the 4 timers
|
|
/api/v2/node/<name>/journal?unit=… last N lines of journalctl
|
|
/api/v2/storage/blobs?limit&offset BlobStore::list_blob_ids + summaries
|
|
/api/v2/storage/tags?prefix TagStore::list (both layers)
|
|
/api/v2/storage/refs?limit&offset RefStore::list unioned
|
|
/api/v2/storage/snapshots SnapshotStore::list
|
|
/api/v2/storage/ref-tracking?repo RefTracking::list_all
|
|
/api/v2/cache/metrics router.metrics().snapshot() + gossiped
|
|
/api/v2/peers gossip snapshot + last-probe route
|
|
/api/v2/events SSE — fleet event stream
|
|
POST /api/v2/actions/scrub
|
|
POST /api/v2/actions/gc { evict_to_gb? }
|
|
POST /api/v2/actions/snapshot { name }
|
|
POST /api/v2/actions/pin { key, blob_id_hex }
|
|
POST /api/v2/actions/unpin { key }
|
|
```
|
|
|
|
## Aggregation model
|
|
|
|
**Server-side fan-out.** When the dashboard requests `/api/v2/fleet`,
|
|
the serving node walks its gossip peer list and issues a
|
|
`PeerStatus` RPC to each. Results collated into one JSON.
|
|
|
|
Trade-offs:
|
|
- Simpler frontend (no per-peer TLS material in browser).
|
|
- Backend caches results per-endpoint (5 s TTL) so 10 dashboard
|
|
tabs don't cause 30 peer RPCs.
|
|
- Any node can serve the dashboard — no "coordinator" single point
|
|
of failure.
|
|
|
|
## Frontend shape (implementation PR follows)
|
|
|
|
Routes:
|
|
|
|
```
|
|
/ → CommandCenter
|
|
/nodes/<name> → NodeDetail
|
|
/storage/blobs → StorageBrowser (blobs tab)
|
|
/storage/tags → StorageBrowser (tags tab)
|
|
/storage/refs → StorageBrowser (refs tab)
|
|
/storage/snapshots → StorageBrowser (snapshots tab)
|
|
/refs/tracking → RefTracking
|
|
/ops → OpsPanel (timers + actions + journal)
|
|
```
|
|
|
|
React + Vite + Tailwind, single SPA served from `/usr/share/claw-store/static-v2/`.
|
|
|
|
## Cutover plan
|
|
|
|
1. Ship v2 backend endpoints — legacy `/api/*` untouched.
|
|
2. Ship v2 frontend at `dashboard-v2/`, built to `static-v2/`.
|
|
3. `claw-store serve --v2-static-dir <path>` — new flag serves v2 assets at `/v2` while `/` still serves legacy for a burn-in period.
|
|
4. After burn-in: swap defaults; legacy accessible at `/legacy`.
|
|
5. Remove legacy after 30 days.
|
|
|
|
## Auth
|
|
|
|
Config-driven: `[dashboard] api_token = "..."`. Bearer required for
|
|
all POST endpoints; GET endpoints open on trusted-fleet networks
|
|
(tailscale + LAN). If token absent → POSTs disabled entirely
|
|
(read-only dashboard). Same shape as the legacy dashboard.
|