Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
119 lines
3.0 KiB
Go
119 lines
3.0 KiB
Go
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,
|
|
})
|
|
}
|