1e6f1e985d
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>
42 lines
1.9 KiB
HTML
42 lines
1.9 KiB
HTML
{{/* Poll Lane status: one row per Site, refreshing itself so a run can be
|
|
watched rather than sampled by reloading. The refresh is one attribute on
|
|
the fragment root and the endpoint answers with this same fragment, so the
|
|
swap replaces the element that asked for it.
|
|
|
|
Every figure here is read out of the running poller, never out of a table:
|
|
a Site absent from Rows has not completed a pass since the last restart,
|
|
which the empty state must say — zeroes would read as a stopped Lane. */}}
|
|
{{define "lanes"}}
|
|
<section class="lanes" id="lanes"
|
|
hx-get="/ui/admin/lanes" hx-trigger="every 30s" hx-swap="outerHTML">
|
|
<h2>Poll Lanes</h2>
|
|
{{if .Rows}}
|
|
<ul class="lanelist">
|
|
{{range .Rows}}
|
|
<li{{if .Attention}} class="attention"{{end}}>
|
|
<span class="lane-site">{{.Site}}</span>
|
|
<span class="lane-fact">{{.Due}} due</span>
|
|
<span class="lane-fact">{{.Checked}} checked</span>
|
|
<span class="lane-fact">ran {{.Ran}}</span>
|
|
{{if .Gap}}<span class="lane-fact">gap {{.Gap}}</span>{{end}}
|
|
{{if .Clamped}}<span class="lane-mark">gap at floor</span>{{end}}
|
|
{{if .Refusing}}<span class="lane-mark">refusing</span>{{end}}
|
|
{{if .BrowserLost}}<span class="lane-mark">no browser</span>{{end}}
|
|
{{if .Stalled}}<span class="lane-mark">not checking</span>{{end}}
|
|
</li>
|
|
{{end}}
|
|
</ul>
|
|
{{else}}
|
|
<p class="setup-copy">No data yet — no Lane has completed a pass since the
|
|
backend started.</p>
|
|
{{end}}
|
|
<p class="setup-copy lane-browser">
|
|
{{if .PollerOff}}Polling is switched off in this deployment: no Lane runs,
|
|
and Latest Chapter comes from the userscripts alone.
|
|
{{else}}Browser sidecar:
|
|
{{if not .BrowserConfigured}}not configured — comix, kagane and novelfull
|
|
pages are not fetched through it{{else if .BrowserReachable}}reachable
|
|
{{else}}unreachable{{end}}.{{end}}</p>
|
|
</section>
|
|
{{end}}
|