Files
minstrel/internal/api/admin_summary.go
T
bvandeusenandClaude Opus 5.5 ace7e22e6a
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m10s
release / android (push) Successful in 5m16s
release / Build signed APK (releases and dev) (push) Successful in 5m27s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Skipped
feat: Admin is a left rail with how many wait on you in each queue, and the Overview opens with a verdict, the queues that need you and since when, then the clear ones on one line (M503 #5605)
- GET /api/admin/summary: every admin queue (requests, reported tracks,
  playback errors, duplicates, suspect sources, missing files) with its
  count and the age of its oldest item, in one query.
- The admin layout drops the 11-tab strip for SectionRail, as Settings and
  the Collection have, with each queue's count as its badge; the counts
  refresh on each admin navigation and every 30s.
- The Overview leads with "Nothing needs you." or "N things need you.",
  lists each waiting queue with "oldest 3d ago" and a link to it, counts a
  failed library scan as a problem, and names the empty queues on one line.
  The request and quarantine previews and the stat tiles are gone; those
  queues are worked on their own pages. A failed summary says so with Try
  again instead of reading as all-clear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:58:13 -04:00

60 lines
1.9 KiB
Go

package api
import (
"net/http"
"github.com/jackc/pgx/v5/pgtype"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
// adminQueueView is one queue that waits on a person: how many, and since
// when the oldest of them has waited (null when the queue is empty).
type adminQueueView struct {
Count int64 `json:"count"`
Since *string `json:"since"`
}
// adminSummaryView is what the admin rail and the Overview's verdict read
// (#5605): every queue an admin acts on, in one response, so each admin page
// pays one cheap read for its rail.
type adminSummaryView struct {
Requests adminQueueView `json:"requests"`
Quarantined adminQueueView `json:"quarantined"`
PlaybackErrors adminQueueView `json:"playback_errors"`
Duplicates adminQueueView `json:"duplicates"`
Suspect adminQueueView `json:"suspect"`
Missing adminQueueView `json:"missing"`
}
func queueView(count int64, since pgtype.Timestamptz) adminQueueView {
v := adminQueueView{Count: count}
if count > 0 && since.Valid {
s := formatTimestamp(since)
v.Since = &s
}
return v
}
func adminSummaryFrom(row dbq.AdminSummaryRow) adminSummaryView {
return adminSummaryView{
Requests: queueView(row.Requests, row.RequestsSince),
Quarantined: queueView(row.Quarantined, row.QuarantinedSince),
PlaybackErrors: queueView(row.PlaybackErrors, row.PlaybackErrorsSince),
Duplicates: queueView(row.Duplicates, row.DuplicatesSince),
Suspect: queueView(row.Suspect, row.SuspectSince),
Missing: queueView(row.Missing, row.MissingSince),
}
}
// handleGetAdminSummary implements GET /api/admin/summary.
func (h *handlers) handleGetAdminSummary(w http.ResponseWriter, r *http.Request) {
row, err := dbq.New(h.pool).AdminSummary(r.Context())
if err != nil {
h.logger.Error("admin: summary", "err", err)
writeAdminJSONErr(w, http.StatusInternalServerError, "server_error")
return
}
writeJSON(w, http.StatusOK, adminSummaryFrom(row))
}