phase: 29_gateway_dashboard_page
Phase 29 completion pass is done. Final state:
**Verification (all re-run this pass, no task files touched)**
- `uv run pytest` → **1746 passed, 16 skipped**; `uv run pytest --cov` → **99.05 %** (gate 94 %); `bash .agents/validate.sh` → **rc=0** (ruff clean, pyright `0 errors, 0 warnings`, `node --check` clean); `uv run pytest -m live --live` → **12 passed**.
- Live, read-only: dev server **10/10 sources `ok`** (incl. `gateway`); `data/logs/29-task06-live.cjs` → **49 PASS / 0 FAIL** with per-port inbound counts reconciled against a direct `/proc/net/tcp{,6}` read (divergence 0); `data/logs/verify-29-task06-predates.py` → 0 failed (1.1.0 arm renders `WHY_PREDATES`, no pipes, every tally `n/a`, job still `ok`); phase-27 mobile audit re-run over all nine served pages → **371 PASS / 0 FAIL / 0 FINDING** (`data/logs/29-final-mobile-audit.out`).
- Criteria: (1) unconfigured `GATEWAY_HOST` → `404 {configured:false}` with dial spies + `#gate-empty` sentence ✅; (2) 1.1.0/`--no-proxy` → `supported:false`, no `0`s, never red ✅; (3) live gateway 1.2.0 → pipes animate, totals reconcile ✅; (4) no `severity`/verdict colour anywhere, validate green ✅; (5) docs updated + gap 13 logged ✅; (6) the five existing dashboards, jobs and `/api/summary` unchanged (live control leg) ✅.
**Docs (consistency pass, incl. pre-existing drift)**
- `README.md`: architecture diagram now lists `sources/gateway.py`, the 30 s gateway job and the third `/snapshot` reader; `GATEWAY_HOST` row says *a name, never a URL*; the `POLL_MS_*` row states `/gateway`'s fixed 15 s browser poll; "nine pollers" → per-source cadences; phase-28 "until phase 29" wording → current state; new Testing paragraph for `tests/test_gateway_source.py` (join pinned from the captures, reconciliation identity, `getaddrinfo` spy).
- `AGENTS.md`: phase-28 stop-point wording replaced with the shipped phase-29 state; suite/coverage figures → 1746 / 16 skipped / 99.08 %→99.05 % band and `~99 %`; window-chips paragraph records that `/gateway` has **no** chips (fixed 24 h); new testing-policy bullet for the join; stale page counts fixed (seven → eight tabbed pages / `MARKUP_PAGES`, clock-order and badge-count transcripts annotated); gap 12 now records the phase-29 nine-page audit re-run.
- `tests/test_api.py`: comment/docstring-only fixes for the "seven pages" wording that phase 29 made eight.
**Deviations / notes**
- Nothing implemented: all six task files were already in `complete/`, so this run was the final verification + docs pass; no git index or phase file was touched.
- Coverage wobbles 99.05–99.08 % between runs on a pre-existing timing-dependent line (`collectors.py` `/slots`-alias `except`/`break`), not a phase-29 path (`sources/gateway.py` is 100 %).
- `uv pip list --outdated` still lists upgrades; dependencies did not move in this phase, so they were left alone rather than upgraded mid-verification.
- Historical browser-transcript counts (phase 18/20/25 script runs) were left as recorded history and annotated instead of rewritten.
**Next phase:** none — `.agents/phases/todo/` is empty after 29; further work needs a new phase authored first.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
Phase 29 completion pass is done. Final state:
|
||||
|
||||
**Verification (all re-run this pass, no task files touched)**
|
||||
- `uv run pytest` → **1746 passed, 16 skipped**; `uv run pytest --cov` → **99.05 %** (gate 94 %); `bash .agents/validate.sh` → **rc=0** (ruff clean, pyright `0 errors, 0 warnings`, `node --check` clean); `uv run pytest -m live --live` → **12 passed**.
|
||||
- Live, read-only: dev server **10/10 sources `ok`** (incl. `gateway`); `data/logs/29-task06-live.cjs` → **49 PASS / 0 FAIL** with per-port inbound counts reconciled against a direct `/proc/net/tcp{,6}` read (divergence 0); `data/logs/verify-29-task06-predates.py` → 0 failed (1.1.0 arm renders `WHY_PREDATES`, no pipes, every tally `n/a`, job still `ok`); phase-27 mobile audit re-run over all nine served pages → **371 PASS / 0 FAIL / 0 FINDING** (`data/logs/29-final-mobile-audit.out`).
|
||||
- Criteria: (1) unconfigured `GATEWAY_HOST` → `404 {configured:false}` with dial spies + `#gate-empty` sentence ✅; (2) 1.1.0/`--no-proxy` → `supported:false`, no `0`s, never red ✅; (3) live gateway 1.2.0 → pipes animate, totals reconcile ✅; (4) no `severity`/verdict colour anywhere, validate green ✅; (5) docs updated + gap 13 logged ✅; (6) the five existing dashboards, jobs and `/api/summary` unchanged (live control leg) ✅.
|
||||
|
||||
**Docs (consistency pass, incl. pre-existing drift)**
|
||||
- `README.md`: architecture diagram now lists `sources/gateway.py`, the 30 s gateway job and the third `/snapshot` reader; `GATEWAY_HOST` row says *a name, never a URL*; the `POLL_MS_*` row states `/gateway`'s fixed 15 s browser poll; "nine pollers" → per-source cadences; phase-28 "until phase 29" wording → current state; new Testing paragraph for `tests/test_gateway_source.py` (join pinned from the captures, reconciliation identity, `getaddrinfo` spy).
|
||||
- `AGENTS.md`: phase-28 stop-point wording replaced with the shipped phase-29 state; suite/coverage figures → 1746 / 16 skipped / 99.08 %→99.05 % band and `~99 %`; window-chips paragraph records that `/gateway` has **no** chips (fixed 24 h); new testing-policy bullet for the join; stale page counts fixed (seven → eight tabbed pages / `MARKUP_PAGES`, clock-order and badge-count transcripts annotated); gap 12 now records the phase-29 nine-page audit re-run.
|
||||
- `tests/test_api.py`: comment/docstring-only fixes for the "seven pages" wording that phase 29 made eight.
|
||||
|
||||
**Deviations / notes**
|
||||
- Nothing implemented: all six task files were already in `complete/`, so this run was the final verification + docs pass; no git index or phase file was touched.
|
||||
- Coverage wobbles 99.05–99.08 % between runs on a pre-existing timing-dependent line (`collectors.py` `/slots`-alias `except`/`break`), not a phase-29 path (`sources/gateway.py` is 100 %).
|
||||
- `uv pip list --outdated` still lists upgrades; dependencies did not move in this phase, so they were left alone rather than upgraded mid-verification.
|
||||
- Historical browser-transcript counts (phase 18/20/25 script runs) were left as recorded history and annotated instead of rewritten.
|
||||
|
||||
**Next phase:** none — `.agents/phases/todo/` is empty after 29; further work needs a new phase authored first.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
== uv run pytest --cov (hermetic: no network, live tests skip) ==
|
||||
........................................................................ [ 4%]
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 12%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 20%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 28%]
|
||||
........................................................s............... [ 32%]
|
||||
........................................................................ [ 36%]
|
||||
........................................................................ [ 40%]
|
||||
........................................................................ [ 44%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 53%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 61%]
|
||||
........................................................................ [ 65%]
|
||||
........................................................................ [ 69%]
|
||||
............................................................ssssssssssss [ 73%]
|
||||
........................................................................ [ 77%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
................................................sss..................... [ 98%]
|
||||
.................................. [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------------------
|
||||
src/dashboards/__init__.py 3 0 100%
|
||||
src/dashboards/__main__.py 14 1 93% 32
|
||||
src/dashboards/alerts.py 182 0 100%
|
||||
src/dashboards/api.py 491 0 100%
|
||||
src/dashboards/app.py 190 0 100%
|
||||
src/dashboards/auth.py 265 0 100%
|
||||
src/dashboards/certs.py 292 0 100%
|
||||
src/dashboards/collectors.py 216 5 98% 323-324, 328-329, 346
|
||||
src/dashboards/config.py 126 0 100%
|
||||
src/dashboards/debug.py 14 0 100%
|
||||
src/dashboards/poller.py 165 3 98% 192-194, 279
|
||||
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
|
||||
src/dashboards/rates.py 73 0 100%
|
||||
src/dashboards/severity.py 49 0 100%
|
||||
src/dashboards/sources/__init__.py 0 0 100%
|
||||
src/dashboards/sources/disks.py 134 0 100%
|
||||
src/dashboards/sources/gateway.py 321 0 100%
|
||||
src/dashboards/sources/host.py 209 0 100%
|
||||
src/dashboards/sources/llm.py 178 1 99% 285
|
||||
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
|
||||
src/dashboards/sources/nut_sim.py 134 0 100%
|
||||
src/dashboards/store.py 313 2 99% 553-554
|
||||
------------------------------------------------------------------
|
||||
TOTAL 3894 37 99%
|
||||
Required test coverage of 94% reached. Total coverage: 99.05%
|
||||
1746 passed, 16 skipped, 1 warning in 167.20s (0:02:47)
|
||||
== uv run ruff check src tests agent ==
|
||||
All checks passed!
|
||||
== uv run pyright ==
|
||||
0 errors, 0 warnings, 0 informations
|
||||
== node --check src/dashboards/web/static/*.js ==
|
||||
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
|
||||
certifi 2026.10.19 2026.10.23 wheel
|
||||
coverage 7.16.2 7.17.0 wheel
|
||||
fastapi 0.153.0 0.161.1 wheel
|
||||
pycparser 3.11 3.12 wheel
|
||||
ruff 0.17.1 0.17.2 wheel
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
**Implemented**
|
||||
- `config.py`: `gateway_host` (`GATEWAY_HOST`, default `gateway`), `gateway_interval` (30 s), `gateway_timeout` (10 s), `gateway_enabled` (name must be a `HOST_TARGETS` entry, `nut_enabled` shape) and it now counts in `any_source_enabled`.
|
||||
- New `src/dashboards/sources/gateway.py`: `parse(snapshot, host)` joins agent 1.2.0's `haproxy`/`nginx`/`sockets` into inbound pipes (per listen port: domains, live count, distinct clients) and outbound pipes (haproxy `server`, concrete `proxy_pass`, `$var`→`map` fan-out), plus `unmatched` tallies, `samples()` gauges (`gateway.in_conns@<port>`, `gateway.out_conns@<host>:<port>`) and `parse_gateway_snapshot() -> Result`. Gauges only: no estimator/`Δ` window, no `severity`, `null` (never `0`) whenever the socket table or the join cannot answer; `supported:false` (1.1.0/`--no-proxy`) + per-section `why` + verbatim `section_errors`.
|
||||
- Join never resolves a name: `socket.getaddrinfo` spy pins zero calls; exact (ip,port) match `by_port:false`, name matched on port only when that port has one configured host (`by_port:true`), several names on one port → nobody chosen (`connections:null`, sockets in `unmatched.outbound`, one `unmatched.groups` entry naming port/count/candidates). `join_sockets` attributes each socket once, so inbound+outbound+unmatched == the socket table.
|
||||
- Tests: `tests/test_gateway_source.py` (35, incl. a fixture built by running the **real agent `Collector`** over the captured configs via new `gateway_snapshot` conftest fixture), 3 new `tests/test_config.py` cases, and a read-only `--live` test against the real gateway agent. Probe: `data/logs/verify-29-task01.py` → `29-task01-live.out`.
|
||||
- Docs: `.env.example` + `.env.dev.example` gateway block, README Configuration rows, AGENTS.md (config bullet, new `sources/gateway.py` responsibility bullet, metric-naming/gauge family), `store.py` family docstring (`disks`/`gateway` added).
|
||||
|
||||
**Results** — `uv run pytest`: 1691 passed, 16 skipped; `uv run pytest --cov`: 99.05% total, `sources/gateway.py` and `config.py` 100%; `uv run ruff check src tests agent` + `uv run pyright`: clean; `bash .agents/validate.sh`: green; `uv run pytest -m live --live`: 12 passed; dev server restarted, all nine sources `ok`. Live join: 10 inbound / 31 outbound / 17 map entries, partition `19 + 1 + 8 = 28 == established` OK, zero `severity` keys.
|
||||
|
||||
**Deviations (documented in code + AGENTS.md)**
|
||||
- Outbound series carry the port (`@host:port`, vs the phase's host-only sketch): this gateway's two haproxy Minecraft backends share `minecraft.reeselink.com` on 25565/25566, and one name would `INSERT OR REPLACE` one pipe's gauge over the other in silence (rule 2).
|
||||
- The shared-port fan-out (16 `*.reeselink.com:443` map entries) yields `null` counts + `unmatched.groups` rather than a guessed or repeated attribution; `unmatched.other` is defined as live listeners no config claims (phase 28's "unclaimed socket").
|
||||
- No live infra changes were made (read-only dial only). Note: `NOTES.md` is empty in the working tree (pre-existing, not this task); its two items are already shipped (phase 24 mTLS, phase 20 task 04 admin links), so I left it as-is.
|
||||
|
||||
**Next pending task:** `.agents/phases/todo/29_gateway_dashboard_page/02_wiring.md` (the `gateway` poller job, alerts rule, `/api/summary` block, `/api/config` entry).
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
== uv run pytest --cov (hermetic: no network, live tests skip) ==
|
||||
........................................................................ [ 4%]
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 12%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 21%]
|
||||
........................................................................ [ 25%]
|
||||
........................................................................ [ 29%]
|
||||
.......s................................................................ [ 33%]
|
||||
........................................................................ [ 37%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 46%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 59%]
|
||||
........................................................................ [ 63%]
|
||||
........................................................................ [ 67%]
|
||||
........................................................................ [ 71%]
|
||||
.....ssssssssssss....................................................... [ 75%]
|
||||
........................................................................ [ 80%]
|
||||
........................................................................ [ 84%]
|
||||
........................................................................ [ 88%]
|
||||
........................................................................ [ 92%]
|
||||
.................................................................sss.... [ 97%]
|
||||
................................................... [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------------------
|
||||
src/dashboards/__init__.py 3 0 100%
|
||||
src/dashboards/__main__.py 14 1 93% 32
|
||||
src/dashboards/alerts.py 176 0 100%
|
||||
src/dashboards/api.py 440 0 100%
|
||||
src/dashboards/app.py 186 0 100%
|
||||
src/dashboards/auth.py 265 0 100%
|
||||
src/dashboards/certs.py 292 0 100%
|
||||
src/dashboards/collectors.py 206 4 98% 304, 308-309, 326
|
||||
src/dashboards/config.py 126 0 100%
|
||||
src/dashboards/debug.py 14 0 100%
|
||||
src/dashboards/poller.py 165 3 98% 192-194, 279
|
||||
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
|
||||
src/dashboards/rates.py 73 0 100%
|
||||
src/dashboards/severity.py 49 0 100%
|
||||
src/dashboards/sources/__init__.py 0 0 100%
|
||||
src/dashboards/sources/disks.py 134 0 100%
|
||||
src/dashboards/sources/gateway.py 307 0 100%
|
||||
src/dashboards/sources/host.py 209 0 100%
|
||||
src/dashboards/sources/llm.py 178 1 99% 285
|
||||
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
|
||||
src/dashboards/sources/nut_sim.py 134 0 100%
|
||||
src/dashboards/store.py 313 2 99% 553-554
|
||||
------------------------------------------------------------------
|
||||
TOTAL 3809 36 99%
|
||||
Required test coverage of 94% reached. Total coverage: 99.05%
|
||||
1691 passed, 16 skipped, 1 warning in 163.88s (0:02:43)
|
||||
== uv run ruff check src tests agent ==
|
||||
All checks passed!
|
||||
== uv run pyright ==
|
||||
0 errors, 0 warnings, 0 informations
|
||||
== node --check src/dashboards/web/static/*.js ==
|
||||
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
|
||||
coverage 7.16.2 7.17.0 wheel
|
||||
fastapi 0.153.0 0.161.1 wheel
|
||||
pycparser 3.11 3.12 wheel
|
||||
ruff 0.17.1 0.17.2 wheel
|
||||
@@ -0,0 +1,18 @@
|
||||
Everything is green. Final state:
|
||||
|
||||
**Implementation** (`src/dashboards/`)
|
||||
- `collectors.py`: `fetch_gateway_snapshot()` (third reader of the same agent `/snapshot`) + `gateway_job()` — **one** job (the `nut` shape), key/dashboard `gateway`, `label` = `GATEWAY_HOST`'s name, `GATEWAY_INTERVAL_SECONDS`/`GATEWAY_TIMEOUT_SECONDS`, **no** `RateEstimator`, `None` when the name isn't a target (invariant 3).
|
||||
- `app.build_hub`: adds that job between `disks` and `llm` on the **same** `AgentTls` entry as `host:`/`disks:`, so one revoke fails all three without a dial.
|
||||
- `alerts.py`: `HREF["/gateway"]`, staleness in the gateway's own 30 s interval, `AGENT_ECHO_DASHBOARDS = ("disks", "gateway")` with `_partner_host()` (gateway finds its host by **label**, not key) — dead agent = one line, wedged sections while the host answers = the only voice; **no** gateway threshold or payload argument.
|
||||
- `api.py`: additive `/api/summary.gateway` (`configured, host, supported, unreachable, inbound, outbound, map_targets, unmatched, unmatched_listeners, partial, states`; `null` never `0` in the arms that didn't measure) and the `/api/config.gateway` row with redacted `display_url`.
|
||||
|
||||
**Tests/lint/coverage**
|
||||
- 19 new tests (5 alerts, 11 api, 3 collectors); full suite **1712 passed, 16 skipped**, coverage **99.07%** (was 99.05%), `alerts.py`/`api.py`/`app.py` back to **100%**; `ruff` + `pyright` clean; `bash .agents/validate.sh` exit 0 (dep list is the pre-existing advisory).
|
||||
- Mutation-checked: breaking the gateway label lookup fails 3 pins, so the echo rule isn't vacuous.
|
||||
- Live (`--live`, `.env.dev`): **12 passed**; dev server restarted — `/api/health` **ten sources ok** incl. `gateway`, `/api/summary.gateway` live (in 12 / out 4 / map 17 / unmatched 10+2 / partial), `/api/config.gateway` `{enabled, gateway, https://10.1.1.27:9120, 30, 10}`.
|
||||
|
||||
**Docs**: AGENTS.md (poller job-of-its-own, collectors, alerts, api block, app, "ten sources" fleet fact) and README (alerts echo/interval rule, summary+config blocks).
|
||||
|
||||
**Decisions**: `states` as the repo-standard list of health records (not a `{gateway: str}` map); added `partial` so a sum that omitted an unattributable port isn't read as the total; no `severity` anywhere.
|
||||
|
||||
**Next pending task**: `29_gateway_dashboard_page/03_api_routes.md` — `/api/gateway/snapshot` + `/api/gateway/history`.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
== uv run pytest --cov (hermetic: no network, live tests skip) ==
|
||||
........................................................................ [ 4%]
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 12%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 20%]
|
||||
........................................................................ [ 25%]
|
||||
........................................................................ [ 29%]
|
||||
.........................s.............................................. [ 33%]
|
||||
........................................................................ [ 37%]
|
||||
........................................................................ [ 41%]
|
||||
........................................................................ [ 45%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 62%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
..........................ssssssssssss.................................. [ 75%]
|
||||
........................................................................ [ 79%]
|
||||
........................................................................ [ 83%]
|
||||
........................................................................ [ 87%]
|
||||
........................................................................ [ 91%]
|
||||
........................................................................ [ 95%]
|
||||
..............sss....................................................... [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------------------
|
||||
src/dashboards/__init__.py 3 0 100%
|
||||
src/dashboards/__main__.py 14 1 93% 32
|
||||
src/dashboards/alerts.py 182 0 100%
|
||||
src/dashboards/api.py 473 0 100%
|
||||
src/dashboards/app.py 189 0 100%
|
||||
src/dashboards/auth.py 265 0 100%
|
||||
src/dashboards/certs.py 292 0 100%
|
||||
src/dashboards/collectors.py 216 4 98% 324, 328-329, 346
|
||||
src/dashboards/config.py 126 0 100%
|
||||
src/dashboards/debug.py 14 0 100%
|
||||
src/dashboards/poller.py 165 3 98% 192-194, 279
|
||||
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
|
||||
src/dashboards/rates.py 73 0 100%
|
||||
src/dashboards/severity.py 49 0 100%
|
||||
src/dashboards/sources/__init__.py 0 0 100%
|
||||
src/dashboards/sources/disks.py 134 0 100%
|
||||
src/dashboards/sources/gateway.py 307 0 100%
|
||||
src/dashboards/sources/host.py 209 0 100%
|
||||
src/dashboards/sources/llm.py 178 1 99% 285
|
||||
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
|
||||
src/dashboards/sources/nut_sim.py 134 0 100%
|
||||
src/dashboards/store.py 313 2 99% 553-554
|
||||
------------------------------------------------------------------
|
||||
TOTAL 3861 36 99%
|
||||
Required test coverage of 94% reached. Total coverage: 99.07%
|
||||
1712 passed, 16 skipped, 1 warning in 164.73s (0:02:44)
|
||||
== uv run ruff check src tests agent ==
|
||||
All checks passed!
|
||||
== uv run pyright ==
|
||||
0 errors, 0 warnings, 0 informations
|
||||
== node --check src/dashboards/web/static/*.js ==
|
||||
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
|
||||
coverage 7.16.2 7.17.0 wheel
|
||||
fastapi 0.153.0 0.161.1 wheel
|
||||
pycparser 3.11 3.12 wheel
|
||||
ruff 0.17.1 0.17.2 wheel
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
**Task 03 complete — `/api/gateway/snapshot` + `/api/gateway/history`.**
|
||||
|
||||
**Implementation** — `api.gateway_router()` (both GETs, included after `disks_router()` in `app.create_app`): `/snapshot` tests `settings.gateway_enabled` in its first line and raises `404 {"configured": false}` naming `GATEWAY_HOST` before any client/fetch/store read; answering → `sources/gateway.parse`'s payload verbatim + `states` (payload `ts` untouched); never-answered → new `gateway_source.stub(host, ts)` (`sources/gateway.py`: `_skeleton()` shared with `parse`, `WHY_NO_ANSWER`, `unreachable: true`, `supported: null`, every count `null`). `/history` goes through the one `_history_batch()` → `store.history_many()` door, `hosts=` defaulting to the ingress alone (no configured gate, since history dials nothing — the `/api/disks/history` rule).
|
||||
|
||||
**Tests** — 16 new tests in `tests/test_api.py` (both unconfigured arms — unset name and a name that is not a target — spy-pinned on `httpx.AsyncClient.get` + `collectors.fetch_snapshot`, with a control leg that names a real target; the stub's keys/counts/`states`; the payload↔`parse` key-set equality; inbound+outbound+unmatched == `sockets.established`; a recursive no-`severity`/no-`window_seconds` walk; history points re-read through `store.query`; unknown names → empty series; host default; family isolation in `/api/metrics?source=`; the two GETs, 405 on the rest, and the app's one upstream POST still `/api/ups/command`), plus the two paths added to `test_auth_api.py::PROTECTED_API`. Both controls were verified by mutation (gate moved after the fetch → refusal tests fail; batch source changed `gateway`→`host` → history tests fail).
|
||||
|
||||
**Gate** — `uv run pytest --cov`: **1728 passed, 16 skipped**, coverage **99.05%** (`api.py`, `app.py`, `sources/gateway.py` each 100%); `ruff` and `pyright` clean; `bash .agents/validate.sh` **exit 0** (only the pre-existing dependency advisory; no deps moved); `-m live --live` 12 passed. Dev server restarted: all ten sources `ok`, and live `/api/gateway/snapshot` against agent 1.2.0 answered 6 frontends / 17 map targets / 31 outbound pipes with the identity reconciling exactly (39 == 39), while `/history` served `gateway.in_conns@443` and empty for a name that never measured.
|
||||
|
||||
**Docs** — README and AGENTS `api.py` bullets now describe the pair (three arms, the gate-before-dial, the stub, the port-in-the-name gauges, no write), AGENTS records `gateway_source.stub()` and that all five `history` routes share the batched door.
|
||||
|
||||
**Deviations** — none. `tests/card_contract.py` untouched (task 04's `COLUMNS` device); task file left in `todo/`, no git commands run.
|
||||
|
||||
**Next task** — `.agents/phases/todo/29_gateway_dashboard_page/04_page.md`.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
== uv run pytest --cov (hermetic: no network, live tests skip) ==
|
||||
........................................................................ [ 4%]
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 12%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 20%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 28%]
|
||||
.......................................s................................ [ 33%]
|
||||
........................................................................ [ 37%]
|
||||
........................................................................ [ 41%]
|
||||
........................................................................ [ 45%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 53%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 61%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
..........................................ssssssssssss.................. [ 74%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 82%]
|
||||
........................................................................ [ 86%]
|
||||
........................................................................ [ 90%]
|
||||
........................................................................ [ 94%]
|
||||
..............................sss....................................... [ 99%]
|
||||
................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------------------
|
||||
src/dashboards/__init__.py 3 0 100%
|
||||
src/dashboards/__main__.py 14 1 93% 32
|
||||
src/dashboards/alerts.py 182 0 100%
|
||||
src/dashboards/api.py 491 0 100%
|
||||
src/dashboards/app.py 190 0 100%
|
||||
src/dashboards/auth.py 265 0 100%
|
||||
src/dashboards/certs.py 292 0 100%
|
||||
src/dashboards/collectors.py 216 5 98% 323-324, 328-329, 346
|
||||
src/dashboards/config.py 126 0 100%
|
||||
src/dashboards/debug.py 14 0 100%
|
||||
src/dashboards/poller.py 165 3 98% 192-194, 279
|
||||
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
|
||||
src/dashboards/rates.py 73 0 100%
|
||||
src/dashboards/severity.py 49 0 100%
|
||||
src/dashboards/sources/__init__.py 0 0 100%
|
||||
src/dashboards/sources/disks.py 134 0 100%
|
||||
src/dashboards/sources/gateway.py 321 0 100%
|
||||
src/dashboards/sources/host.py 209 0 100%
|
||||
src/dashboards/sources/llm.py 178 1 99% 285
|
||||
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
|
||||
src/dashboards/sources/nut_sim.py 134 0 100%
|
||||
src/dashboards/store.py 313 2 99% 553-554
|
||||
------------------------------------------------------------------
|
||||
TOTAL 3894 37 99%
|
||||
Required test coverage of 94% reached. Total coverage: 99.05%
|
||||
1728 passed, 16 skipped, 1 warning in 167.39s (0:02:47)
|
||||
== uv run ruff check src tests agent ==
|
||||
All checks passed!
|
||||
== uv run pyright ==
|
||||
0 errors, 0 warnings, 0 informations
|
||||
== node --check src/dashboards/web/static/*.js ==
|
||||
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
|
||||
coverage 7.16.2 7.17.0 wheel
|
||||
fastapi 0.153.0 0.161.1 wheel
|
||||
pycparser 3.11 3.12 wheel
|
||||
ruff 0.17.1 0.17.2 wheel
|
||||
@@ -0,0 +1,10 @@
|
||||
**Task 04 complete — `/gateway` page with animated pipes. Everything green.**
|
||||
|
||||
- **Page**: `web/pages/gateway.html` + `web/static/page-gateway.js` + a `/* ---- gateway pipes */` CSS section; route added in `app.py`. Four cards (`haproxy`, `nginx`, `map`, `unmatched`) keyed by each pipe's own `source`; hand-rolled SVG pipes (`<path>`, `vector-effect`), node label = `snapshot.host`, solo-side layouts for the stream map.
|
||||
- **Honesty pins**: labels/counts/clients are `textContent` only; `null` → `n/a` (no `|| 0` on any count); a pipe animates **only** when `connections > 0`, `.still` otherwise; `prefers-reduced-motion` names **both** states; no `ok|warn|fail` token anywhere in the gateway CSS section; every plot carries `ariaLabel`; one `/api/gateway/history` request per card, plot built only while its `Charts` disclosure is open.
|
||||
- **Degradation** (browser-proved, all from payload/route text): unconfigured → setup block naming `GATEWAY_HOST` + agent 1.2.0, zero red, all `n/a`; predates / never-answered → banner prints the payload's own `why`, nothing drawn, dimmed; silent socket table → config map stays, every count `n/a`.
|
||||
- **Two defects found and fixed**: `LineChart._yExtent()` padded a flat series **below** its declared `yMin` (an idle pipe drew a `-1` connection axis) — shared fix, measured on `/system` `/disks` `/llm` `/ups` (no axis moved); and an all-unknown remainder/total rendered a card of `n/a`s or a `0` — now hidden/`n/a`.
|
||||
- **Tests**: 14 new served-byte pins in `tests/test_api.py` (+3 existing audits widened to nine pages/twelve assets), `/gateway` added to `tests/test_auth_api.py`'s protected pages and served route table.
|
||||
- **Browser (live fleet)**: `data/logs/29-task04-{pipes,degradation,unconfigured,charts-floor}.cjs` → all pass; 41 live pipes, `15 + 4 + 11 = 30 = established`; screenshots `29-task04-{gateway,charts-open,phone,predates,sockets_silent,unanswered,unconfigured}.png`, transcripts `.out`.
|
||||
- **Gate**: `pytest` 1742 passed / 16 skipped, coverage 99.08 % (floor 94), `ruff` + `pyright` clean, `node --check` clean; dev server up with all ten sources `ok`.
|
||||
- Docs (`AGENTS.md`/`README.md`) and the sixth masthead tab are task 05/06's deliverables and were deliberately left untouched; `PAGES` in `test_api.py` stays the five-tab table so task 05 moves it as one contract.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
== uv run pytest --cov (hermetic: no network, live tests skip) ==
|
||||
........................................................................ [ 4%]
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 12%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 20%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 28%]
|
||||
....................................................s................... [ 32%]
|
||||
........................................................................ [ 36%]
|
||||
........................................................................ [ 40%]
|
||||
........................................................................ [ 45%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 53%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 61%]
|
||||
........................................................................ [ 65%]
|
||||
........................................................................ [ 69%]
|
||||
........................................................ssssssssssss.... [ 73%]
|
||||
........................................................................ [ 77%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 86%]
|
||||
........................................................................ [ 90%]
|
||||
........................................................................ [ 94%]
|
||||
............................................sss......................... [ 98%]
|
||||
.............................. [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------------------
|
||||
src/dashboards/__init__.py 3 0 100%
|
||||
src/dashboards/__main__.py 14 1 93% 32
|
||||
src/dashboards/alerts.py 182 0 100%
|
||||
src/dashboards/api.py 491 0 100%
|
||||
src/dashboards/app.py 190 0 100%
|
||||
src/dashboards/auth.py 265 0 100%
|
||||
src/dashboards/certs.py 292 0 100%
|
||||
src/dashboards/collectors.py 216 5 98% 323-324, 328-329, 346
|
||||
src/dashboards/config.py 126 0 100%
|
||||
src/dashboards/debug.py 14 0 100%
|
||||
src/dashboards/poller.py 165 3 98% 192-194, 279
|
||||
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
|
||||
src/dashboards/rates.py 73 0 100%
|
||||
src/dashboards/severity.py 49 0 100%
|
||||
src/dashboards/sources/__init__.py 0 0 100%
|
||||
src/dashboards/sources/disks.py 134 0 100%
|
||||
src/dashboards/sources/gateway.py 321 0 100%
|
||||
src/dashboards/sources/host.py 209 0 100%
|
||||
src/dashboards/sources/llm.py 178 1 99% 285
|
||||
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
|
||||
src/dashboards/sources/nut_sim.py 134 0 100%
|
||||
src/dashboards/store.py 313 2 99% 553-554
|
||||
------------------------------------------------------------------
|
||||
TOTAL 3894 37 99%
|
||||
Required test coverage of 94% reached. Total coverage: 99.05%
|
||||
1742 passed, 16 skipped, 1 warning in 161.90s (0:02:41)
|
||||
== uv run ruff check src tests agent ==
|
||||
All checks passed!
|
||||
== uv run pyright ==
|
||||
0 errors, 0 warnings, 0 informations
|
||||
== node --check src/dashboards/web/static/*.js ==
|
||||
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
|
||||
coverage 7.16.2 7.17.0 wheel
|
||||
fastapi 0.153.0 0.161.1 wheel
|
||||
pycparser 3.11 3.12 wheel
|
||||
ruff 0.17.1 0.17.2 wheel
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
All gates green. Final state:
|
||||
|
||||
**Done (task 05)**
|
||||
- `Gateway` tab added to all **eight** signed-in pages (six dashboards + `/settings` + `/certs`), `aria-current` on `/gateway` only; tab audit rewritten as `nav hrefs == TAB_ORDER` (`PAGES` is now the six dashboards) with per-page `aria-current` asserted in both directions; `test_the_certs_page_is_a_page_and_not_a_sixth_dashboard` renamed `…_not_a_dashboard` now that `/gateway` *is* the sixth.
|
||||
- Overview ingress line: `#fleet-gate` in the fleet panel, rendered by `renderGateway()` from `/api/summary`'s block (`GATEWAY_FIELDS` resolved against the live payload — the `SLOT_FIELDS` device), `.stale` when unreachable, **hidden when `configured: false`**. `/api/summary`'s `gateway` block gained an additive `href` derived from `alerts.HREF`, so the row links with data instead of a page-side route literal (same rule the alert rows keep).
|
||||
- Pins: `SUMMARY_CONTRACT` now names every gateway key; `NAV_STATUS_BADGES` gained `gateway`; new `test_the_overview_line_reads_the_gateway_block_it_claims` and `test_the_gateway_block_links_to_the_page_the_alert_row_links_to`.
|
||||
- Docs: AGENTS.md (six-page/nav/badge counts, the ingress line, the `href` derivation, the wrap rule, the nine-page audit) and README.md updated.
|
||||
|
||||
**Deviations / defects found**
|
||||
- Task assumption "the fifth `#nav-status` badge with **no JS change**" was false — `initChrome()`'s badge table is a literal, so app.js gained `{ key: 'gateway', label: 'gateway', href: '/gateway' }` (pinned).
|
||||
- The fifth badge **broke phase 18's guarantee**: `#nav-status` was `nowrap` at ≤640px, so five chips (352px) widened the document to 372px — a sideways scroll on a phone. Fixed by letting the badge row wrap inside its own row (no label shrunk, nothing hidden); the served-CSS pin flipped to demand `wrap`, and the phase-27 mobile audit (extended to include `/gateway`) re-runs clean.
|
||||
|
||||
**Verification**
|
||||
- `bash .agents/validate.sh`: **1744 passed, 16 skipped**, coverage 99.08%, ruff + pyright + `node --check` clean.
|
||||
- `data/logs/29-task05-tabs.cjs` → `29-task05-tabs.out`: **97 PASS / 0 FAIL** (8 pages × 1280/360 six tabs + one-own-current + five populated badges + no sideways scroll; 360px dropdown lists all six; the tab navigates and draws the tallies; the `/` line reads the block's counts/href, and a wire-simulated `configured:false` renders nothing). Screenshots `data/logs/masthead-29-*.png`.
|
||||
- `data/logs/27-task02-mobile-audit.cjs` (now including `/gateway`): **0 failures, 0 findings**.
|
||||
- Dev server up; `/api/health` and `dev-server.sh status` report all ten sources `ok`.
|
||||
- validate.sh's dependency advisory (coverage/fastapi/pycparser/ruff newer) is pre-existing; this task touched no dependencies.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
== uv run pytest --cov (hermetic: no network, live tests skip) ==
|
||||
........................................................................ [ 4%]
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 12%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 20%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 28%]
|
||||
......................................................s................. [ 32%]
|
||||
........................................................................ [ 36%]
|
||||
........................................................................ [ 40%]
|
||||
........................................................................ [ 45%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 53%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 61%]
|
||||
........................................................................ [ 65%]
|
||||
........................................................................ [ 69%]
|
||||
..........................................................ssssssssssss.. [ 73%]
|
||||
........................................................................ [ 77%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 90%]
|
||||
........................................................................ [ 94%]
|
||||
..............................................sss....................... [ 98%]
|
||||
................................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------------------
|
||||
src/dashboards/__init__.py 3 0 100%
|
||||
src/dashboards/__main__.py 14 1 93% 32
|
||||
src/dashboards/alerts.py 182 0 100%
|
||||
src/dashboards/api.py 491 0 100%
|
||||
src/dashboards/app.py 190 0 100%
|
||||
src/dashboards/auth.py 265 0 100%
|
||||
src/dashboards/certs.py 292 0 100%
|
||||
src/dashboards/collectors.py 216 5 98% 323-324, 328-329, 346
|
||||
src/dashboards/config.py 126 0 100%
|
||||
src/dashboards/debug.py 14 0 100%
|
||||
src/dashboards/poller.py 165 3 98% 192-194, 279
|
||||
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
|
||||
src/dashboards/rates.py 73 0 100%
|
||||
src/dashboards/severity.py 49 0 100%
|
||||
src/dashboards/sources/__init__.py 0 0 100%
|
||||
src/dashboards/sources/disks.py 134 0 100%
|
||||
src/dashboards/sources/gateway.py 321 0 100%
|
||||
src/dashboards/sources/host.py 209 0 100%
|
||||
src/dashboards/sources/llm.py 178 1 99% 285
|
||||
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
|
||||
src/dashboards/sources/nut_sim.py 134 0 100%
|
||||
src/dashboards/store.py 313 2 99% 553-554
|
||||
------------------------------------------------------------------
|
||||
TOTAL 3894 37 99%
|
||||
Required test coverage of 94% reached. Total coverage: 99.05%
|
||||
1744 passed, 16 skipped, 1 warning in 164.35s (0:02:44)
|
||||
== uv run ruff check src tests agent ==
|
||||
All checks passed!
|
||||
== uv run pyright ==
|
||||
0 errors, 0 warnings, 0 informations
|
||||
== node --check src/dashboards/web/static/*.js ==
|
||||
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
|
||||
coverage 7.16.2 7.17.0 wheel
|
||||
fastapi 0.153.0 0.161.1 wheel
|
||||
pycparser 3.11 3.12 wheel
|
||||
ruff 0.17.1 0.17.2 wheel
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
Task 06 is complete. Final state:
|
||||
|
||||
## Done
|
||||
|
||||
**Docs**
|
||||
- `README.md` — intro now *five* dashboards; `/gateway` page-table row (knob + **agent 1.2.0** requirement + the unconfigured-rather-than-red arm), the `GATEWAY_HOST` / `GATEWAY_INTERVAL_SECONDS` / `GATEWAY_TIMEOUT_SECONDS` config rows, and the user-facing paragraph on the topology question and both degraded arms.
|
||||
- `AGENTS.md` — route-table row, the `/gateway` UI-conventions paragraph (pipes, `n/a`-not-`0`, no verdict colour, the label-line rule, one history request per card), the metric-naming paragraph's `gateway.in_conns@<listen-port>` / `gateway.out_conns@<host>:<port>`, the config bullet's `default \`gateway\``, and **gap 13** (the residual label width, with the rejected alternative and three costed options).
|
||||
- New pin `tests/test_api.py::test_the_docs_name_the_page_its_knob_and_its_gauges` so prose can't rot: both page rows name `/gateway` + `GATEWAY_HOST` + 1.2.0, the bullet names the default, the gauges and `/api/gateway/*` stay named.
|
||||
|
||||
**Live read-only pass** — `data/logs/29-task06-live.cjs` → `29-task06-live.out`: **49 PASS / 0 FAIL**
|
||||
- payload: `configured`/`supported`, agent `1.2.0`, **6 haproxy frontends** (each port→backend), nginx listening on **80/443/4443/8443**, **17 stream-map entries** (fan-out 443×16 + 4443×1), every entry's key/host/port complete.
|
||||
- page: 4 cards, 41 aria-labelled pipes matching the payload's sentence shape, header tallies = payload figures, `in + out + unmatched == sockets.established` exact, `by port` chip on every `by_port` pipe, collapsed→open chart labelled with legend rows naming exactly that card's pipes.
|
||||
- `/proc/net/tcp{,6}` via `ssh gateway-ops cat`: per-port inbound == ESTABLISHED-on-that-port (≤1 tolerance), `unmatched.inbound` == the port complement (`2222`, `9120`), `by_port` spot-check page 2 vs /proc 2, ambiguous `:443` → 16 null pipes + one `groups` sentence, and nulls never became `0`.
|
||||
- control leg: nine pre-existing sources `ok`; `/` 3 fleet rows, `/system` 3 cards, `/disks` 2 hosts (gateway still hidden), `/llm` 2 engines, `/ups` 2 UPSes; `/`'s ingress line renders and disappears on `configured:false`; 360px has no sideways scroll.
|
||||
- Screenshots: `29-task06-{gateway,card-haproxy,card-stream,charts-expanded,phone}.png`. Transcripts grepped clean of PEM/token/cookie.
|
||||
|
||||
**Pre-1.2.0 arm** — `data/logs/verify-29-task06-predates.py` (+ `.cjs`) → **PASS, 0 failed checks**: throwaway loopback dashboard + scratch SQLite + a stub agent from `tests/fixtures/agent_gateway.json` stamped `1.1.0` with the three sections stripped → `supported: false` with `WHY_PREDATES`, page prints that sentence, draws no cards, every tally `n/a`, banner `info` not red, job `ok`. Screenshot `29-task06-predates.png`.
|
||||
|
||||
## Defect found and fixed
|
||||
The pass caught the page clipping its own answer: pipe labels showed **seven characters** of a 40-character target (`.pipe-label` was one `nowrap` flex row, so the name shrank to what the chip/footnote/count left). Wrapping the text inside that box was tried and measured worse (eleven lines of one word), so the fix gives the label the line: `flex-wrap: wrap` + `flex: 1 1 max-content` on `.pipe-name` → the name now gets the whole column and the chip/footnote/count wrap beneath. `test_a_clipped_pipe_label_keeps_its_full_text_on_the_element` pins both declarations plus the `title`/`aria-label` that keep the remaining ellipsis from being a lost fact; the residual (28 of 42 at 1440px, 16 of 42 at 360px) is gap 13 with the options ordered by cost.
|
||||
|
||||
## Gates
|
||||
- `bash .agents/validate.sh` → **rc=0**: 1746 passed, 16 skipped, coverage **99.05 %**, ruff clean, pyright 0 errors, `node --check` clean.
|
||||
- `./scripts/dev-server.sh status`: all **ten** sources `ok`.
|
||||
- Regression control for the CSS change: phase 27's nine-page audit re-ran — **371 PASS, 0 failures, 0 findings**.
|
||||
- validate.sh's dependency advisory (coverage/fastapi/pycparser/ruff newer) is pre-existing; no dependency moved.
|
||||
|
||||
## Notable decisions
|
||||
- The kernel cross-check asserts the **payload's own partition** (`attributed + unmatched == established`) and reports the raw `/proc` total as a finding — two reads 0.8 s apart cannot be equal on a busy ingress, and pretending otherwise would make the pass flaky about a fact that isn't a bug. The per-port table is the real join proof.
|
||||
- Payload and DOM are read payload → probe → payload, retrying if `ts` moved, so a 30 s poll can't compare a picture to a newer document.
|
||||
- The 1.1.0 arm was proven on a loopback stub, never by rolling a fleet host back.
|
||||
|
||||
## Next pending task
|
||||
This was phase 29's last file — `.agents/phases/todo/29_gateway_dashboard_page/` now holds only `00_phase.md` and `06_docs_and_verify.md`, so the phase is ready to move to `complete/`. No other phase is in `todo/`; gap 13 (pipe-label column width) is the one open item this task surfaced.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
== uv run pytest --cov (hermetic: no network, live tests skip) ==
|
||||
........................................................................ [ 4%]
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 12%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 20%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 28%]
|
||||
........................................................s............... [ 32%]
|
||||
........................................................................ [ 36%]
|
||||
........................................................................ [ 40%]
|
||||
........................................................................ [ 44%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 53%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 61%]
|
||||
........................................................................ [ 65%]
|
||||
........................................................................ [ 69%]
|
||||
............................................................ssssssssssss [ 73%]
|
||||
........................................................................ [ 77%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
................................................sss..................... [ 98%]
|
||||
.................................. [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------------------
|
||||
src/dashboards/__init__.py 3 0 100%
|
||||
src/dashboards/__main__.py 14 1 93% 32
|
||||
src/dashboards/alerts.py 182 0 100%
|
||||
src/dashboards/api.py 491 0 100%
|
||||
src/dashboards/app.py 190 0 100%
|
||||
src/dashboards/auth.py 265 0 100%
|
||||
src/dashboards/certs.py 292 0 100%
|
||||
src/dashboards/collectors.py 216 4 98% 324, 328-329, 346
|
||||
src/dashboards/config.py 126 0 100%
|
||||
src/dashboards/debug.py 14 0 100%
|
||||
src/dashboards/poller.py 165 3 98% 192-194, 279
|
||||
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
|
||||
src/dashboards/rates.py 73 0 100%
|
||||
src/dashboards/severity.py 49 0 100%
|
||||
src/dashboards/sources/__init__.py 0 0 100%
|
||||
src/dashboards/sources/disks.py 134 0 100%
|
||||
src/dashboards/sources/gateway.py 321 0 100%
|
||||
src/dashboards/sources/host.py 209 0 100%
|
||||
src/dashboards/sources/llm.py 178 1 99% 285
|
||||
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
|
||||
src/dashboards/sources/nut_sim.py 134 0 100%
|
||||
src/dashboards/store.py 313 2 99% 553-554
|
||||
------------------------------------------------------------------
|
||||
TOTAL 3894 36 99%
|
||||
Required test coverage of 94% reached. Total coverage: 99.08%
|
||||
1746 passed, 16 skipped, 1 warning in 165.41s (0:02:45)
|
||||
== uv run ruff check src tests agent ==
|
||||
All checks passed!
|
||||
== uv run pyright ==
|
||||
0 errors, 0 warnings, 0 informations
|
||||
== node --check src/dashboards/web/static/*.js ==
|
||||
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
|
||||
certifi 2026.10.19 2026.10.23 wheel
|
||||
coverage 7.16.2 7.17.0 wheel
|
||||
fastapi 0.153.0 0.161.1 wheel
|
||||
pycparser 3.11 3.12 wheel
|
||||
ruff 0.17.1 0.17.2 wheel
|
||||
Reference in New Issue
Block a user