Make source-health page public with a trimmed reader view

/status was admin-only (404 for everyone else). Serve it to all: a
reader view with per-feed live/idle/delayed status and last-update time,
while admins additionally get poll cadence, item/story counts, paywall
rates, posting times, and raw fetch errors. Error strings are stripped
server-side for non-admins so feed-specific workarounds and upstream URLs
never reach the public payload. Nav status link now shows for everyone.
This commit is contained in:
prosolis
2026-07-07 17:56:22 -07:00
parent 35850eaf73
commit 77581ac152
5 changed files with 85 additions and 50 deletions

View File

@@ -48,17 +48,16 @@ type sourceStatus struct {
type statusPage struct {
pageData
Sources []sourceStatus
DegradedCnt int // sources currently failing
DegradedCnt int // sources currently failing
Admin bool // viewer is an admin: show the full diagnostic columns
}
// handleStatus renders the owner-facing source-health dashboard. Access is
// restricted to admin subjects; everyone else gets a 404 so the page's
// existence isn't advertised.
// handleStatus renders the source-health page. It's public: everyone sees a
// trimmed reader view (per-feed up/stale/idle and when each last updated), while
// admins additionally get the operator diagnostics (poll cadence, item counts,
// paywall rates, posting times, and raw fetch errors).
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
if !s.isAdmin(r) {
http.NotFound(w, r)
return
}
admin := s.isAdmin(r)
s.track(r, "status")
health, err := storage.ListSourceHealth()
@@ -92,6 +91,11 @@ func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
Paywalled: st.Paywalled,
}
row.Healthy = hasHealth && h.ConsecutiveFailures == 0
// Raw fetch errors leak feed-specific workarounds and upstream URLs, so
// they stay out of the public payload entirely (not just hidden in CSS).
if !admin {
row.LastError = ""
}
if h.LastPollAt > 0 {
row.LastPollAt = time.Unix(h.LastPollAt, 0)
}
@@ -124,5 +128,5 @@ func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
base := s.base(r)
base.Active = "status"
s.render(w, "status", statusPage{pageData: base, Sources: rows, DegradedCnt: degraded})
s.render(w, "status", statusPage{pageData: base, Sources: rows, DegradedCnt: degraded, Admin: admin})
}