diff --git a/internal/server/dashboard.go b/internal/server/dashboard.go index e34c6266..59aeef54 100644 --- a/internal/server/dashboard.go +++ b/internal/server/dashboard.go @@ -370,6 +370,32 @@ func (s *Server) handleProvidersUsage(c *gin.Context) { c.JSON(http.StatusOK, SuccessResponse(rows)) } +func (s *Server) handleClientsUsage(c *gin.Context) { + var filter UsagePeriodFilter + if err := c.ShouldBindQuery(&filter); err != nil { + // ignore + } + + clause, binds := filter.ToSQL() + + var rows []struct { + ClientID string `db:"client_id" json:"client_id"` + Requests int `db:"requests" json:"requests"` + } + err := s.database.Select(&rows, fmt.Sprintf(` + SELECT COALESCE(client_id, 'unknown') as client_id, COUNT(*) as requests + FROM llm_requests + WHERE 1=1 %s + GROUP BY client_id + `, clause), binds...) + if err != nil { + c.JSON(http.StatusOK, SuccessResponse([]interface{}{})) + return + } + + c.JSON(http.StatusOK, SuccessResponse(rows)) +} + func (s *Server) handleAnalyticsBreakdown(c *gin.Context) { var filter UsagePeriodFilter if err := c.ShouldBindQuery(&filter); err != nil { diff --git a/internal/server/server.go b/internal/server/server.go index dbbbda19..fda1562e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -74,8 +74,7 @@ func NewServer(cfg *config.Config, database *db.DB) *Server { } func (s *Server) setupRoutes() { - // Global middleware should only be for logging/recovery - // Auth is specific to groups + s.router.Use(middleware.AuthMiddleware(s.database)) // Static files s.router.StaticFile("/", "./static/index.html") @@ -110,6 +109,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/clients", s.handleClientsUsage) admin.GET("/usage/detailed", s.handleDetailedUsage) admin.GET("/analytics/breakdown", s.handleAnalyticsBreakdown)