phase: 22_disks_hide_unconfigured
All green. Final report: **Verified (phase 22 final pass — both task files already in `complete/`)** - `api._disk_visible` (`is not False`) is the single gate, applied to the assembled host list in `/api/disks/snapshot` **and** `/api/summary`'s disks block before the fleet sum and the rows; `sources/disks.py`, `page-disks.js`, `poller.py`, `store.py` and `alerts.py` are byte-unchanged (`git diff --stat`: only `api.py`, `test_api.py`, docs). - Re-ran the live browser pass `data/logs/22-task02-disks-live.cjs` against the running dev server: **34/34 PASS** — no gateway card/why-line, `2/2 hosts answering`, fleet header reads the API's `2 of 2` / `17-17-0-1`, deskwork `nvme0n1` OK 47 °C/19 202 h/11 %, driveripper 16 live cards + `13 uncorrectable` chip, both aria-labelled plots, `/` still 3 fleet rows. Screenshots + `22-task02-api.json` refreshed. - `./scripts/dev-server.sh status`: all **nine sources `ok`**, `disks:gateway` included; live `/api/events?source=disks` still carries gateway transitions (10 of 45), proving the README/AGENTS claim the gate hides a row and not the job's history. **Completion criteria** - `false` host absent from both endpoints, `None` + `unreachable` stub present, fleet ≡ row count: **PASS** (6 pins in `tests/test_api.py`, incl. the single-hidden-host = 200-with-empty-list leg). - Live `/disks` = deskwork + driveripper, header 2 hosts, cards unchanged, screenshot in `data/logs/`: **PASS** (re-run above). - `uv run pytest` → **1477 passed, 15 skipped**; `--cov --cov-fail-under=94` → **98.94 %** (`api.py` 100 %); `ruff check src tests agent` → all passed; `pyright` → 0 errors; `bash .agents/validate.sh` → **EXIT 0**. `uv run pytest -m live --live` → **11 passed**. - No behaviour change in completed work: **PASS** (unchanged files above; live numbers match the pre-phase fleet record). **Docs** — README: `/disks` page-table row + the agent SMART paragraph now distinguish "cannot read drives" (dimmed cards kept) from "no smartctl" (host absent from the page/API lists). AGENTS.md: hermetic tally 1471→**1477**, and the `collectors.py`/`api.py` passages now note the `false` arm never reaches the `/disks` why-line while an overview alert from a hidden host's `disks` job still names it. Everything else (routes vs served table, env table vs `config.py`, commands, scripts/fixtures/tests references, masthead/POLL_MS/threshold/retention claims) **verified consistent**. **Deviations** — `NOTES.md` left empty by the task runs (both notes are tracked: mTLS → phase 24 `todo/`, the admin-links bug → fixed in phase 20 task 04); `/disks`' `#hosts-empty` copy left as pinned markup (it already names smartmontools + the disk group, so an all-hidden fleet is still told the fix); validate's advisory `fastapi 0.153.0 → 0.156.4` untouched (no dependency changed this phase). No live fleet writes of any kind. **Next pending phase:** `23_llm_rate_responsiveness`.
This commit is contained in:
@@ -6,7 +6,7 @@ Four dashboards, one domain, one process, one SQLite file:
|
||||
| ----------- | ------------------------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| `/` | Is anything wrong, and which dashboard do I need? | all four, in one request (`/api/summary`) |
|
||||
| `/system` | What are my machines doing — CPU, memory, disk, GPU, network, and the history of all of it | `agent/host_agent.py`, our own stdlib agent per host |
|
||||
| `/disks` | Are any of my **physical** disks failing, and which one — health, temperature, power-on hours, wear, sector alarms | the same agents, running `smartctl -i -A -H` per whole drive (never a partition or a mount) |
|
||||
| `/disks` | Are any of my **physical** disks failing, and which one — health, temperature, power-on hours, wear, sector alarms | the same agents, running `smartctl -i -A -H` per whole drive (never a partition or a mount); a host whose agent has no `smartctl` is not a host on this page |
|
||||
| `/llm` | Tokens/sec, active sessions, queue depth, KV-cache pressure, latency percentiles | llama.cpp-family (gufo) `/metrics` + `/slots`, vLLM `/metrics` — read-only |
|
||||
| `/ups` | Battery health, runtime left, power events, and battery-test buttons | NUT `upsd`, on whatever port it really listens on (its own wire-protocol client) |
|
||||
|
||||
@@ -114,7 +114,9 @@ disk can never cost a poll more than its own timeout (only the very first pass a
|
||||
agent starts runs inline). A host without
|
||||
`smartmontools`, or whose agent user cannot read the block devices, is *not broken*: every
|
||||
disk reports `smart: null` with one explanatory `errors` line, and that line never makes
|
||||
the system page amber. Enable SMART with `smartmontools` plus the `disk` group (which
|
||||
the system page amber. The two cases differ on `/disks`: a host that only cannot **read** its
|
||||
drives keeps its (dimmed, all-`n/a`) cards, while one whose agent says `smartctl` is not
|
||||
installed is not listed on that page at all. Enable SMART with `smartmontools` plus the `disk` group (which
|
||||
`deploy/install-agent.sh` grants when it exists) — the agent shells `smartctl` as itself and
|
||||
never calls `sudo`, so a sudoers rule alone is not enough — and **redeploy the agent**, since
|
||||
one installed before phase 09 has no SMART pass to run. Opt out silently with `--no-smart`.
|
||||
@@ -631,16 +633,25 @@ certs.py ─ root CA + client cert, minted once at startup (unreadable row → n
|
||||
queries per series — and the JSON it answers with is the same document the per-series loop
|
||||
produced, series for series and in the same order (`tests/test_api.py` pins that against
|
||||
points read straight out of the store with `query()`). `/api/disks/snapshot` is the
|
||||
drive-health surface: one entry per host with its physical disk rows, the agent's `smart`
|
||||
block verbatim, `smartctl_available` (`true` / `false` = install smartmontools / `null` =
|
||||
the agent never said), a `severity` beside each row's rated numbers, and a `fleet` block
|
||||
summed here so the page does no arithmetic — a host that has not answered yet is listed as
|
||||
an `unreachable` stub of `n/a`s and counted, never summed. `/api/config` carries
|
||||
drive-health surface: one entry per host **with drives to show** — its physical disk rows,
|
||||
the agent's `smart` block verbatim, `smartctl_available` (`true` / `false` = install
|
||||
smartmontools / `null` = the agent never said), a `severity` beside each row's rated
|
||||
numbers, and a `fleet` block summed here so the page does no arithmetic — a host that has
|
||||
not answered yet is listed as an `unreachable` stub of `n/a`s and counted, never summed.
|
||||
The one host that is **not** listed is the one whose agent said `false`: a box with no
|
||||
smartmontools (a VM with nothing behind `vda`) has no card, no fleet contribution and no
|
||||
`/api/summary` row, because the only thing its card could say is "install it". `null` and
|
||||
an `unreachable` stub both stay — an agent that cannot say is not an agent that said *no*
|
||||
(`api._disk_visible` is the single predicate, pinned both ways in `tests/test_api.py`; the
|
||||
`disks:<host>` job, its health record and its stored `disk.*` series still see the host).
|
||||
`/api/config` carries
|
||||
`disks.interval_seconds`, which is why a drive card four minutes old is behaving, not
|
||||
broken. `/api/summary` grew a `disks` block with that page (the same `fleet` sums plus one row
|
||||
per host — counts, `smartctl_available`, and `failed_devices`, so a notifier learns *which*
|
||||
drive the hardware called `FAILED` without a second call); it is additive, exactly as `alerts`
|
||||
was, and the key paths of both are pinned with and without targets configured.
|
||||
was, and the key paths of both are pinned with and without targets configured. Since phase 22
|
||||
that block lists the **same hosts** `/api/disks/snapshot` does, and `fleet.hosts_total` is
|
||||
that row count, so `/` and `/disks` can never describe two different fleets.
|
||||
* `web/` — five static dashboard pages (`/`, `/system`, `/disks`, `/llm`, `/ups`) plus the three
|
||||
admin pages (`/login`, `/settings`, `/certs`), one stylesheet, hand-rolled
|
||||
SVG charts (`charts.js`), and a masthead status dot per dashboard job (`disks` has its own:
|
||||
@@ -714,9 +725,13 @@ certs.py ─ root CA + client cert, minted once at startup (unreadable row → n
|
||||
`severity.py` rates no power-on hours. The sector counters appear as **red chips that exist only
|
||||
while non-zero**, so a healthy drive shows no alarm row at all rather than a wall of zeroes.
|
||||
A host whose SMART cannot be read keeps its cards, dimmed with every number `n/a` and one
|
||||
`.banner` line saying why — `smartctl not available on <host>` when the agent reported the binary
|
||||
missing, its own sentence when it reported something else, and "not answering" when the agent is
|
||||
down — never a red panel, because a missing binary is unconfigured, not broken. The header's
|
||||
`.banner` line saying why — the agent's own sentence when it reported something, and "not
|
||||
answering" when the agent is down — never a red panel, because unreadable drives are
|
||||
unconfigured, not broken. The one case that gets no card at all is the agent that said
|
||||
`smartctl` is **not installed**: such a host is absent from the page's host cards (and from
|
||||
the API's host lists) rather than a card whose only content is "install smartmontools" — its
|
||||
`disks:<host>` job still polls, still reports `ok`, and its transitions still reach the drive
|
||||
events feed below the cards, because that feed is the job's history and not a host list. The header's
|
||||
drive counts are the API's `fleet` block (hidden below two hosts, so one box's sums never
|
||||
duplicate its own cards), and each host's **Charts** disclosure behaves exactly like `/system`'s:
|
||||
collapsed, built only while open, and fed by one `/api/disks/history` request per host per
|
||||
|
||||
Reference in New Issue
Block a user