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

View File

@@ -0,0 +1,18 @@
import { ClaudeModeToggle } from './ClaudeModeToggle'
import { StatsWidget } from './StatsWidget'
import { NewPlanModal } from './NewPlanModal'
export function DashboardHeader() {
return (
<header className="flex items-center justify-between border-b border-gray-200 dark:border-gray-700 px-4 py-3">
<div className="flex items-center gap-4">
<h1 className="text-xl font-serif font-semibold text-gray-900 dark:text-gray-100">Director</h1>
<NewPlanModal />
</div>
<div className="flex items-center gap-4">
<StatsWidget />
<ClaudeModeToggle />
</div>
</header>
)
}

View File

@@ -0,0 +1,63 @@
import { useEffect, useState } from 'react'
interface Stats {
status: string
agent_count: number
plan_counts: Record<string, number>
task_counts: Record<string, number>
queue_counts: Record<string, number>
}
export function StatsWidget() {
const [stats, setStats] = useState<Stats | null>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchStats = async () => {
try {
const response = await fetch('/api/stats')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
setStats(data)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch stats')
}
}
fetchStats()
const interval = setInterval(fetchStats, 30000) // Refresh every 30 seconds
return () => clearInterval(interval)
}, [])
if (error) {
return (
<div className="font-mono text-[10px] uppercase tracking-wider text-red-500">
err
</div>
)
}
if (!stats) {
return (
<div className="font-mono text-[10px] uppercase tracking-wider text-gray-400">
loading...
</div>
)
}
const activeAgents = stats.agent_count
const inProgressPlans = stats.plan_counts.in_progress || 0
const completedTasks = stats.task_counts.complete || 0
return (
<div className="flex items-center gap-1 font-mono text-[10px] uppercase tracking-wider text-gray-400">
<span className="text-gray-900 dark:text-gray-100">{activeAgents}</span>
<span>·</span>
<span className="text-gray-900 dark:text-gray-100">{inProgressPlans}</span>
<span>·</span>
<span className="text-gray-900 dark:text-gray-100">{completedTasks}</span>
</div>
)
}