diff --git a/apps/metrics/20260130-000000/index.tsx b/apps/metrics/20260130-000000/index.tsx index 4850500..dd2dde9 100644 --- a/apps/metrics/20260130-000000/index.tsx +++ b/apps/metrics/20260130-000000/index.tsx @@ -55,6 +55,12 @@ interface ProcessMetrics { rss: number } +interface SystemMetrics { + cpu: number + ram: { used: number, total: number, percent: number } + disk: { used: number, total: number, percent: number } +} + // ============================================================================ // Process Metrics Collection // ============================================================================ @@ -402,6 +408,36 @@ const ChartWrapper = define('ChartWrapper', { height: '150px', }) +const GaugeLabel = define('GaugeLabel', { + fontSize: '13px', + fontWeight: 600, + color: theme('colors-textMuted'), + textTransform: 'uppercase', + letterSpacing: '0.5px', +}) + +const GaugeValueText = define('GaugeValueText', { + textAlign: 'center', + fontSize: '20px', + fontWeight: 'bold', + marginTop: '-4px', + color: theme('colors-text'), +}) + +const GaugesCard = define('GaugesCard', { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '8px', +}) + +const GaugesGrid = define('GaugesGrid', { + display: 'flex', + justifyContent: 'center', + gap: '40px', + padding: '20px 0', +}) + const NoDataMessage = define('NoDataMessage', { display: 'flex', alignItems: 'center', @@ -435,6 +471,14 @@ function formatRss(kb?: number): string { return `${(kb / 1024 / 1024).toFixed(2)} GB` } +async function fetchSystemMetrics(): Promise { + try { + const res = await fetch(`${TOES_URL}/api/system/metrics`) + if (res.ok) return await res.json() as SystemMetrics + } catch {} + return { cpu: 0, ram: { used: 0, total: 0, percent: 0 }, disk: { used: 0, total: 0, percent: 0 } } +} + function getStatusColor(state: string): string { switch (state) { case 'running': @@ -477,6 +521,107 @@ function Layout({ title, children }: LayoutProps) { ) } +// ============================================================================ +// Gauge Rendering +// ============================================================================ + +const G_SEGMENTS = 19 +const G_START = -225 +const G_SWEEP = 270 +const G_CX = 60 +const G_CY = 60 +const G_R = 44 +const G_GAP = 3 +const G_SW = 8 +const G_NL = 38 + +const gToRad = (deg: number) => (deg * Math.PI) / 180 + +const gSegColor = (i: number): string => { + const t = i / (G_SEGMENTS - 1) + if (t < 0.4) return '#4caf50' + if (t < 0.6) return '#8bc34a' + if (t < 0.75) return '#ffc107' + if (t < 0.9) return '#ff9800' + return '#f44336' +} + +function renderGauge(value: number, id: string) { + const segSweep = G_SWEEP / G_SEGMENTS + const active = Math.round((value / 100) * G_SEGMENTS) + const innerR = G_R - G_SW / 2 + const outerR = G_R + G_SW / 2 + + const segments = [] + for (let i = 0; i < G_SEGMENTS; i++) { + const s = G_START + i * segSweep + G_GAP / 2 + const e = G_START + (i + 1) * segSweep - G_GAP / 2 + const x1 = G_CX + outerR * Math.cos(gToRad(s)) + const y1 = G_CY + outerR * Math.sin(gToRad(s)) + const x2 = G_CX + outerR * Math.cos(gToRad(e)) + const y2 = G_CY + outerR * Math.sin(gToRad(e)) + const x3 = G_CX + innerR * Math.cos(gToRad(e)) + const y3 = G_CY + innerR * Math.sin(gToRad(e)) + const x4 = G_CX + innerR * Math.cos(gToRad(s)) + const y4 = G_CY + innerR * Math.sin(gToRad(s)) + segments.push( + + ) + } + + const angle = G_START + (value / 100) * G_SWEEP + const nx = G_CX + G_NL * Math.cos(gToRad(angle)) + const ny = G_CY + G_NL * Math.sin(gToRad(angle)) + const pa = angle + 90 + const bw = 3 + const bx1 = G_CX + bw * Math.cos(gToRad(pa)) + const by1 = G_CY + bw * Math.sin(gToRad(pa)) + const bx2 = G_CX - bw * Math.cos(gToRad(pa)) + const by2 = G_CY - bw * Math.sin(gToRad(pa)) + + return ( + + {id} + + {segments} + + + + {value}% + + ) +} + +const gaugeScript = ` +(function() { + var S=19,ST=-225,SW=270,CX=60,CY=60,R=44,W=8,NL=38,GAP=3; + var iR=R-W/2, oR=R+W/2; + function rad(d){return d*Math.PI/180} + function sc(i){var t=i/(S-1);return t<.4?'#4caf50':t<.6?'#8bc34a':t<.75?'#ffc107':t<.9?'#ff9800':'#f44336'} + function upd(id,v){ + var svg=document.getElementById('gauge-'+id);if(!svg)return; + var a=Math.round((v/100)*S); + svg.querySelectorAll('[data-segment]').forEach(function(p,i){p.setAttribute('fill',i { return c.json(history) }) +app.get('/api/system', async c => { + const metrics = await fetchSystemMetrics() + return c.json(metrics) +}) + app.get('/api/history/:name', c => { const name = c.req.param('name') const history = getHistory(name) @@ -956,67 +1106,17 @@ app.get('/', async c => { ) } - // All apps view - const metrics = await getAppMetrics() - - // Sort: running first, then by name - metrics.sort((a, b) => { - if (a.state === 'running' && b.state !== 'running') return -1 - if (a.state !== 'running' && b.state === 'running') return 1 - return a.name.localeCompare(b.name) - }) - - if (metrics.length === 0) { - return c.html( - - No apps found - - ) - } - - const running = metrics.filter(s => s.state === 'running') - const totalCpu = running.reduce((sum, s) => sum + (s.cpu ?? 0), 0) - const totalRss = running.reduce((sum, s) => sum + (s.rss ?? 0), 0) - const totalData = metrics.reduce((sum, s) => sum + (s.dataSize ?? 0), 0) + // Dashboard view: system metrics gauges + const sys = await fetchSystemMetrics() return c.html( - - - - - - PID - CPU - MEM - RSS - Data - - - - {metrics.map(s => ( - - - - {s.pid ?? '-'} - {formatPercent(s.cpu)} - {formatPercent(s.memory)} - {formatRss(s.rss)} - {formatBytes(s.dataSize)} - - ))} - -
NameState
- {s.name} - {s.tool && [tool]} - - - {s.state} - -
- - {running.length} running · {formatPercent(totalCpu)} CPU · {formatRss(totalRss)} RSS · {formatBytes(totalData)} data - + + {renderGauge(sys.cpu, 'cpu')} + {renderGauge(sys.ram.percent, 'ram')} + {renderGauge(sys.disk.percent, 'disk')} + +