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.
|
rest.
|
||||||
|
|
||||||
**Refusals and browser loss are Lane-local.** Two `errChallengeHeld` in a pass
|
**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
|
`errBrowserInterrupted` (remote Chrome restarted) sets a shared Poller flag so
|
||||||
the *other* browser Lanes skip their passes for the same window — otherwise a
|
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
|
restarting Chrome stamps one Series per Lane per pass, burning rests on
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"bookmarkmanager/backend/internal/store"
|
"bookmarkmanager/backend/internal/store"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ func (h *Handler) lanesView() lanesView {
|
|||||||
passes, err := h.store.LatestLanePasses()
|
passes, err := h.store.LatestLanePasses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("admin lanes: latest passes: %v", err)
|
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
|
return v
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -14,9 +14,8 @@
|
|||||||
<div class="sechead">
|
<div class="sechead">
|
||||||
<h2 class="sec">Poll Lanes</h2>
|
<h2 class="sec">Poll Lanes</h2>
|
||||||
<p class="statusline">
|
<p class="statusline">
|
||||||
{{if .PollerOff}}Polling is switched off in this deployment: no Lane
|
{{if .PollerOff}}Polling: <span class="mark-faint">off</span>
|
||||||
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 bad">unreachable</span>{{end}}{{end}}
|
||||||
{{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}}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{if .Rows}}
|
{{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
|
// HTTP client cannot be built. Any problem here is logged and skipped: this
|
||||||
// feature going missing degrades the service to userscript-only latest-chapter
|
// feature going missing degrades the service to userscript-only latest-chapter
|
||||||
// tracking, which is exactly how it behaved before. It returns the running
|
// 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 {
|
func startLatestPoller(ctx context.Context, s *store.Store, cfg LatestPoll, browser latest.Fetcher) *latest.Poller {
|
||||||
if !cfg.Enabled {
|
if !cfg.Enabled {
|
||||||
log.Println("latest-chapter poller: disabled by config")
|
log.Println("latest-chapter poller: disabled by config")
|
||||||
|
|||||||
+5
-5
@@ -885,7 +885,7 @@ func TestAdminPageWithoutAPollerSaysSo(t *testing.T) {
|
|||||||
cfg Config
|
cfg Config
|
||||||
want, unwant string
|
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"},
|
{"browser not configured", lanesConfig(), "not configured", "Polling is switched off"},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -986,7 +986,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
|
|||||||
router, st := newWebTestServer(t, lanesConfig())
|
router, st := newWebTestServer(t, lanesConfig())
|
||||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.UnixMilli(), Skip: latest.SkipNoFetcher})
|
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.UnixMilli(), Skip: latest.SkipNoFetcher})
|
||||||
body := lanesBody(t, router, st)
|
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)
|
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)
|
router, st := newWebTestServer(t, cfg)
|
||||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
|
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
|
||||||
body := lanesBody(t, router, st)
|
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)
|
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)
|
router, st := newWebTestServer(t, cfg)
|
||||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Checked: 3})
|
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Checked: 3})
|
||||||
body := lanesBody(t, router, st)
|
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)
|
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)
|
router, st := newWebTestServer(t, cfg)
|
||||||
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-30 * time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
|
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-30 * time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
|
||||||
body := lanesBody(t, router, st)
|
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)
|
t.Errorf("a sidecar-down older than the backoff must read as reachable:\n%s", body)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user