Closes #102.
The only operational surface was /healthz and a fold-out roster inside the owner's own reading page. This adds /admin: an owner-only page carrying the Reader roster and one row per Poll Lane.
- **Poller seam.** `latest.Poller` records each Lane's last pass (`Site`, `Due`, `Checked`, `LastRun`, `Gap`, `Clamped`, `Browser`) and answers `LaneStatus()`; the page reads that snapshot, never a table. A pass that returns before computing its figures (refusal backoff, sidecar down) carries the previous pass's figures forward rather than recording zeroes, and a Lane that has never reached a pace renders no gap at all. Refusal and sidecar reachability are derived at snapshot time.
- **Owner gate at registration.** Every route reaching past the acting Reader lives in `adminRoutes()` and is wrapped in `requireOwner` when it is registered, so a missing gate is visible in the route list rather than hidden in a handler. `web.AdminPatterns()` is what the gate test walks, so a new route cannot be added without being tested. A non-owner gets 404, never 403.
- **Nil poller is a first-class state.** `main.newRouter` takes the reporter as an interface and converts a nil `*Poller` to a nil interface; no poller and no completed pass both render "No data yet" with the reason spelled out, rather than confident zeroes.
- **Roster moved** off the reading page onto /admin, with the Sighting counters and a confirm-gated `Clear marks` control. #103 fills those counters, so on delivery they read zero for everyone - deliberate ordering.
- **One accent, `--patina`** (verdigris, both colour branches): the far side of the wheel from ember's crimson and clear of the archive blue. Ember still means new chapter only; revocation still wears --danger.
Verification: `go vet ./...` and `go test ./...` green (Docker-backed); admin page screenshotted at 1100px and 390px in both colour schemes. Reviewed on both axes (spec, standards); findings on the accent hue, zero-figure honesty and three tests that could not fail are fixed in 58014eb.
Reviewed-on: #107
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
This commit was merged in pull request #107.
This commit is contained in:
@@ -87,6 +87,11 @@
|
||||
--moss: #7fae86; /* finished */
|
||||
--clay: #b5906f; /* set chapter */
|
||||
--trash: #977671; /* remove, resting — icons need 3:1, not 4.5:1 */
|
||||
/* A Lane needing attention: the admin page's only accent. Verdigris — cool,
|
||||
the far side of the wheel from ember's crimson, and clear of the archive
|
||||
blue. Neither ember (new chapter) nor danger (destruction) may say
|
||||
"system unhealthy". */
|
||||
--patina: #5fb3a6;
|
||||
|
||||
/* Desktop cell borders for the two coloured action states. */
|
||||
--play-hot-line: #3a1d18;
|
||||
@@ -146,6 +151,7 @@
|
||||
--moss: #3d6c46;
|
||||
--clay: #7c5533;
|
||||
--trash: #8c6558;
|
||||
--patina: #1f6f66;
|
||||
|
||||
--play-hot-line: #f0cfc6;
|
||||
--fav-line: #e3d3a4;
|
||||
@@ -290,9 +296,21 @@ button { cursor: pointer; }
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
/* ---- reader roster (owner only): same hairline panel, one row per Reader ---- */
|
||||
.readerlist { margin: 0; padding: 0; list-style: none; }
|
||||
.readerlist li {
|
||||
/* ---- admin page: two sections on the same measured sheet, no cards ----
|
||||
The reading page is a list of series; this is a list of facts. Both are
|
||||
sheets of hairline-separated rows, so the roster keeps the shape it had as
|
||||
a fold-out and the Lane block copies it. */
|
||||
.readers, .lanes { margin: 0 20px; padding: 12px 0 16px; border-bottom: 1px solid var(--rule); }
|
||||
.readers h2, .lanes h2 {
|
||||
margin: 0;
|
||||
padding: 8px 0;
|
||||
font: 500 10px/1 var(--font-mono);
|
||||
letter-spacing: .2em;
|
||||
text-transform: uppercase;
|
||||
color: var(--mute-2);
|
||||
}
|
||||
.readerlist, .lanelist { margin: 0; padding: 0; list-style: none; }
|
||||
.readerlist li, .lanelist li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
@@ -300,7 +318,8 @@ button { cursor: pointer; }
|
||||
min-height: 44px;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.readerlist form { margin: 0 0 0 auto; }
|
||||
.reader-actions { display: flex; gap: 18px; margin-left: auto; }
|
||||
.readerlist form { margin: 0; }
|
||||
.reader-id {
|
||||
font: 500 13px/1.4 var(--font-mono);
|
||||
letter-spacing: .04em;
|
||||
@@ -312,6 +331,30 @@ button { cursor: pointer; }
|
||||
text-transform: uppercase;
|
||||
color: var(--mute);
|
||||
}
|
||||
.reader-sightings, .lane-fact {
|
||||
font: 500 10px/1 var(--font-mono);
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--mute-2);
|
||||
}
|
||||
/* Two states the owner is meant to find rather than read for: a Reader whose
|
||||
reports no longer defer a Poll, and a Lane that is not keeping its promise.
|
||||
Both wear --patina — never ember, which means one thing, and never danger,
|
||||
which is destruction. */
|
||||
.reader-blocked, .lane-mark {
|
||||
font: 500 10px/1 var(--font-mono);
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--patina);
|
||||
}
|
||||
.lane-site {
|
||||
font: 400 19px/1.2 var(--font-display);
|
||||
color: var(--paper-dim);
|
||||
}
|
||||
/* The whole row leans patina when the Lane needs attention, so the scan is one
|
||||
pass down the left edge rather than a read of every mark. */
|
||||
.lanelist li.attention .lane-site { color: var(--patina); }
|
||||
.lane-browser { padding: 12px 0 0; }
|
||||
/* Revocation cuts someone off, so it wears --danger. Ember stays reserved for
|
||||
the new-chapter signal. */
|
||||
.ghost.danger { color: var(--danger); }
|
||||
@@ -600,6 +643,9 @@ button { cursor: pointer; }
|
||||
box-shadow: inset 0 -2px 0 var(--ember);
|
||||
}
|
||||
.topbar form { margin-left: 18px; }
|
||||
/* The admin page's topbar has no switch to fill the middle, so its back link
|
||||
keeps company with Log out at the right edge instead of floating centre. */
|
||||
.topbar .back { margin-left: auto; }
|
||||
/* At phone width brand + switch + Log out do not fit on one line, so the
|
||||
switch takes its own row under the wordmark rather than pushing Log out
|
||||
off-screen. */
|
||||
@@ -609,6 +655,9 @@ button { cursor: pointer; }
|
||||
.libswitch { order: 3; margin-left: 0; }
|
||||
.libswitch a { flex: 1; text-align: center; padding: 8px 14px; }
|
||||
.topbar form { margin-left: 12px; }
|
||||
/* The admin page has no switch to take the second row, so its brand claims
|
||||
the first outright and the back link keeps Log out company below. */
|
||||
.topbar:has(.back) .brand { flex: 1 1 100%; }
|
||||
}
|
||||
|
||||
/* ---- action strip: full-width on a phone, hairline-divided cells ---- */
|
||||
|
||||
Reference in New Issue
Block a user