From 9c64a8fe42a64f61e1884b04acdcffdb17cb70fd Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Thu, 19 Mar 2026 12:24:58 -0400 Subject: [PATCH] fix: restore analytics page functionality Implemented missing /api/usage/detailed endpoint and ensured analytics breakdown and time-series return data in the expected format. --- internal/server/dashboard.go | 55 +++++++++++++++++++++++++++++++++++- internal/server/server.go | 1 + 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/internal/server/dashboard.go b/internal/server/dashboard.go index cd63b101..7081cab3 100644 --- a/internal/server/dashboard.go +++ b/internal/server/dashboard.go @@ -329,8 +329,15 @@ func (s *Server) handleTimeSeries(c *gin.Context) { } } + // Add granularity for the UI + granularity := "day" + if filter.Period == "24h" || filter.Period == "today" { + // If we had hourly bucketing we'd use 'hour' + } + c.JSON(http.StatusOK, SuccessResponse(gin.H{ - "series": series, + "series": series, + "granularity": granularity, })) } @@ -394,6 +401,52 @@ func (s *Server) handleAnalyticsBreakdown(c *gin.Context) { })) } +func (s *Server) handleDetailedUsage(c *gin.Context) { + var filter UsagePeriodFilter + if err := c.ShouldBindQuery(&filter); err != nil { + // ignore + } + + clause, binds := filter.ToSQL() + + query := fmt.Sprintf(` + SELECT + strftime('%%Y-%%m-%%d', timestamp) as date, + client_id as client, + provider, + model, + COUNT(*) as requests, + SUM(total_tokens) as tokens, + SUM(cache_read_tokens) as cache_read_tokens, + SUM(cache_write_tokens) as cache_write_tokens, + SUM(cost) as cost + FROM llm_requests + WHERE 1=1 %s + GROUP BY date, client, provider, model + ORDER BY date DESC, cost DESC + `, clause) + + var rows []struct { + Date string `db:"date" json:"date"` + Client string `db:"client" json:"client"` + Provider string `db:"provider" json:"provider"` + Model string `db:"model" json:"model"` + Requests int `db:"requests" json:"requests"` + Tokens int `db:"tokens" json:"tokens"` + CacheReadTokens int `db:"cache_read_tokens" json:"cache_read_tokens"` + CacheWriteTokens int `db:"cache_write_tokens" json:"cache_write_tokens"` + Cost float64 `db:"cost" json:"cost"` + } + + err := s.database.Select(&rows, query, binds...) + if err != nil { + c.JSON(http.StatusInternalServerError, ErrorResponse(err.Error())) + return + } + + c.JSON(http.StatusOK, SuccessResponse(rows)) +} + func (s *Server) handleGetClients(c *gin.Context) { var clients []db.Client err := s.database.Select(&clients, "SELECT * FROM clients ORDER BY created_at DESC") diff --git a/internal/server/server.go b/internal/server/server.go index 33930083..0148b5eb 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -107,6 +107,7 @@ func (s *Server) setupRoutes() { admin.GET("/usage/summary", s.handleUsageSummary) admin.GET("/usage/time-series", s.handleTimeSeries) admin.GET("/usage/providers", s.handleProvidersUsage) + admin.GET("/usage/detailed", s.handleDetailedUsage) admin.GET("/analytics/breakdown", s.handleAnalyticsBreakdown) admin.GET("/clients", s.handleGetClients)