docs(web): document the card/executions/template-delete changes

CLAUDE.md's web/app.py summary, WEB-UI.md's dashboard-layout and Executions
sections, and a new design-history spec covering the tile removal, the
compacted Host card, the dropped level-word strip, and the four cards'
shared <dl> grammar.
This commit is contained in:
2026-07-25 19:35:44 +02:00
parent 3ffcf4314b
commit 612e37844f
3 changed files with 217 additions and 16 deletions
+88 -15
View File
@@ -1,7 +1,7 @@
# Web UI — Management Dashboard
Server-rendered dashboard (`ci web serve`) for the local CI/CD system: recent
CI executions with their live VMs (§8), cargo cache, disk, and act_runner
CI executions with their live VMs (§9), cargo cache, disk, and act_runner
status, plus confirm-gated destructive actions (VM remove/cleanup, cache
purge, template backup) with an audit trail. In-process only — it imports
`operations/` and `backends.load_backend` directly and never shells out to
@@ -106,7 +106,7 @@ application **before** enabling `[web.oidc]`:
5. Add the Gitea username(s) allowed to log in to `[web].allowed_users`.
**The allowlist is fail-closed**: an empty list denies everyone, it
never means "allow all".
6. For the Executions card (§8), add an API read scope to
6. For the Executions card (§9), add an API read scope to
`[web.oidc].scopes` — `read:repository`, plus `read:admin` if the account
is a Gitea admin and you want runs from every repo on the instance. New
scopes take effect after one fresh login (Gitea shows the consent screen
@@ -152,10 +152,15 @@ reinstalls the `[web]` extra and restarts the service in one step (steps
address while `[web.oidc]` is absent — an unauthenticated dashboard that
can destroy VMs and purge caches must never be reachable from the LAN.
Configure `[web.oidc]` first, or bind `127.0.0.1`.
- Every mutating route (VM remove/cleanup, cache purge, template backup) is
behind a synchronizer-token CSRF check and a two-step confirm gate (a
preview/dry-run, then an explicit "Confirm" that actually executes) —
there is no one-click destroy.
- Every mutating route (VM remove/cleanup, cache purge, template backup,
template delete, backup delete) is behind a synchronizer-token CSRF check
and a two-step confirm gate (a preview/dry-run, then an explicit "Confirm"
that actually executes) — there is no one-click destroy.
- **Template delete has no backup-required safety rail.** It removes the
entire template directory — the golden image VM clones are made from, not
a single backup archive — behind the same confirm gate as everything else,
but nothing stops you from deleting a template with zero backups. Use the
adjacent Backup button first if you want a way back.
- Every mutating action appends exactly one entry to the audit log
(`[web].audit_log`, default `/var/lib/ci/logs/web-audit.jsonl`), viewable
at `/audit` in the dashboard.
@@ -280,13 +285,73 @@ on the live host with `sudo visudo -c` before relying on the panel.
---
## 8. Executions card (recent Gitea Actions runs)
## 8. Dashboard layout — one card per subject, no tile row
The dashboard opens with four compact info cards, each the **only** place its
subject is reported:
| Card | Route | Refresh | Contents |
|---|---|---|---|
| **Host** | `GET /host` | 5s | `System` (hostname · kernel), `CPU` (model · cores), CPU / RAM / disk with gauges; uptime in the header bar, load average under the CPU gauge |
| **VMware modules** | `GET /vmware` | 15s | `vmmon`, `vmnet`, `vmware-host.service` |
| **Runners** | `GET /runners` | 5s | each `act-runner*` unit with its sub-state |
| **Cargo cache** | `GET /cache` | 5s | root, total size, entries, oldest entry, retention |
Then the wide tables: Executions (§9), Templates, Artifacts, Services,
Maintenance.
All four info cards render the same `<dl>` grammar at the page's plain body
size — one `<dt>`/`<dd>` pair per fact. VMware and Runners used to be a
bordered `<table>` at a smaller font (`0.9rem` body text, uppercase `0.75rem`
column headers), which read as a visibly different style sitting next to
Host and Cargo cache's plain label/value lists. They carry the same two data
points as before per row (a status badge, a sub-state) — just as `<dt>{{
unit }}</dt><dd>{{ badge }} {{ sub_state }}</dd>` instead of a table row —
so no reading was dropped, and no card in this row uses `<table>` any more.
There is **no status-header tile row**. It used to sit above these cards
showing VMs / Runners / CPU / RAM / Disk / Cache, which meant runner state was
displayed in three places at once (tile, Runners card, Services card) and the
core count, RAM total, cache size and entry count in two. The readouts that
only the tiles had — CPU%, RAM used, disk used/free — moved into the Host card,
where the rest of the host data already was. The VM count went away with the
tiles; running VMs are listed in the Executions card, and the orphan sweep is
in Maintenance.
The Host card carries the most data of the four, so it is packed to match
their height: the near-static facts pair up (`System`, `CPU`), uptime rides the
header bar (spelled out — it has no `<dt>` to label it), and the load average
is the CPU gauge's sub-line. Five `<dt>` rows total. The CPU model is trimmed
by the `cpu_short` filter (`Intel(R) Core(TM) i9-13900K CPU @ 3.00GHz` →
`Intel Core i9-13900K`); the untrimmed name is the `title` of that row.
**Health does not depend on color.** Each gauge states its value as text
directly above the bar (`42%`, `20.0 / 62 GB`, `40% · 60 GB free`), and the
level word — `ok` / `elevated` / `critical` / `unknown` — is part of the
gauge's `aria-label`, so a screen reader announces it. There is no visible
level strip: the words repeated what the numbers already said on every row.
Thresholds behind the fill colour and the announced word: CPU and RAM `> 75%`
amber, `> 90%` red; disk `> 70%` amber, `> 85%` red; a source that could not
be read is a neutral `unknown`, never a false green — and when the disk probe
fails outright, `unknown` is printed as the row's value, since there is no
gauge left to carry it.
---
## 9. Executions card (recent Gitea Actions runs)
The dashboard's top wide table is **Executions**: the most recent workflow
runs, the live ones included, each with its status, workflow, branch/commit,
trigger, duration, and the build VM(s) that run spawned (with their `Remove`
button). It replaced the old read-only "Live VMs" table — a VM alone could
never answer "did the last build pass?".
runs, the live ones included, with the build VM(s) that run spawned (with
their `Remove` button). It replaced the old read-only "Live VMs" table — a VM
alone could never answer "did the last build pass?".
Every fact is its own column — Run, Repo, Workflow, Branch, Commit, Trigger,
Actor, Status, Duration, Started, VM, IP / Uptime, Actions. No cell stacks two
facts on separate lines with `<br>`; a run's repo, the commit hash, who
triggered it, and when it started each get their own column instead. The
table is wide as a result — 13 columns — and scrolls horizontally inside the
card's own `.table-wrap` rather than the page, same as the other wide tables
(Templates, Services).
Runs come from Gitea's Actions API: `/api/v1/admin/actions/runs` (every repo
on the instance, admin token) with `/api/v1/user/actions/runs` as the
@@ -294,6 +359,15 @@ non-admin fallback. Results are cached for 10s, so the card's 5s refresh costs
Gitea at most one call per 10s window; the VM columns are re-read on every
refresh.
The Status pill carries a check/✕ icon for the two outcomes that are a real
verdict (`success`/`failure`, via `run_badge_icon`); `queued`/`running`/
`cancelled`/`skipped` stay plain text since they are not a pass/fail on the
run. Every pill in the column — outcome pills and the VM-only `orphan`/
`running` pills on unmatched-VM rows — is forced to the same width
(`#executions-card td:nth-child(8)` — Status is the 8th column, see above —
sized to fit `cancelled`, the longest word) with left-aligned content, so the
column reads as a clean list instead of ragged pill widths.
Rows are ordered **newest first** (a queued run, which has no start time yet,
sorts above everything; ties break on Gitea's run id). The row-count dropdown at
the right end of the card's header bar picks how many to list — 10 / 25 / 50 /
@@ -340,13 +414,12 @@ passes. Do not remove that header.
**Orphaned VMs are not handled in this card.** A powered-on VM whose run is no
longer in the window (or that no run ever claimed) keeps a row of its own
flagged `no run`, with its `Remove` button — but the orphan *sweep* lives in
the **Maintenance** card ("Cleanup orphaned VMs") and the orphan *count* in
the status-header VMs tile. Those two are the places to look at orphans; the
Executions card only avoids hiding them.
the **Maintenance** card ("Cleanup orphaned VMs"). That is the place to act on
orphans; the Executions card only avoids hiding them.
---
## 9. Troubleshooting
## 10. Troubleshooting
**Login loop (bounces back to `/login` after authenticating with Gitea)**
: The registered Redirect URI in Gitea does not exactly match