Skip to content

feat(web): make the dashboard show what needs attention (spec 0016) - #15

Merged
nofuturekid merged 3 commits into
mainfrom
feat/0016-dashboard
Aug 3, 2026
Merged

nofuturekid merged 3 commits into
mainfrom
feat/0016-dashboard

Conversation

@nofuturekid

Copy link
Copy Markdown
Owner

Implements spec 0016. Stacked on #14 — review that one first; this PR's diff is only the dashboard.

Why

GET / has been a stub since spec 0003, which called it that in as many words. It showed the user's name, their role, and the cabin version — all three of which #14 moved into the navigation rail. Its entire remaining content was a warning that is only true in the first five minutes after installation.

Meanwhile the question an internal CA actually gets asked was on no page: what is about to stop working? The inventory could answer it, but only if someone thought to filter by status=expiring. Nothing surfaced the two expiries that take real work to fix — the intermediate's and the root's — and nothing said whether the published CRL was still inside its validity window.

What it shows

  • Four counts — valid / expiring / expired / revoked, each linking to the inventory filtered to it. Backed by a new cabin.ca.certs.status_counts() that reuses the same _filters the inventory uses, so a tile and the page behind it cannot disagree. A second expression of "what expiring means" is exactly the kind of duplicate that drifts.
  • Expiring soon — up to 10 certificates, soonest first, with days remaining.
  • The CA itself — intermediate and root expiry, warning-tagged a year out. Replacing a CA means touching every trust store it is installed in; 30 days' notice would be useless.
  • Revocation — CRL number, generation time, next update, and a stale flag once that has passed. A stale CRL is the difference between clients seeing a revocation and clients silently trusting a revoked certificate.
  • Services — ACME/MCP state and the base URL, only for roles that may see /settings. The dashboard aggregates pages with different roles attached; it has to keep each one, or it becomes a way around authorisation.
  • Recent activity — the last five audit entries.

No new table, no background job, no new dependency. Every figure is read from what the database already holds, off one clock taken per request so counts, tags and "days remaining" on a single render cannot straddle a tick.

Two corrections made while implementing

The warning I specified could never fire. FR-6 originally called for an "enabled but no base URL" warning. Spec 0010 FR-5 and spec 0013 FR-4 already refuse to store that combination — POST /settings answers 400. The warning would have been code that never runs, so it is gone; the spec now says why, and the test asserts the rejection instead, so that if that gate is ever relaxed the test says the warning is needed again.

A tag class with no rule renders grey. The dashboard's "expires in 364 days" shipped colourless in my first pass: #14 renamed the tag rules to value names (tag-expiring) while this view emitted role names (tag-warn), which no longer existed. assert "tag-warn" in page passed the whole time. Fixed by defining the role classes alongside the value classes, and by test_every_rendered_tag_class_has_a_rule, which renders pages in states that actually reach every alarm tag — asserting first that the fixture produced them — and then checks each emitted class resolves to a rule or is on an explicit neutral list.

Verification

make check: 491 passed, no skips. 15 new tests in tests/test_web_dashboard.py.

Both new tests were checked for sensitivity, not assumed: removing the .tag-warn rule turns the tag test red, and an earlier version of it passed against the broken CSS because its fixture never reached a warning state — that version was thrown away rather than kept as reassurance.

One fixture bug worth noting: my first _insert helper wrote microsecond-precision not_after values. Real certificates are second-granular (X.509 has no sub-second validity), and at the exact 30-day boundary the string comparison flips on that difference. The helper now truncates, like a real certificate does.

Replaces the spec 0003 stub with expiring certificates, inventory counts,
the CA's own expiry, CRL freshness, service state and recent activity.

The counts run the same filters the inventory runs, so a tile and the page
it links to cannot disagree. The services section keeps /settings' role: a
viewer must not learn configuration here that they are refused there.
@nofuturekid
nofuturekid changed the base branch from feat/0015-ui-layout to main August 3, 2026 11:13
@nofuturekid
nofuturekid merged commit 6c81038 into main Aug 3, 2026
1 check passed
@nofuturekid
nofuturekid deleted the feat/0016-dashboard branch August 10, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant