plan/10: Add /api/stats endpoint and dashboard stats badge

- Add GetStats handler returning agent counts, plan/task/queue breakdowns
- Wire GET /api/stats route in api/main.go
- Add stats_test.go with handler unit tests
- Add StatsWidget.tsx component (idle/working agents, plan/task counts)
- Add DashboardHeader.tsx displaying stats badge in the dashboard header
This commit is contained in:
2026-04-08 10:04:25 -05:00
parent 3a64d8fb8e
commit ec3108125d
5 changed files with 220 additions and 0 deletions

93
api/handlers/stats.go Normal file
View File

@@ -0,0 +1,93 @@
package handlers
import (
"github.com/gin-gonic/gin"
)
type StatsResponse struct {
AgentCount int `json:"agent_count"`
WorkingAgents int `json:"working_agents"`
PlanCounts map[string]int `json:"plan_counts"`
TaskCounts map[string]int `json:"task_counts"`
QueueCounts map[string]int `json:"queue_counts"`
}
func (h *Handler) GetStats(c *gin.Context) {
// Get total agent count
agentCount, err := h.db.Query(`SELECT COUNT(*) FROM agents`)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer agentCount.Close()
var totalAgents int
if agentCount.Next() {
agentCount.Scan(&totalAgents)
}
// Get working agent count
workingAgents, err := h.db.Query(`SELECT COUNT(*) FROM agents WHERE status = 'working'`)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer workingAgents.Close()
var workingAgentCount int
if workingAgents.Next() {
workingAgents.Scan(&workingAgentCount)
}
// Get plan counts by status
planCounts := make(map[string]int)
rows, err := h.db.Query(`SELECT status, COUNT(*) FROM plans GROUP BY status`)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer rows.Close()
for rows.Next() {
var status string
var count int
rows.Scan(&status, &count)
planCounts[status] = count
}
// Get task counts by status
taskCounts := make(map[string]int)
rows, err = h.db.Query(`SELECT status, COUNT(*) FROM tasks GROUP BY status`)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer rows.Close()
for rows.Next() {
var status string
var count int
rows.Scan(&status, &count)
taskCounts[status] = count
}
// Get queue counts by status
queueCounts := make(map[string]int)
rows, err = h.db.Query(`SELECT status, COUNT(*) FROM director_queue GROUP BY status`)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer rows.Close()
for rows.Next() {
var status string
var count int
rows.Scan(&status, &count)
queueCounts[status] = count
}
// Return stats
c.JSON(200, StatsResponse{
AgentCount: totalAgents,
WorkingAgents: workingAgentCount,
PlanCounts: planCounts,
TaskCounts: taskCounts,
QueueCounts: queueCounts,
})
}