package httpapi import ( "html/template" "net/http" "net/url" "strings" "time" ) type statusPageData struct { GeneratedAt string RefreshSeconds int Filters statusPageFilters QuickFilters []statusFilterLink Overview screenStatusOverview StatusAPIPath string StatusPagePath string } type statusPageFilters struct { ServerConnectivity string Stale string UpdatedSince string Limit string } type statusFilterLink struct { Label string Href string Class string Active bool } var statusPageTemplate = template.Must(template.New("status-page").Funcs(template.FuncMap{ "connectivityLabel": connectivityLabel, "screenDetailPath": screenDetailPath, "statusClass": statusClass, "timestampLabel": timestampLabel, }).Parse(` Screen Status

Screen Status

A compact browser view of the latest screen reports from the current in-memory status overview. Offline and degraded screens stay at the top for quick diagnostics.

{{.Overview.Summary.Total}} screens
Updated {{.GeneratedAt}}
{{.Overview.Summary.Total}} Total known screens
{{.Overview.Summary.Offline}} Offline
{{.Overview.Summary.Degraded}} Degraded
{{.Overview.Summary.Online}} Online
{{.Overview.Summary.Stale}} Stale reports

Filters and refresh

This page refreshes every {{.RefreshSeconds}} seconds. Use the shortcut links or the form to narrow the existing connectivity and freshness filters without leaving the lightweight server-rendered flow.

JSON overview

Quick views

{{range .QuickFilters}} {{.Label}} {{end}}
Clear

Latest reports

Each row links directly to the existing per-screen JSON detail endpoint for a quick drill-down into the raw status payload.

{{if .Overview.Screens}}
{{range .Overview.Screens}} {{end}}
Screen Derived state Player status Server link Received Heartbeat
{{.ScreenID}}
{{if .MQTTBroker}}{{.MQTTBroker}}{{else if .ServerURL}}{{.ServerURL}}{{else}}No endpoint details{{end}}
{{.DerivedState}}
{{.Status}}
{{if .Stale}}Marked stale by server freshness check{{else}}Fresh within expected heartbeat window{{end}}
{{connectivityLabel .ServerConnectivity}} {{if .ServerURL}}{{.ServerURL}}{{end}}
{{timestampLabel .ReceivedAt}}
{{if .LastHeartbeatAt}}Heartbeat {{timestampLabel .LastHeartbeatAt}}{{end}}
{{if gt .HeartbeatEverySeconds 0}}{{.HeartbeatEverySeconds}}s{{else}}-{{end}} {{if .StartedAt}}Started {{timestampLabel .StartedAt}}{{end}}
{{else}}

No screen has reported status yet. Once a player posts to the existing status API, it will appear here automatically.

{{end}}
`)) func handleStatusPage(store playerStatusStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { overview, err := buildScreenStatusOverview(store, r.URL.Query()) if err != nil { writeOverviewQueryError(w, err) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) data := buildStatusPageData(store, r.URL.Query(), overview) if err := statusPageTemplate.Execute(w, data); err != nil { http.Error(w, "failed to render status page", http.StatusInternalServerError) } } } func buildStatusPageData(store playerStatusStore, query url.Values, overview screenStatusOverview) statusPageData { filters := statusPageFilters{ ServerConnectivity: strings.TrimSpace(query.Get("server_connectivity")), Stale: strings.TrimSpace(query.Get("stale")), UpdatedSince: strings.TrimSpace(query.Get("updated_since")), Limit: strings.TrimSpace(query.Get("limit")), } return statusPageData{ GeneratedAt: store.Now().Format(time.RFC3339), RefreshSeconds: 15, Filters: filters, QuickFilters: buildStatusQuickFilters(filters), Overview: overview, StatusAPIPath: buildOverviewPath("/api/v1/screens/status", filters), StatusPagePath: "/status", } } func buildStatusQuickFilters(filters statusPageFilters) []statusFilterLink { return []statusFilterLink{ { Label: "All screens", Href: buildStatusPageHref(statusPageFilters{Limit: filters.Limit, UpdatedSince: filters.UpdatedSince}), Class: "", Active: filters.ServerConnectivity == "" && filters.Stale == "", }, { Label: "Connectivity offline", Href: buildStatusPageHref(statusPageFilters{ServerConnectivity: "offline", Limit: filters.Limit, UpdatedSince: filters.UpdatedSince}), Class: "offline", Active: filters.ServerConnectivity == "offline" && filters.Stale == "", }, { Label: "Connectivity degraded", Href: buildStatusPageHref(statusPageFilters{ServerConnectivity: "degraded", Limit: filters.Limit, UpdatedSince: filters.UpdatedSince}), Class: "degraded", Active: filters.ServerConnectivity == "degraded" && filters.Stale == "", }, { Label: "Stale reports", Href: buildStatusPageHref(statusPageFilters{Stale: "true", Limit: filters.Limit, UpdatedSince: filters.UpdatedSince}), Class: "", Active: filters.ServerConnectivity == "" && filters.Stale == "true", }, { Label: "Fresh reports", Href: buildStatusPageHref(statusPageFilters{Stale: "false", Limit: filters.Limit, UpdatedSince: filters.UpdatedSince}), Class: "online", Active: filters.ServerConnectivity == "" && filters.Stale == "false", }, } } func buildStatusPageHref(filters statusPageFilters) string { return buildOverviewPath("/status", filters) } func buildOverviewPath(basePath string, filters statusPageFilters) string { query := url.Values{} if filters.ServerConnectivity != "" { query.Set("server_connectivity", filters.ServerConnectivity) } if filters.Stale != "" { query.Set("stale", filters.Stale) } if filters.UpdatedSince != "" { query.Set("updated_since", filters.UpdatedSince) } if filters.Limit != "" { query.Set("limit", filters.Limit) } encoded := query.Encode() if encoded == "" { return basePath } return basePath + "?" + encoded } func statusClass(value string) string { trimmed := strings.TrimSpace(value) if trimmed == "" { return "degraded" } return trimmed } func connectivityLabel(value string) string { trimmed := strings.TrimSpace(value) if trimmed == "" { return "unknown" } return trimmed } func screenDetailPath(screenID string) string { return "/api/v1/screens/" + url.PathEscape(strings.TrimSpace(screenID)) + "/status" } func timestampLabel(value string) string { trimmed := strings.TrimSpace(value) if trimmed == "" { return "-" } return trimmed }