Files
scarf/design/static-site/ui-kit/Health.jsx
T
Alan Wizemann 8a2d89654b feat(design): adopt ScarfDesign system across Mac UI
Add a typed design-system package (Packages/ScarfDesign) with rust-tone
color tokens, type scale, spacing/radius tokens, ScarfPageHeader and
component primitives (ScarfCard, ScarfBadge, ScarfTextField,
ScarfSectionHeader, ScarfDivider, four button styles). Both Mac and iOS
targets `import ScarfDesign`.

Sidebar redesigned per design/static-site/ui-kit/Sidebar.jsx — glassy
translucent background, 224 px width, app-icon header with server pill,
custom tokenized rows with rust accent-tint when active, footer with
live Hermes-running indicator (wired to ServerLiveStatusRegistry).

14 mockup-backed feature screens redesigned: Settings, Dashboard,
Sessions, Memory, Chat (visual sweep), Activity, Cron, Insights,
MCPServers, Health, Logs, Tools (full); Projects light-touch.
Non-mockup features inherit rust through AccentColor.colorset repoint.

Mac AppIcon.appiconset replaced with the rust set. AccentColor.colorset
repointed to BrandRust hex (light + dark variants).

Visual sweep: every multi-button page-header / action-bar cluster now
wraps in .fixedSize(horizontal: true, vertical: false) so labels can't
wrap letter-by-letter at narrow widths (regression seen on the MCP
detail pane with 4 buttons).

Follow-ups landed:
- Sidebar Hermes-running probe wired to per-window
  ServerLiveStatusRegistry (no more placeholder green).
- Sessions: today filter predicate (isDateInToday(startedAt)); pill
  count reflects real count. Starred stays a no-op pending an upstream
  pinned/starred field on HermesSession.
- Dashboard: Recent activity column rendered alongside Recent sessions
  in a ViewThatFits 2-col grid. Populated from
  HermesDataService.fetchRecentToolCalls(limit:) flattened to
  ActivityEntry. ActivityEntry gains a public memberwise init.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:27:54 +02:00

112 lines
5.5 KiB
React

// Health — diagnostics report. One-shot health check across services.
const HEALTH_CHECKS = [
{ name: 'Anthropic API', status: 'ok', latency: '124 ms', detail: 'authenticated as Aurora · sonnet-4.5 reachable' },
{ name: 'Local gateway', status: 'ok', latency: '2 ms', detail: 'pid 84021 · uptime 4d 2h · listening :7421' },
{ name: 'Filesystem', status: 'ok', latency: '—', detail: '14.2 GB free of 512 GB' },
{ name: 'GitHub MCP', status: 'ok', latency: '84 ms', detail: 'oauth ok · 18 tools · rate-limit 4500/5000 (warn at 4750)' },
{ name: 'Linear MCP', status: 'ok', latency: '142 ms', detail: 'oauth ok · 9 tools' },
{ name: 'Postgres MCP', status: 'ok', latency: '12 ms', detail: 'stdio · prod read replica' },
{ name: 'Figma MCP', status: 'ok', latency: '210 ms', detail: 'oauth ok · 6 tools' },
{ name: 'Notion MCP', status: 'error', latency: '—', detail: 'TLS handshake failed · 4 retries · backing off 30s' },
{ name: 'Slack MCP', status: 'warn', latency: '—', detail: 'oauth token expired · re-authenticate' },
{ name: 'Sentry MCP', status: 'idle', latency: '—', detail: 'disabled' },
{ name: 'Cron scheduler', status: 'ok', latency: '—', detail: '5 jobs registered · next: incident-triage in 12m' },
{ name: 'Local model cache', status: 'ok', latency: '—', detail: '412 MB · last pruned 2d ago' },
];
function Health() {
const [scanning, setScanning] = React.useState(false);
React.useEffect(() => { requestAnimationFrame(() => window.lucide && window.lucide.createIcons()); });
const ok = HEALTH_CHECKS.filter(c => c.status === 'ok').length;
const warn = HEALTH_CHECKS.filter(c => c.status === 'warn').length;
const err = HEALTH_CHECKS.filter(c => c.status === 'error').length;
function rerun() {
setScanning(true);
setTimeout(() => setScanning(false), 1400);
}
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<ContentHeader title="Health"
subtitle="A diagnostics report across Scarf, the agent, and connected services"
actions={<>
<Btn icon="download">Save report</Btn>
<Btn kind="primary" icon="rotate-cw" loading={scanning} onClick={rerun}>{scanning ? 'Scanning…' : 'Re-run'}</Btn>
</>} />
<div style={{ flex: 1, overflowY: 'auto', padding: '24px 32px' }}>
{/* Summary banner */}
<div style={{
background: err > 0 ? 'var(--red-100)' : warn > 0 ? 'var(--orange-100)' : 'var(--green-100)',
border: `0.5px solid ${err > 0 ? 'var(--red-500)' : warn > 0 ? 'var(--orange-500)' : 'var(--green-500)'}`,
borderRadius: 10, padding: 16, marginBottom: 24,
display: 'flex', alignItems: 'center', gap: 14,
}}>
<div style={{
width: 38, height: 38, borderRadius: 9,
background: err > 0 ? 'var(--red-500)' : warn > 0 ? 'var(--orange-500)' : 'var(--green-500)',
color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<i data-lucide={err > 0 ? 'alert-octagon' : warn > 0 ? 'alert-triangle' : 'shield-check'} style={{ width: 20, height: 20 }}></i>
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 15, fontWeight: 600, marginBottom: 2 }}>
{err > 0 ? `${err} service${err === 1 ? '' : 's'} unhealthy`
: warn > 0 ? `${warn} warning${warn === 1 ? '' : 's'} to review`
: 'All systems healthy'}
</div>
<div style={{ fontSize: 13, color: 'var(--fg-muted)' }}>
{ok} ok · {warn} warning · {err} error · scanned 2 minutes ago
</div>
</div>
</div>
{/* Checks */}
<SettingsGroup title="Diagnostic checks">
{HEALTH_CHECKS.map((c, i) => <HealthRow key={c.name} c={c} last={i === HEALTH_CHECKS.length - 1} />)}
</SettingsGroup>
<SettingsGroup title="Environment">
<SettingsRow icon="info" title="Scarf version"
description="0.14.2 · 0.15.0 available"
control={<Btn size="sm">Update</Btn>} />
<SettingsRow icon="cpu" title="Platform"
description="macOS 14.4.1 · Apple M3 Pro · 36 GB"
control={<Pill tone="green" dot>supported</Pill>} />
<SettingsRow icon="terminal" title="Shell"
description="/bin/zsh 5.9 · path 47 entries"
control={<Btn size="sm">Inspect</Btn>} last />
</SettingsGroup>
</div>
</div>
);
}
function HealthRow({ c, last }) {
const tones = {
ok: { tone: 'green', icon: 'check-circle' },
warn: { tone: 'amber', icon: 'alert-triangle' },
error: { tone: 'red', icon: 'x-circle' },
idle: { tone: 'gray', icon: 'minus-circle' },
};
const t = tones[c.status];
return (
<div style={{
display: 'flex', alignItems: 'center', gap: 12, padding: '12px 18px',
borderBottom: last ? 'none' : '0.5px solid var(--border)',
}}>
<Pill tone={t.tone} icon={t.icon} size="sm">{c.status}</Pill>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 500 }}>{c.name}</div>
<div style={{ fontSize: 11.5, color: 'var(--fg-muted)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>{c.detail}</div>
</div>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-faint)', width: 70, textAlign: 'right' }}>{c.latency}</span>
</div>
);
}
window.Health = Health;