web: owner-only /admin with the Reader roster and Poll Lane status (#102)
The only operational surface was /healthz and a fold-out roster inside the owner's own reading page. This gives the owner a page: two sections of facts on the same measured sheet, no cards. - admin.go holds every route that reaches past the acting Reader, listed once in adminRoutes() and wrapped in requireOwner at registration - a missing gate is visible in the route list rather than hidden inside a handler. A non-owner gets 404, the same answer revoke already gave. - Lane figures arrive through the LaneReporter seam, so the page reads the running poller rather than a table. newRouter converts a nil *Poller to a nil interface: a typed nil would make the page claim a poller exists. - The roster moves out of the reading page and gains the Sighting counters, the blocked verdict and Clear marks. Clearing restores a privilege, so it is a plain ghost button; --danger stays with revocation. - --patina is the page's one accent, held at the weight of the other action accents. Neither --ember (new chapter) nor --danger (destruction) is borrowed for system health.
This commit is contained in:
@@ -87,6 +87,10 @@
|
||||
--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, held at the same
|
||||
muted weight as --brass so it never competes with ember. Neither ember
|
||||
(new chapter) nor danger (destruction) may say "system unhealthy". */
|
||||
--patina: #b08a4a;
|
||||
|
||||
/* Desktop cell borders for the two coloured action states. */
|
||||
--play-hot-line: #3a1d18;
|
||||
@@ -146,6 +150,7 @@
|
||||
--moss: #3d6c46;
|
||||
--clay: #7c5533;
|
||||
--trash: #8c6558;
|
||||
--patina: #7a5a1e;
|
||||
|
||||
--play-hot-line: #f0cfc6;
|
||||
--fav-line: #e3d3a4;
|
||||
@@ -290,9 +295,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 +317,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 +330,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 +642,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 +654,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