Screen 1 · Dashboard (/)
restic-manager
user: alice (admin) · logout
Fleet status
10 online · 1 offline · 1 degraded
Last sync 12s ago
Storage (sum across repos)
2.4 TB across 12 repos
+18 GB last 24h
Open alerts
3 · 1 critical
2 unacked
[ search hosts · filter by tag · status ]
+ Add host

### Hosts

prod-db-01 linux/amd64
online

Last backup
2h ago · success
Repo
412 GB · 1,284 snapshots
Alerts
—
View Backup now
staging-app linux/arm64
degraded

Last backup
9h ago · failed
Repo
88 GB · 412 snapshots
Alerts
2 · 1 critical
View Retry
laptop-bob windows/amd64
offline

Last seen
3d ago
Repo
142 GB · 88 snapshots
Alerts
1
View Backup now
… more host cards (12 total in target deployment)

### Recent activity (fleet-wide)

When | Host | Kind | Status | Duration |
--- | --- | --- | --- | --- | ---
2h ago | prod-db-01 | backup | succeeded | 00:14:22 | view
3h ago | web-02 | backup | succeeded | 00:08:11 | view
9h ago | staging-app | backup | failed | 00:01:03 | view
1d ago | prod-db-01 | check | succeeded | 00:42:17 | view
1d ago | web-01 | prune | succeeded | 00:04:55 | view

#### Data sources

- Fleet summary strip — no endpoint in §6.1. Either (a) add `GET /api/fleet/summary` or (b) compute client-side from `GET /api/hosts` + `GET /api/alerts`. Recommend (a) — cheaper than fanout, and Prometheus already needs the rollup (§14.4).
- Host cards — `GET /api/hosts` must return: status, last_backup_at, last_backup_status, repo_size_bytes, snapshot_count, open_alert_count, agent_version. Domain model (§5) only has `status` + `last_seen_at`. Need to extend list response.
- "Backup now" button — `POST /api/hosts/:id/jobs` with `{kind: "backup"}`.
- Recent activity — `GET /api/jobs?limit=N&order=desc`. Spec doesn't document query params; need to add.
- HTMX cadence — this page polls every ~10s with `hx-trigger="every 10s"` on the summary + cards. WS push isn't needed here.

Screen 2 · Host detail (/hosts/:id)
restic-manager
user: alice (admin)
« Dashboard / Hosts

## prod-db-01

linux/amd64 · agent 0.4.2 · restic 0.17.1 · last seen 12s ago
online tag: prod tag: db
Currently: idle
Backup now Run check …
Snapshots Schedules Jobs Repo Settings
[ filter by tag · path · date range ]
[ sort: newest first ]

Snapshot | Time | Paths | Tags | Size | Files |
--- | --- | --- | --- | --- | --- | ---
`3a8f1e` | 2h ago | /var/lib/postgres | auto, daily | 412 GB | 1.2M | restore · diff
`8c7b22` | 1d ago | /var/lib/postgres | auto, daily | 411 GB | 1.2M | restore · diff
`4f0a99` | 2d ago | /var/lib/postgres, /etc | auto, weekly | 411 GB | 1.2M | restore · diff
… 1,281 more · load more

Other tabs (preview, not navigated):
Tab · Schedules

Kind | Cron | Paths | Retention | Enabled
--- | --- | --- | --- | ---
backup | 0 2 * * * | /var/lib/postgres | 7d/4w/12m | [x]
forget+prune | 0 4 * * 0 | — | per policy | [x]
check | 0 5 1 * * | — | — | [ ]

+ New schedule
schedule editor (expanded form)
kind: [backup ▾]
cron: [ 0 2 * * * ] human: every day at 02:00
paths: [ /var/lib/postgres ] [+ add]
excludes: [ *.tmp, /tmp ]
tags: [ auto, daily ]
retention: keep [7] daily, [4] weekly, [12] monthly · keep-tag [ ]
bandwidth: upload [ ] KB/s · download [ ] KB/s §14.2
pre-hook: [ pg_dump ... ] §14.3 admin-only
post-hook: [ ... ]
enabled: [x]
Tab · Jobs (host-scoped)

Started | Kind | Status | Duration | By
--- | --- | --- | --- | ---
2h ago | backup | succeeded | 00:14:22 | schedule
1d ago | check | succeeded | 00:42:17 | schedule
2d ago | backup | cancelled | 00:00:42 | alice
3d ago | backup | failed | 00:01:09 | schedule

Tab · Repo
URL
rest:https://restic.lab…/prod-db-01
Kind
rest (append-only)
Total size
412 GB
Dedup ratio
4.2×
Snapshots
1,284
Last check
1d ago · clean
Lock state
unlocked
Credential
append-only · rotated 14d ago
Run check Unlock Forget+prune (admin)
Tab · Settings
Tags
prod, db [+ add]
Default pre-hook
(empty)
Default post-hook
(empty)
Hook shell
/bin/sh
Default bandwidth caps
none
Enrollment
enrolled 42d ago · Regenerate token
Agent
0.4.2 · auto-update [x] · Force update now
Danger zone
Remove host does not touch repo data

#### Data sources

- Host header — `GET /api/hosts/:id`. Gap: "currently running job" not in domain model. Either join a `current_job_id` on Host, or have UI poll `GET /api/jobs?host_id=X&status=running`.
- Snapshots tab — `GET /api/hosts/:id/snapshots`. Filtering needs server support: `?tag=`, `?path=`, `?since=`. Tag autocomplete needs distinct list — either client-derived or new endpoint.
- Schedules tab — `GET /api/hosts/:id/schedules` + `POST/PUT/DELETE`. Editor exposes §14.2 bandwidth and §14.3 hooks — both stored as JSON blobs on Schedule, but UI needs structured fields. Confirm `retention_policy` JSON shape.
- Jobs tab — `GET /api/jobs?host_id=X`. Gap: "By" column wants user-or-schedule attribution. AuditLog has it; Job table doesn't expose `actor` directly. Either denormalize onto Job or join.
- Repo tab — `GET /api/hosts/:id/repo`. Gap: spec lists size/last-check/lock state. Add: dedup ratio, snapshot count, credential rotation timestamp, append-only flag. (Some derive from `restic stats`.)
- Settings tab — mostly host-row edits. New: `POST /api/hosts/:id/agent/update` for force-update (§4.2 self-update). Gap: spec doesn't surface this.
- HTMX cadence — tab content swap via `?tab=jobs` hyperlinks (server renders partial). Header polls every 10s for currently-running state.

Screen 3 · Job detail (/jobs/:id) — running state
restic-manager
user: alice (admin)
« prod-db-01 / Jobs

## backup · prod-db-01

job `j_01HJ8K7` · started 4m12s ago · triggered by alice
running schedule: nightly-pg
Cancel job
Progress
38% · ~6m remaining
156 GB of 412 GB · 482k of 1.2M files
Files new
2,103
Files changed
418
Bytes added
2.4 GB
Throughput
42 MB/s
Live log (streaming via WS)
14:02:11 [agent] starting restic backup --json 14:02:11 [agent] pre_hook: pg_dump | gzip > /tmp/dump.sql.gz 14:02:48 [pre_hook] dump complete (1.2 GB) 14:02:49 [restic] open repository 14:02:50 [restic] lock repository 14:02:50 [restic] load index files 14:02:53 [restic] start scan 14:02:55 [restic] start backup on /var/lib/postgres 14:03:01 [restic] {"message_type":"status","percent_done":0.04,"total_files":1234567,"files_done":48234,"total_bytes":442000000000,"bytes_done":17600000000} 14:04:22 [restic] {"message_type":"status","percent_done":0.18,"...} 14:05:55 [restic] {"message_type":"status","percent_done":0.31,"...} 14:06:23 [restic] warning: failed to lstat /var/lib/postgres/pg_wal/.lock 14:06:24 [restic] {"message_type":"status","percent_done":0.38,"...} ▌
[ ] auto-scroll [ ] show stderr only download full log

#### Data sources

- Header — `GET /api/jobs/:id`. Need: kind, host, started_at, actor (user / schedule / system), status, schedule_id, schedule_name. Gap: Job table has `scheduled_id` but no actor/user_id; need to join AuditLog or denormalize.
- Progress block — live updates from `WS /api/jobs/:id/stream`. The WS message `job.progress` (§6.2) needs a documented JSON shape: `{percent_done, files_done, total_files, bytes_done, total_bytes, eta_seconds, throughput_bps}`. Spec leaves this vague.
- Stats panel — on completion mirrors `restic backup --json` summary fields: `files_new`, `files_changed`, `files_unmodified`, `data_added`, `total_bytes_processed`, `duration`, `snapshot_id`. Lives in `Job.stats` JSON.
- Live log — `WS` messages of type `log.stream` (agent → server) fan out to browsers subscribed to `/api/jobs/:id/stream`. UI distinguishes `stdout` / `stderr` / `event` — the schema's `JobLog.stream` enum already covers this.
- Cancel — `POST /api/jobs/:id/cancel` → server emits `command.cancel` WS to agent (§6.2). UI should optimistically show "cancelling…" until WS confirms `job.finished`.
- HTMX caveat — this is the one screen where progressive enhancement isn't enough; live log requires WS. Plan: `hx-ext="ws"` with `ws-connect`, server sends innerHTML-fragment patches for the progress + log areas. Falls back to 2s polling without WS.

### Findings — gaps in spec.md §6 surfaced by Phase 0 wireframing

1. Aggregate fleet endpoint missing. Dashboard summary strip and Prometheus metrics (§14.4) both need fleet rollups. Add `GET /api/fleet/summary` returning host counts by status, total repo bytes, open alert counts. Cheaper than client fanout and reused by /metrics.
2. Host list response is too thin. Domain model Host (§5) has status + last_seen_at; cards need `last_backup_at`, `last_backup_status`, `repo_size_bytes`, `snapshot_count`, `open_alert_count`, `current_job_id`. Either add columns or compute server-side and include in `GET /api/hosts`.
3. Job actor not modelled. Job table tracks `scheduled_id` but not who (user vs schedule vs system) triggered a run-now. Dashboard "Recent activity" and Jobs tab both want this. Add `Job.actor_kind` + `Job.actor_id` — cheaper than joining AuditLog every time.
4. WS `job.progress` JSON shape is undefined. §6.2 lists the message name only. Lock the shape now: `{percent_done: float, files_done: int, total_files: int, bytes_done: int, total_bytes: int, eta_seconds: int, throughput_bps: int}`. Keeps client + agent in lockstep before Phase 1 codes against it.
5. Repo response needs more fields. §6.1 says size/last-check/lock state. Wireframe also wants: dedup ratio, snapshot count, credential rotation timestamp, append-only flag. Most derive from `restic stats` + Credential row — expose them through `GET /api/hosts/:id/repo`.
6. Snapshot filtering needs server support. Tag/path/date filters belong on the server (12-host fleets are small but a single host can hold thousands of snapshots). Add query params to `GET /api/hosts/:id/snapshots`: `?tag=`, `?path=`, `?since=`, `?limit=`. Distinct-tag list endpoint optional — could be derived client-side at first.
7. Job listing needs query params. Recent activity, host-scoped jobs, and the Jobs page all use `GET /api/jobs`. Lock down: `?host_id=`, `?kind=`, `?status=`, `?since=`, `?limit=`, `?order=`. Pagination too.
8. Agent self-update endpoint not in §6.1. §4.2 describes the mechanism but no REST endpoint exists. Settings tab wants a "Force update now" button — add `POST /api/hosts/:id/agent/update`.
9. Schedule retention/options JSON shape. §14.2 (bandwidth) and §14.3 (hooks) both extend `Schedule`. Document the canonical shape now (`retention_policy`, `options.limit_upload`, `options.limit_download`, `pre_hook`, `post_hook`) so the schedule editor and the agent can both target it.
10. HTMX-vs-WS responsibility split. Decision: only the Job detail screen needs WS. Dashboard, Hosts, Snapshots use HTMX polling (10s). This avoids fan-out complexity for v1; revisit if dashboard feels stale.