phase: 21_page_cleanups
**Phase 21 final verification pass** (all 3 task files already in `complete/`, working tree carries the implementation)
- **Verified** task 01: `#pause`/`Space`/`setPaused`/`_paused` gone — `grep -ri pause src/dashboards/web/` empty, 0 pause hits in all 8 served pages + 10 static assets; re-ran `data/logs/21-task01-nopause.cjs` → 30/30 PASS (hidden tab: 0 data fetches in 22 s, one poll 2 ms after return).
- **Verified** task 02: re-ran `21-task02-gpu-values.cjs` on the live fleet → all PASS (deskwork/driveripper show Model/VRAM/Pool/Temp at 16.3 px; gateway = 5 heroes, slot `hidden`, no `n/a`; hero order unchanged; GPU history chart still builds per host).
- **Verified** task 03: re-ran `21-task03-scroll.cjs` → 14/14 PASS (`class="data scroll"`, scrollWidth 1021 vs clientWidth 292 at 360 px, Rotate/Revoke/bundle reachable and hit-testable; unchanged at 1280 px). Screenshots refreshed in `data/logs/`.
- **Gate**: `uv run pytest` → **1471 passed, 15 skipped**; `--cov --cov-fail-under=94` → **98.94%**; `ruff check src tests agent` clean; `pyright` **0 errors**; `bash .agents/validate.sh` → **EXIT 0** (incl. `node --check`); `uv run pytest -m live --live` → **11 passed**; `/api/health` all 9 sources `ok`.
- **Docs fixed** (pre-existing + this phase): "all **eight** signed-in pages" was a miscount of `MARKUP_PAGES` (7 = 5 dashboards + `/settings` + `/certs`; `/login` is the 8th, unsigned) → corrected in `README.md`, `AGENTS.md` (×2) and the `tests/test_api.py` docstring/comment; `AGENTS.md` Commands hermetic tally 1470 → **1471**.
- **Docs reviewed, consistent**: Quickstart/commands/flags (`install-agent.sh`, `capture-fixtures.sh`, `dev-server.sh`), the env table vs `config.py`, every documented route vs the served route table, masthead/`/system` GPU/`/certs` scroll descriptions vs code, and all `data/logs/*` + `tests/fixtures/*` references.
- **Deviation**: `validate.sh`'s advisory `fastapi 0.153.0 → 0.156.4` left alone — no dependency was touched this phase, so AGENTS rule 8 ("check after touching dependencies") is not triggered; upgrading here would be an unrelated behaviour change.
- No live-infra changes made (repo-side + browser verification only).
- **Next pending phase**: `22_disks_hide_unconfigured`.
This commit is contained in:
@@ -710,10 +710,11 @@ Responsibilities, one line each:
|
||||
15's "no self-link" precedent: chrome that changes shape depending on where you already are reads
|
||||
as a broken button, while a link to the page you are on is a no-op that answers with the page you
|
||||
were looking at (`data/logs/20-task04-admin-links.cjs` measures exactly that — one document
|
||||
response, no redirect, the same cluster afterwards). The two links therefore sit on all eight
|
||||
signed-in pages in one order (clock → `⚷ Certs` → `⚙ Account` → theme toggle), which is what lets
|
||||
the pinned audit be one loop over all eight instead of two over different tuples; `/login` stays
|
||||
the one page with neither. Everything on it is
|
||||
response, no redirect, the same cluster afterwards). The two links therefore sit on all seven
|
||||
signed-in pages (the five dashboards plus `/settings` and `/certs`) in one order
|
||||
(clock → `⚷ Certs` → `⚙ Account` → theme toggle), which is what lets
|
||||
the pinned audit be one loop over `MARKUP_PAGES` instead of two over different tuples; `/login`
|
||||
is the eighth page and the one with neither. Everything on it is
|
||||
`GET /api/certs`: the Root CA block, the display-only Dashboard client line (that certificate is
|
||||
minted with the root and has no route of its own), and one row per host with its status chip,
|
||||
serial, expiry (local timestamp *and* `Dash.duration` left), issued-vs-deployed `bundle_rev`,
|
||||
@@ -731,7 +732,12 @@ Responsibilities, one line each:
|
||||
names every payload key it indexes, and `tests/test_api.py` resolves that table against a live
|
||||
`GET /api/certs` row in both directions (the `SLOT_FIELDS` device), so a rename on either side
|
||||
fails a test instead of printing `n/a` beside a healthy fleet. The two CNs are markup, and pinned
|
||||
to `certs.ROOT_CN`/`certs.CLIENT_CN` the way `/settings` pins its password floor.
|
||||
to `certs.ROOT_CN`/`certs.CLIENT_CN` the way `/settings` pins its password floor. The agents
|
||||
table is `table.data.scroll` — the repo's one scroll mechanism (`TODO.md:9`: at phone width the
|
||||
serial / bundle / `san` / controls tail was clipped by `.card { overflow: hidden }` with no way
|
||||
to reach it), and the class on the served table and the served `table.data.scroll` rule are
|
||||
pinned **together** in `tests/test_api.py`, because the mechanism exists only while both name
|
||||
it (measured at 360 px and 1280 px by `data/logs/21-task03-scroll.cjs`).
|
||||
`app.js`'s masthead status dots are one per dashboard job —
|
||||
including `disks`, because the same agent can answer the 10-second utilisation poll and
|
||||
have a wedged SMART pass, and one dot for both would be right about neither. `app.js` owns the
|
||||
@@ -748,7 +754,11 @@ Responsibilities, one line each:
|
||||
gets a row in `severity.py`, never a bound in JS.
|
||||
**The masthead is three left-justified rows at every width** (phase 20 task 01, `TODO.md:5-6`):
|
||||
row 1 the brand, `#nav-toggle` and the five tabs; row 2 the `#nav-status` per-source dots; row 3
|
||||
the `.masthead-right` controls (clock, `⏸ Pause`, `⚷ Certs`, `⚙ Account`, theme). That is CSS and
|
||||
the `.masthead-right` controls (clock, `⚷ Certs`, `⚙ Account`, theme — phase 21 task 01 deleted
|
||||
the `⏸ Pause` that used to sit between the clock and the two links, button, `Space` shortcut and
|
||||
`setPaused`/`_paused` plumbing alike: it froze the numbers on screen without growing a `stale`
|
||||
label, and a backgrounded tab already idles its polls, which is the only stop this app needs).
|
||||
That is CSS and
|
||||
no markup change, which decides the mechanism: `#nav-status` is a **child** of
|
||||
`.masthead-right`, so there is no wrapper to promote, and the rows are line breaks — `.masthead`
|
||||
is `flex-wrap: wrap`, and both `#nav-status` and `.masthead-right` carry `flex-basis: 100%` with
|
||||
@@ -1064,7 +1074,7 @@ set -a; . ./.env.dev; set +a # dev dials the fleet the prod way, no
|
||||
./scripts/dev-server.sh status # per-source ok/DOWN — unauthenticated: /api/health
|
||||
./scripts/dev-server.sh logs
|
||||
|
||||
uv run pytest # hermetic: 1464 passed + 15 skipped (11 live,
|
||||
uv run pytest # hermetic: 1471 passed + 15 skipped (11 live,
|
||||
# 4 store_perf), no network
|
||||
uv run pytest --cov # coverage gate (98.94%; floor 94%)
|
||||
uv run pytest -m live --live # opt-in: real fleet / engines / upsd
|
||||
@@ -1480,24 +1490,41 @@ body moved inside a disclosure stops being padded until something says so — wh
|
||||
and `.events-pager` are class-based and needed no new rule to keep the 320px scroll and the
|
||||
`1–50 of N` strip working inside it.
|
||||
|
||||
The `/system` host card is one row of six heroes — CPU, MEM, TEMP, DISK, GPU, NET (the
|
||||
The `/system` host card is one row of six hero slots — CPU, MEM, TEMP, DISK, GPU, NET (the
|
||||
`.heroes` grid; `tests/test_api.py` pins the slots and their order on the served script).
|
||||
Colour comes from the payload's `severity` dicts and nowhere else; a missing number or
|
||||
verdict — gateway's CPU temp, any host's absent GPU — prints `n/a` **uncoloured**. NET is
|
||||
one slot with two lines (`.hero-lines`, `↓` in / `↑` out) and is deliberately colourless:
|
||||
throughput has no good/bad, so `network` carries no severity. The GPU slot holds one hero
|
||||
per card whose `.sub` carries model, VRAM used/total, the honest APU `pool` view and the
|
||||
card's temperature (`gpus[].temp_c` — the agent's max over that card's hwmon labels, the
|
||||
same value stored as `gpu.temp_c@N`, so the hero and the history cannot disagree), and it is
|
||||
colourless for the same reason (phase 06): a 95 %-busy card on an LLM server is the job, so
|
||||
the hero carries no `severity` and no utilisation bar — a bar under a verdictless number
|
||||
would be a verdict by shape. A card whose driver exposes no temperature input prints **no**
|
||||
temperature in that footnote rather than `n/a` or `0 °C`: "where you can" is the operator's
|
||||
wording, and `n/a` is reserved for numbers the card is supposed to have. Everything not
|
||||
glance-worthy — load averages, clock, disk read/write/busy, uptime, GPU power and the
|
||||
per-label temp breakdown, filesystems, processes — folds into a collapsed "More detail"
|
||||
`<details>`: a simpler surface, not a less complete page (the table keeps its Temp column;
|
||||
this was a promotion, not a de-duplication).
|
||||
Colour comes from the payload's `severity` dicts and nowhere else, and a missing **number or
|
||||
verdict** prints `n/a` **uncoloured** — gateway's CPU temp, a driver with no temperature
|
||||
input, a rate with no second sample yet. What a machine does not **have** is a different
|
||||
fact and prints nothing at all: the GPU slot ships `hidden` and `renderGpuSlot()` un-hides
|
||||
it only for a host whose payload carries a card, so gateway's card is **five** heroes with
|
||||
no GPU slot in the row (`TODO.md:12` — "Boxes without a GPU should not report N/A but
|
||||
instead should omit these values"), and the sheet's `[hidden] { display: none !important }`
|
||||
is what takes the wrapper out of the grid so the row reflows instead of leaving a wide gap.
|
||||
The two rules are one sentence each in the page and one test each; collapsing them into "any
|
||||
missing thing is `n/a`" is what made a router look broken over the GPU it never had
|
||||
(invariant 3). NET is one slot with two lines (`.hero-lines`, `↓` in / `↑` out) and is
|
||||
deliberately colourless: throughput has no good/bad, so `network` carries no severity.
|
||||
|
||||
The GPU slot is one busy hero per card plus **four labelled values** under it — **Model**
|
||||
(mono, `gpus[].model`), **VRAM** (`used / total`), **Pool** (`mem_used_pct`, the honest APU
|
||||
view a Strix Halo needs because its model lives in GTT) and **Temp** (`gpus[].temp_c` — the
|
||||
agent's max over that card's hwmon labels, the same value stored as `gpu.temp_c@N`, so the
|
||||
cell and the history cannot disagree; the per-label breakdown stays in "More detail"). They
|
||||
replaced one ` · `-joined 11px `.sub` — `TODO.md:12`: "`Radeon 8060S Graphics · VRAM 180.2
|
||||
MiB / 512.0 MiB · pool 79% · 69 °C` is too much info for such small text and should be its
|
||||
own values" — and they are `.metric` cells in a `.gpu-values` grid, so a 20-character model
|
||||
string wraps instead of truncating. The four cells come from one `GPU_VALUE_FIELDS` table,
|
||||
which `tests/test_api.py` resolves against every capture **and** against the served
|
||||
`/api/system/snapshot` (`SLOT_FIELDS`' device): a rename on either side fails a test instead
|
||||
of printing `n/a` beside a healthy fleet. A field the agent did not publish prints `n/a` in
|
||||
its own cell (the card is real, so it is a number the card is supposed to have) and never
|
||||
`0 °C`. Nothing in this slot is coloured and nothing draws a bar — no `severity`, no
|
||||
`ok|warn|fail` token in the markup or the grid's rules (phase 06: a 95 %-busy card on an LLM
|
||||
server is the job, and a bar under a verdictless number would be a verdict by shape).
|
||||
Everything not glance-worthy — load averages, clock, disk read/write/busy, uptime, GPU power
|
||||
and the per-label temp breakdown, filesystems, processes — folds into a collapsed "More
|
||||
detail" `<details>`: a simpler surface, not a less complete page (the table keeps its Temp
|
||||
column; this was a promotion, not a de-duplication).
|
||||
|
||||
A `Δ<window>` label is a measurement, never a config value: `sources/host.py` sends the window
|
||||
the estimator actually used as the payload's `window_seconds`, and `/system`'s footnote prints
|
||||
@@ -1601,8 +1628,8 @@ transition events — and nothing else: the fleet-wide chart stack, the nine-fig
|
||||
panel and the host/metric detail picker are deleted, along with the `#chart-*` / `#detail-*` ids and
|
||||
the `select` / `label.field` styling only those pickers used. Each panel stacks its own
|
||||
`.host-charts` — utilisation (CPU, memory, worst disk), network (in/out), then GPU busy — where
|
||||
the GPU slot is created and destroyed with `host.gpus`, so gateway shows neither a phantom card
|
||||
nor a hole. **That stack is collapsed behind a per-host `Charts` `<details>`** (phase 06: "only
|
||||
the GPU **chart** slot is created and destroyed with `host.gpus` (its own thing from the hero
|
||||
slot above it, which is hidden instead), so gateway shows neither a phantom card nor a hole. **That stack is collapsed behind a per-host `Charts` `<details>`** (phase 06: "only
|
||||
visible if expanded… there will be lots of hosts"): the default `/system` view is the hero row,
|
||||
the summary carries the window label the stack will draw (the chips themselves stay in the page
|
||||
head, one control per page), and `drawHostCharts()` is the single place that decides whether to
|
||||
@@ -2025,7 +2052,7 @@ memory plot; the `cpu.usage_pct@coreN` series is still stored and still queryabl
|
||||
shape applied to one table and does not reach this one. Of the two operator TODO items that sat
|
||||
beside it, `20_masthead_nav_polish` has landed in full (now complete): the bar is three
|
||||
left-justified rows at every width, it is no longer sticky, the phone menu slides, and both
|
||||
`⚙ Account` and `⚷ Certs` render on all eight signed-in pages including the two they name
|
||||
`⚙ Account` and `⚷ Certs` render on all seven signed-in pages including the two they name
|
||||
(task 04, reversing phase 15's "no self-link" rule at the operator's request). The other,
|
||||
`24_dev_mtls_rollout` (never accepting a non-mTLS agent — fleet gap 11's rollout, half of it),
|
||||
is still in `todo/`.
|
||||
|
||||
Reference in New Issue
Block a user