plan/10: Add /api/stats endpoint and dashboard stats badge (#2)
Adds GET /api/stats endpoint with agent/plan/task/queue counts, stats widget in dashboard header, and Go test coverage.
This commit was merged in pull request #2.
This commit is contained in:
18
ui/src/components/DashboardHeader.tsx
Normal file
18
ui/src/components/DashboardHeader.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
63
ui/src/components/StatsWidget.tsx
Normal file
63
ui/src/components/StatsWidget.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user