Merge ticket/145-lanes-from-db
This commit is contained in:
@@ -6,18 +6,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"bookmarkmanager/backend/internal/latest"
|
||||
"bookmarkmanager/backend/internal/store"
|
||||
)
|
||||
|
||||
// LaneReporter is the administrative page's whole window onto the running
|
||||
// poller: one snapshot of Poll Lane state, copied out of memory on request.
|
||||
// The Poller satisfies it in production and a fake with fixed values satisfies
|
||||
// it in tests, so the page's tests need neither a poller nor a Site.
|
||||
type LaneReporter interface {
|
||||
LaneStatus() latest.Status
|
||||
}
|
||||
|
||||
// ownerWindow is the staleness boundary the Series list's "not checked in
|
||||
// 12h" filter compares against. Declared once; later admin tickets read it.
|
||||
const ownerWindow = 12 * time.Hour
|
||||
@@ -36,50 +27,6 @@ type adminView struct {
|
||||
Detail seriesDetailView
|
||||
}
|
||||
|
||||
// lanesView is the Lane status block: one row per Site that has run, plus the
|
||||
// browser fact, which is shared by the three browser Sites rather than held
|
||||
// once per Site.
|
||||
type lanesView struct {
|
||||
Rows []laneRow
|
||||
// PollerOff means no poller is running at all (disabled by config, or its
|
||||
// client could not be built). The browser line must not answer "not
|
||||
// configured" then: the sidecar is not the reason nothing is polled.
|
||||
PollerOff bool
|
||||
BrowserConfigured bool
|
||||
BrowserReachable bool
|
||||
}
|
||||
|
||||
// laneRow is one Lane formatted for reading rather than for arithmetic: the
|
||||
// template renders strings and flags, and every judgement about what they mean
|
||||
// is made here.
|
||||
type laneRow struct {
|
||||
Site string
|
||||
Due int
|
||||
Ran string
|
||||
// Checked is how many Series the last pass read. Due without Checked is a
|
||||
// Lane that has stopped working; the two figures side by side are what
|
||||
// separate that from a Lane with nothing to do.
|
||||
Checked int
|
||||
// Gap is empty when no pass has reached the pace yet, so the row omits the
|
||||
// figure instead of stating a zero.
|
||||
Gap string
|
||||
Clamped bool
|
||||
Refusing bool
|
||||
// BrowserLost marks a Lane whose pages can only be read through the
|
||||
// sidecar while the sidecar is unreachable — including the case where none
|
||||
// is configured, which stops those Series just as completely.
|
||||
BrowserLost bool
|
||||
// Stalled marks a Lane with Series waiting that its last pass did not read
|
||||
// — the difference between a stopped Lane and a quiet one (story 13). A
|
||||
// browser Lane holding Chrome asleep under the wake thresholds is neither,
|
||||
// so it carries Asleep instead and never Stalled.
|
||||
Stalled bool
|
||||
Asleep bool
|
||||
// Attention is the one flag the template colours on, so an unhealthy Lane
|
||||
// is found at a glance rather than read for.
|
||||
Attention bool
|
||||
}
|
||||
|
||||
// adminRoute pairs a route pattern with its handler so the route list and the
|
||||
// gate cannot drift apart.
|
||||
type adminRoute struct {
|
||||
@@ -134,11 +81,6 @@ func (h *Handler) admin(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderAdmin(w, adminView{Page: "overview"})
|
||||
}
|
||||
|
||||
// adminLanes renders the page that hosts the live Lane fragment.
|
||||
func (h *Handler) adminLanes(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderAdmin(w, adminView{Page: "lanes", Lanes: h.lanesView()})
|
||||
}
|
||||
|
||||
// adminReaders renders the Reader roster on its own bookmarkable page.
|
||||
func (h *Handler) adminReaders(w http.ResponseWriter, r *http.Request) {
|
||||
readers, err := h.store.Readers()
|
||||
@@ -154,65 +96,6 @@ func (h *Handler) renderAdmin(w http.ResponseWriter, view adminView) {
|
||||
h.render(w, http.StatusOK, "admin", view)
|
||||
}
|
||||
|
||||
// uiLanes answers the status block's own refresh. Only the block refreshes on a
|
||||
// timer; the roster re-renders after an action, as it always has.
|
||||
func (h *Handler) uiLanes(w http.ResponseWriter, r *http.Request) {
|
||||
h.render(w, http.StatusOK, "lanes", h.lanesView())
|
||||
}
|
||||
|
||||
// lanesView copies the poller's snapshot into display form. A nil reporter (no
|
||||
// poller running) and a poller no Lane has reported to yet are the same thing
|
||||
// to the page: no data, which it must say rather than draw as confident zeroes
|
||||
// — an empty page a few seconds after a restart must not read as a stopped one.
|
||||
func (h *Handler) lanesView() lanesView {
|
||||
if h.lanes == nil {
|
||||
return lanesView{PollerOff: true}
|
||||
}
|
||||
snap := h.lanes.LaneStatus()
|
||||
v := lanesView{
|
||||
Rows: make([]laneRow, 0, len(snap.Lanes)),
|
||||
BrowserConfigured: snap.BrowserConfigured,
|
||||
BrowserReachable: snap.BrowserReachable,
|
||||
}
|
||||
now := time.Now()
|
||||
for _, l := range snap.Lanes {
|
||||
lost := l.Browser && !snap.BrowserReachable
|
||||
// Series waiting and none read is the shape of a Lane that has stopped
|
||||
// working, as distinct from one that is quiet for want of work — or one
|
||||
// deliberately leaving Chrome asleep until its group gathers.
|
||||
stalled := l.Due > 0 && l.Checked == 0 && !l.Asleep
|
||||
gap := ""
|
||||
if l.Gap > 0 {
|
||||
gap = l.Gap.Truncate(time.Second).String()
|
||||
}
|
||||
v.Rows = append(v.Rows, laneRow{
|
||||
Site: l.Site,
|
||||
Due: l.Due,
|
||||
Ran: since(now, l.LastRun),
|
||||
Checked: l.Checked,
|
||||
Gap: gap,
|
||||
Clamped: l.Clamped,
|
||||
Refusing: l.Refusing,
|
||||
BrowserLost: lost,
|
||||
Stalled: stalled,
|
||||
Asleep: l.Asleep,
|
||||
Attention: l.Clamped || l.Refusing || lost || stalled,
|
||||
})
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// since formats how long ago a Lane last ran, at second resolution: the block
|
||||
// refreshes every thirty seconds, so anything finer is noise the owner would
|
||||
// have to ignore.
|
||||
func since(now, then time.Time) string {
|
||||
d := now.Sub(then).Truncate(time.Second)
|
||||
if d < time.Second {
|
||||
return "just now"
|
||||
}
|
||||
return d.String() + " ago"
|
||||
}
|
||||
|
||||
// revokeReaderSessions logs one Reader out of every browser they are signed in
|
||||
// on. The owner gate is the route's, not this handler's.
|
||||
func (h *Handler) revokeReaderSessions(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user