toes/src/client/components/DashboardLanding.tsx
Claude 50e5c97beb
Add system vitals gauges and unified log stream to dashboard
- Add /api/system endpoints for CPU, RAM, and disk metrics (SSE stream)
- Add /api/system/logs for unified log stream from all apps (SSE stream)
- Create Vitals component with three gauges: arc (CPU), bar (RAM), circular (Disk)
- Create UnifiedLogs component with real-time scrolling logs and status highlighting
- Update DashboardLanding with stats, vitals, and activity sections

Design follows Dieter Rams / Teenage Engineering aesthetic with neutral palette.

https://claude.ai/code/session_013L9HKHxMEoub76B1zuKive
2026-02-13 16:41:21 +00:00

92 lines
2.5 KiB
TypeScript

import { useEffect, useState } from 'hono/jsx'
import { apps } from '../state'
import {
DashboardContainer,
DashboardHeader,
DashboardSubtitle,
DashboardTitle,
StatCard,
StatLabel,
StatValue,
StatsGrid,
} from '../styles'
import { UnifiedLogs, type UnifiedLogLine } from './UnifiedLogs'
import { Vitals } from './Vitals'
interface SystemMetrics {
cpu: number
ram: { used: number, total: number, percent: number }
disk: { used: number, total: number, percent: number }
}
export function DashboardLanding() {
const [metrics, setMetrics] = useState<SystemMetrics>({
cpu: 0,
ram: { used: 0, total: 0, percent: 0 },
disk: { used: 0, total: 0, percent: 0 },
})
const [logs, setLogs] = useState<UnifiedLogLine[]>([])
const regularApps = apps.filter(app => !app.tool)
const toolApps = apps.filter(app => app.tool)
const runningApps = apps.filter(app => app.state === 'running')
// Subscribe to system metrics SSE
useEffect(() => {
const metricsSource = new EventSource('/api/system/metrics/stream')
metricsSource.onmessage = e => {
try {
setMetrics(JSON.parse(e.data))
} catch {}
}
return () => metricsSource.close()
}, [])
// Subscribe to unified logs SSE
useEffect(() => {
const logsSource = new EventSource('/api/system/logs/stream')
logsSource.onmessage = e => {
try {
const line = JSON.parse(e.data) as UnifiedLogLine
setLogs((prev: UnifiedLogLine[]) => [...prev.slice(-199), line])
} catch {}
}
return () => logsSource.close()
}, [])
const handleClearLogs = async () => {
try {
await fetch('/api/system/logs/clear', { method: 'POST' })
setLogs([])
} catch {}
}
return (
<DashboardContainer>
<DashboardHeader>
<DashboardTitle>Toes</DashboardTitle>
<DashboardSubtitle>Your personal web appliance</DashboardSubtitle>
</DashboardHeader>
<StatsGrid>
<StatCard>
<StatValue>{regularApps.length}</StatValue>
<StatLabel>Apps</StatLabel>
</StatCard>
<StatCard>
<StatValue>{toolApps.length}</StatValue>
<StatLabel>Tools</StatLabel>
</StatCard>
<StatCard>
<StatValue>{runningApps.length}</StatValue>
<StatLabel>Running</StatLabel>
</StatCard>
</StatsGrid>
<Vitals cpu={metrics.cpu} ram={metrics.ram} disk={metrics.disk} />
<UnifiedLogs logs={logs} onClear={handleClearLogs} />
</DashboardContainer>
)
}