package handlers import ( "database/sql" "net/http" "runtime" "time" "github.com/gin-gonic/gin" "chat-switchboard/database" "chat-switchboard/events" "chat-switchboard/health" "chat-switchboard/models" "chat-switchboard/store" ) // ── Dashboard Admin Handler ───────────────── // GET /api/v1/admin/dashboard // Aggregates live operational data for the built-in admin monitoring page. var processStartTime = time.Now() type DashboardAdminHandler struct { stores store.Stores healthStore health.Store hub *events.Hub } func NewDashboardAdminHandler(stores store.Stores, hs health.Store, hub *events.Hub) *DashboardAdminHandler { return &DashboardAdminHandler{stores: stores, healthStore: hs, hub: hub} } type runtimeStats struct { Goroutines int `json:"goroutines"` HeapMB int `json:"heap_mb"` SysMB int `json:"sys_mb"` NumGC uint32 `json:"num_gc"` GoVersion string `json:"go_version"` } func (h *DashboardAdminHandler) GetDashboard(c *gin.Context) { ctx := c.Request.Context() // Provider health summaries var providerHealth []models.ProviderHealthSummary windows, err := h.healthStore.ListAllCurrent(ctx) if err == nil { for _, w := range windows { providerHealth = append(providerHealth, models.ProviderHealthSummary{ ProviderConfigID: w.ProviderConfigID, Status: health.DeriveStatus(w.ErrorRate(), w.RequestCount), RequestCount: w.RequestCount, ErrorRate: w.ErrorRate(), ErrorCount: w.ErrorCount, RateLimitCount: w.RateLimitCount, TimeoutCount: w.TimeoutCount, AvgLatencyMs: w.AvgLatencyMs(), MaxLatencyMs: w.MaxLatencyMs, LastError: w.LastError, LastErrorAt: w.LastErrorAt, }) } } // 24h usage totals since := time.Now().Add(-24 * time.Hour) totals, _ := h.stores.Usage.GetTotals(ctx, store.UsageQueryOptions{ Since: &since, }) // DB pool stats var dbPool *sql.DBStats if database.DB != nil { stats := database.DB.Stats() dbPool = &stats } // WebSocket connections wsCount := 0 if h.hub != nil { wsCount = h.hub.ConnCount() } // Recent errors from audit log (last 10 error-related entries) var recentErrors []models.AuditEntry entries, _, auditErr := h.stores.Audit.List(ctx, store.AuditListOptions{ ListOptions: store.ListOptions{Limit: 10}, ResourceType: "error", }) if auditErr == nil { recentErrors = entries } // Go runtime stats var mem runtime.MemStats runtime.ReadMemStats(&mem) rt := runtimeStats{ Goroutines: runtime.NumGoroutine(), HeapMB: int(mem.HeapAlloc / 1024 / 1024), SysMB: int(mem.Sys / 1024 / 1024), NumGC: mem.NumGC, GoVersion: runtime.Version(), } // Uptime uptime := time.Since(processStartTime).Truncate(time.Second).String() SafeJSON(c, http.StatusOK, gin.H{ "provider_health": providerHealth, "usage_24h": totals, "db_pool": dbPool, "ws_connections": wsCount, "recent_errors": recentErrors, "runtime": rt, "uptime": uptime, }) }