Admin dashboard: pages, Lane observability, poll pass log, per-Series intervention (#134) #148
+1
-1
@@ -134,7 +134,7 @@ unchanged read is exactly the Sighting worth deferring a Poll on.
|
||||
rest.
|
||||
|
||||
**Refusals and browser loss are Lane-local.** Two `errChallengeHeld` in a pass
|
||||
stop that Site for `refuseBackoff` while other Lanes continue. An
|
||||
stop that Site for `RefuseBackoff` while other Lanes continue. An
|
||||
`errBrowserInterrupted` (remote Chrome restarted) sets a shared Poller flag so
|
||||
the *other* browser Lanes skip their passes for the same window — otherwise a
|
||||
restarting Chrome stamps one Series per Lane per pass, burning rests on
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"bookmarkmanager/backend/internal/store"
|
||||
)
|
||||
|
||||
@@ -81,6 +81,10 @@ func (h *Handler) lanesView() lanesView {
|
||||
passes, err := h.store.LatestLanePasses()
|
||||
if err != nil {
|
||||
log.Printf("admin lanes: latest passes: %v", err)
|
||||
// No evidence of a lost sidecar reads as reachable, per the same rule
|
||||
// browserReachable applies: a store failure must not condemn the
|
||||
// browser. The empty table already says no Lane has recorded a pass.
|
||||
v.BrowserReachable = true
|
||||
return v
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
<div class="sechead">
|
||||
<h2 class="sec">Poll Lanes</h2>
|
||||
<p class="statusline">
|
||||
{{if .PollerOff}}Polling is switched off in this deployment: no Lane
|
||||
runs, and Latest Chapter comes from the userscripts alone.
|
||||
{{else}}Browser: {{if not .BrowserConfigured}}<span class="mark-faint">not configured</span>{{else if .BrowserReachable}}<span class="mark mark-strong">reachable</span>{{else}}<span class="mark mark-strong bad">unreachable</span>{{end}}{{end}}
|
||||
{{if .PollerOff}}Polling: <span class="mark-faint">off</span>
|
||||
{{else}}Browser: {{if not .BrowserConfigured}}<span class="mark-faint">not configured</span>{{else if .BrowserReachable}}<span class="mark mark-strong">reachable</span>{{else}}<span class="mark bad">unreachable</span>{{end}}{{end}}
|
||||
</p>
|
||||
</div>
|
||||
{{if .Rows}}
|
||||
|
||||
+2
-1
@@ -343,7 +343,8 @@ func newLatestPoller(s *store.Store, cfg LatestPoll, fetch, browser latest.Fetch
|
||||
// HTTP client cannot be built. Any problem here is logged and skipped: this
|
||||
// feature going missing degrades the service to userscript-only latest-chapter
|
||||
// tracking, which is exactly how it behaved before. It returns the running
|
||||
// Poller, or nil when there is none — the admin page's Lane status reads it.
|
||||
// Poller, or nil when there is none; the caller starts it for its own sake —
|
||||
// the Lanes page reads the pass log, so no return value is wired anywhere.
|
||||
func startLatestPoller(ctx context.Context, s *store.Store, cfg LatestPoll, browser latest.Fetcher) *latest.Poller {
|
||||
if !cfg.Enabled {
|
||||
log.Println("latest-chapter poller: disabled by config")
|
||||
|
||||
+5
-5
@@ -885,7 +885,7 @@ func TestAdminPageWithoutAPollerSaysSo(t *testing.T) {
|
||||
cfg Config
|
||||
want, unwant string
|
||||
}{
|
||||
{"polling switched off", testConfig(), "Polling is switched off", "not configured"},
|
||||
{"polling switched off", testConfig(), `Polling: <span class="mark-faint">off</span>`, "not configured"},
|
||||
{"browser not configured", lanesConfig(), "not configured", "Polling is switched off"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -986,7 +986,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
|
||||
router, st := newWebTestServer(t, lanesConfig())
|
||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.UnixMilli(), Skip: latest.SkipNoFetcher})
|
||||
body := lanesBody(t, router, st)
|
||||
if !strings.Contains(body, "not configured") || strings.Contains(body, "unreachable") {
|
||||
if !strings.Contains(body, `mark-faint">not configured`) || strings.Contains(body, "unreachable") {
|
||||
t.Errorf("unset BROWSER_WS_URL must read as not configured:\n%s", body)
|
||||
}
|
||||
})
|
||||
@@ -997,7 +997,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
|
||||
router, st := newWebTestServer(t, cfg)
|
||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
|
||||
body := lanesBody(t, router, st)
|
||||
if !strings.Contains(body, "unreachable") {
|
||||
if !strings.Contains(body, `bad">unreachable`) {
|
||||
t.Errorf("recent sidecar-down passes must read as unreachable:\n%s", body)
|
||||
}
|
||||
})
|
||||
@@ -1008,7 +1008,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
|
||||
router, st := newWebTestServer(t, cfg)
|
||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Checked: 3})
|
||||
body := lanesBody(t, router, st)
|
||||
if !strings.Contains(body, "reachable") {
|
||||
if !strings.Contains(body, `mark-strong">reachable`) {
|
||||
t.Errorf("clean browser passes must read as reachable:\n%s", body)
|
||||
}
|
||||
})
|
||||
@@ -1019,7 +1019,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
|
||||
router, st := newWebTestServer(t, cfg)
|
||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-30 * time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
|
||||
body := lanesBody(t, router, st)
|
||||
if !strings.Contains(body, "reachable") {
|
||||
if !strings.Contains(body, `mark-strong">reachable`) {
|
||||
t.Errorf("a sidecar-down older than the backoff must read as reachable:\n%s", body)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user