fix: resolve dashboard SQL scan errors and 401 noise
Robustified all analytics queries to handle empty datasets and NULL values. Restricted AuthMiddleware to /v1 group only.
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user