fix: resolve dashboard SQL scan errors and 401 noise
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

Robustified all analytics queries to handle empty datasets and NULL values. Restricted AuthMiddleware to /v1 group only.
This commit is contained in:
2026-03-19 12:39:48 -04:00
parent 263c0f0dc9
commit b7e37b0399

View File

@@ -1,6 +1,7 @@
package server package server
import ( import (
"database/sql"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@@ -314,27 +315,28 @@ func (s *Server) handleTimeSeries(c *gin.Context) {
ORDER BY bucket ORDER BY bucket
`, clause) `, clause)
var rows []struct { rows, err := s.database.Queryx(query, binds...)
Bucket string `db:"bucket"`
Requests int `db:"requests"`
Tokens int `db:"tokens"`
Cost float64 `db:"cost"`
}
err := s.database.Select(&rows, query, binds...)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(err.Error())) c.JSON(http.StatusInternalServerError, ErrorResponse(err.Error()))
return return
} }
defer rows.Close()
series := make([]gin.H, len(rows)) var series []gin.H
for i, r := range rows { for rows.Next() {
series[i] = gin.H{ var bucket string
"time": r.Bucket, var requests int
"requests": r.Requests, var tokens int
"tokens": r.Tokens, var cost float64
"cost": r.Cost, if err := rows.Scan(&bucket, &requests, &tokens, &cost); err != nil {
continue
} }
series = append(series, gin.H{
"time": bucket,
"requests": requests,
"tokens": tokens,
"cost": cost,
})
} }
granularity := "day" granularity := "day"
@@ -352,12 +354,8 @@ func (s *Server) handleProvidersUsage(c *gin.Context) {
clause, binds := filter.ToSQL() clause, binds := filter.ToSQL()
var rows []struct { rows, err := s.database.Queryx(fmt.Sprintf(`
Provider string `db:"provider"` SELECT COALESCE(provider, 'unknown') as provider, COUNT(*) as requests
Requests int `db:"requests"`
}
err := s.database.Select(&rows, fmt.Sprintf(`
SELECT provider, COUNT(*) as requests
FROM llm_requests FROM llm_requests
WHERE 1=1 %s WHERE 1=1 %s
GROUP BY provider GROUP BY provider
@@ -366,8 +364,18 @@ func (s *Server) handleProvidersUsage(c *gin.Context) {
c.JSON(http.StatusOK, SuccessResponse([]interface{}{})) c.JSON(http.StatusOK, SuccessResponse([]interface{}{}))
return return
} }
defer rows.Close()
c.JSON(http.StatusOK, SuccessResponse(rows)) var results []gin.H
for rows.Next() {
var provider string
var requests int
if err := rows.Scan(&provider, &requests); err == nil {
results = append(results, gin.H{"provider": provider, "requests": requests})
}
}
c.JSON(http.StatusOK, SuccessResponse(results))
} }
func (s *Server) handleClientsUsage(c *gin.Context) { func (s *Server) handleClientsUsage(c *gin.Context) {
@@ -378,11 +386,7 @@ func (s *Server) handleClientsUsage(c *gin.Context) {
clause, binds := filter.ToSQL() clause, binds := filter.ToSQL()
var rows []struct { rows, err := s.database.Queryx(fmt.Sprintf(`
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 SELECT COALESCE(client_id, 'unknown') as client_id, COUNT(*) as requests
FROM llm_requests FROM llm_requests
WHERE 1=1 %s WHERE 1=1 %s
@@ -392,8 +396,18 @@ func (s *Server) handleClientsUsage(c *gin.Context) {
c.JSON(http.StatusOK, SuccessResponse([]interface{}{})) c.JSON(http.StatusOK, SuccessResponse([]interface{}{}))
return return
} }
defer rows.Close()
c.JSON(http.StatusOK, SuccessResponse(rows)) var results []gin.H
for rows.Next() {
var clientID string
var requests int
if err := rows.Scan(&clientID, &requests); err == nil {
results = append(results, gin.H{"client_id": clientID, "requests": requests})
}
}
c.JSON(http.StatusOK, SuccessResponse(results))
} }
func (s *Server) handleAnalyticsBreakdown(c *gin.Context) { func (s *Server) handleAnalyticsBreakdown(c *gin.Context) {
@@ -404,28 +418,38 @@ func (s *Server) handleAnalyticsBreakdown(c *gin.Context) {
clause, binds := filter.ToSQL() clause, binds := filter.ToSQL()
// Models breakdown
var models []struct { var models []struct {
Label string `db:"label"` Label string `json:"label"`
Value int `db:"value"` Value int `json:"value"`
} }
err := s.database.Select(&models, fmt.Sprintf("SELECT COALESCE(model, 'unknown') as label, COUNT(*) as value FROM llm_requests WHERE 1=1 %s GROUP BY model ORDER BY value DESC", clause), binds...) mRows, err := s.database.Queryx(fmt.Sprintf("SELECT COALESCE(model, 'unknown') as label, COUNT(*) as value FROM llm_requests WHERE 1=1 %s GROUP BY model ORDER BY value DESC", clause), binds...)
if err != nil { if err == nil {
models = []struct { for mRows.Next() {
Label string `db:"label"` var label string
Value int `db:"value"` var value int
}{} if err := mRows.Scan(&label, &value); err == nil {
models = append(models, struct{Label string `json:"label"`; Value int `json:"value"`}{label, value})
}
}
mRows.Close()
} }
// Clients breakdown
var clients []struct { var clients []struct {
Label string `db:"label"` Label string `json:"label"`
Value int `db:"value"` Value int `json:"value"`
} }
err = s.database.Select(&clients, fmt.Sprintf("SELECT COALESCE(client_id, 'unknown') as label, COUNT(*) as value FROM llm_requests WHERE 1=1 %s GROUP BY client_id ORDER BY value DESC", clause), binds...) cRows, err := s.database.Queryx(fmt.Sprintf("SELECT COALESCE(client_id, 'unknown') as label, COUNT(*) as value FROM llm_requests WHERE 1=1 %s GROUP BY client_id ORDER BY value DESC", clause), binds...)
if err != nil { if err == nil {
clients = []struct { for cRows.Next() {
Label string `db:"label"` var label string
Value int `db:"value"` var value int
}{} if err := cRows.Scan(&label, &value); err == nil {
clients = append(clients, struct{Label string `json:"label"`; Value int `json:"value"`}{label, value})
}
}
cRows.Close()
} }
c.JSON(http.StatusOK, SuccessResponse(gin.H{ c.JSON(http.StatusOK, SuccessResponse(gin.H{
@@ -459,25 +483,34 @@ func (s *Server) handleDetailedUsage(c *gin.Context) {
ORDER BY date DESC, cost DESC ORDER BY date DESC, cost DESC
`, clause) `, clause)
var rows []struct { rows, err := s.database.Queryx(query, binds...)
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 { if err != nil {
c.JSON(http.StatusOK, SuccessResponse([]interface{}{})) c.JSON(http.StatusOK, SuccessResponse([]interface{}{}))
return return
} }
defer rows.Close()
c.JSON(http.StatusOK, SuccessResponse(rows)) var results []gin.H
for rows.Next() {
var date, client, provider, model string
var requests, tokens, cacheRead, cacheWrite int
var cost float64
if err := rows.Scan(&date, &client, &provider, &model, &requests, &tokens, &cacheRead, &cacheWrite, &cost); err == nil {
results = append(results, gin.H{
"date": date,
"client": client,
"provider": provider,
"model": model,
"requests": requests,
"tokens": tokens,
"cache_read_tokens": cacheRead,
"cache_write_tokens": cacheWrite,
"cost": cost,
})
}
}
c.JSON(http.StatusOK, SuccessResponse(results))
} }
func (s *Server) handleGetClients(c *gin.Context) { func (s *Server) handleGetClients(c *gin.Context) {
@@ -517,7 +550,10 @@ func (s *Server) handleGetClients(c *gin.Context) {
} }
var lastUsed *time.Time var lastUsed *time.Time
_ = s.database.Get(&lastUsed, "SELECT MAX(last_used_at) FROM client_tokens WHERE client_id = ?", cl.ClientID) err := s.database.Get(&lastUsed, "SELECT MAX(last_used_at) FROM client_tokens WHERE client_id = ?", cl.ClientID)
if err != nil && err != sql.ErrNoRows {
// ignore
}
uiClients[i] = UIClient{ uiClients[i] = UIClient{
ID: cl.ClientID, ID: cl.ClientID,