Turn the console landing page into an operational dashboard. - new internal/opsstatus: read-only Postgres projection via go-jet — ping + per-status COUNT/GROUP BY on runtime_records, mail_deliveries, notification_routes, and a malformed-intent count; degrades per-probe into Snapshot.Errors rather than failing the page - dashboard renders backend readiness, database health, the three status tables, the malformed count, and any collection errors; falls back to a "monitoring not wired" note when no reader is injected - AdminConsoleHandlers now takes an AdminConsoleDeps struct (Monitor + Ready added) so later stages add service refs without churning the signature Tests: opsstatus store test against a Postgres testcontainer (empty schema + one enqueued delivery); dashboard render tests with a fake reader (with and without monitoring). Docs: ARCHITECTURE 14.1 + FUNCTIONAL 10.2.1 (+ru) describe the dashboard. (Prometheus /metrics exporters were already enabled in dev-deploy in Stage 1.)
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"galaxy/backend/internal/adminconsole"
|
||||
"galaxy/backend/internal/opsstatus"
|
||||
"galaxy/backend/internal/server/httperr"
|
||||
"galaxy/backend/internal/server/middleware/basicauth"
|
||||
|
||||
@@ -25,22 +26,38 @@ type AdminConsoleHandlers struct {
|
||||
renderer *adminconsole.Renderer
|
||||
csrf *adminconsole.CSRF
|
||||
assets http.Handler
|
||||
monitor opsstatus.Reader
|
||||
ready func() bool
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAdminConsoleHandlers constructs the console handler set. A nil renderer
|
||||
// falls back to the embedded default templates; a nil csrf falls back to a
|
||||
// fresh per-process random key; a nil logger falls back to zap.NewNop. It
|
||||
// AdminConsoleDeps bundles the collaborators for the operator console. Every
|
||||
// field is optional: a nil Renderer or CSRF falls back to the embedded default
|
||||
// templates and a per-process random key; a nil Monitor renders the dashboard
|
||||
// without the monitoring panels; a nil Ready reports backend readiness as not
|
||||
// ready; a nil Logger falls back to zap.NewNop.
|
||||
type AdminConsoleDeps struct {
|
||||
Renderer *adminconsole.Renderer
|
||||
CSRF *adminconsole.CSRF
|
||||
Monitor opsstatus.Reader
|
||||
Ready func() bool
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAdminConsoleHandlers constructs the console handler set from deps. It
|
||||
// panics only on conditions that are unrecoverable at startup (template parse
|
||||
// failure or crypto/rand failure), both of which indicate a broken build or
|
||||
// host rather than a runtime input.
|
||||
func NewAdminConsoleHandlers(renderer *adminconsole.Renderer, csrf *adminconsole.CSRF, logger *zap.Logger) *AdminConsoleHandlers {
|
||||
func NewAdminConsoleHandlers(deps AdminConsoleDeps) *AdminConsoleHandlers {
|
||||
logger := deps.Logger
|
||||
if logger == nil {
|
||||
logger = zap.NewNop()
|
||||
}
|
||||
renderer := deps.Renderer
|
||||
if renderer == nil {
|
||||
renderer = adminconsole.MustNewRenderer()
|
||||
}
|
||||
csrf := deps.CSRF
|
||||
if csrf == nil {
|
||||
generated, err := adminconsole.NewRandomCSRF()
|
||||
if err != nil {
|
||||
@@ -58,17 +75,46 @@ func NewAdminConsoleHandlers(renderer *adminconsole.Renderer, csrf *adminconsole
|
||||
renderer: renderer,
|
||||
csrf: csrf,
|
||||
assets: http.StripPrefix("/_gm/assets/", http.FileServer(http.FS(assetsFS))),
|
||||
monitor: deps.Monitor,
|
||||
ready: deps.Ready,
|
||||
logger: logger.Named("http.admin.console"),
|
||||
}
|
||||
}
|
||||
|
||||
// Dashboard renders the console landing page (GET /_gm and GET /_gm/).
|
||||
// Dashboard renders the console landing page (GET /_gm and GET /_gm/),
|
||||
// including the monitoring panels when an ops-status reader is wired.
|
||||
func (h *AdminConsoleHandlers) Dashboard() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
h.render(c, http.StatusOK, "dashboard", "dashboard", "Dashboard", nil)
|
||||
data := adminconsole.DashboardData{}
|
||||
if h.ready != nil {
|
||||
data.BackendReady = h.ready()
|
||||
}
|
||||
if h.monitor != nil {
|
||||
data.MonitorAvailable = true
|
||||
snapshot := h.monitor.Collect(c.Request.Context())
|
||||
data.PostgresHealthy = snapshot.PostgresHealthy
|
||||
data.Runtimes = toViewCounts(snapshot.Runtimes)
|
||||
data.MailDeliveries = toViewCounts(snapshot.MailDeliveries)
|
||||
data.NotificationRoutes = toViewCounts(snapshot.NotificationRoutes)
|
||||
data.NotificationMalformed = snapshot.NotificationMalformed
|
||||
data.Errors = snapshot.Errors
|
||||
}
|
||||
h.render(c, http.StatusOK, "dashboard", "dashboard", "Dashboard", data)
|
||||
}
|
||||
}
|
||||
|
||||
// toViewCounts maps ops-status counts to the console's view-layer counts.
|
||||
func toViewCounts(in []opsstatus.StatusCount) []adminconsole.StatusCount {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]adminconsole.StatusCount, len(in))
|
||||
for i, sc := range in {
|
||||
out[i] = adminconsole.StatusCount{Status: sc.Status, Count: sc.Count}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Asset serves the embedded console static assets under `/_gm/assets/`.
|
||||
func (h *AdminConsoleHandlers) Asset() gin.HandlerFunc {
|
||||
return gin.WrapH(h.assets)
|
||||
|
||||
Reference in New Issue
Block a user