Repository Public
1'use client';2import type { SystemStatus } from '@/lib/dto-auth';3import { DASH, fmtRelative } from '@/lib/format';4import { cn } from '@/lib/cn';56export interface HealthItem {7 key: string;8 label: string;9 /** true = healthy, false = problem, null = the server did not say. */10 ok: boolean | null;11 detail: string;12}13/** Worker / database / provider keys, from GET /system/status. Missing fields stay unknown. */14export function healthItems(s: SystemStatus | undefined, nowMs: number): HealthItem[] {15 const w = s?.worker;16 const db = s?.db;17 const p = s?.providers;18 const key = (v: boolean | null | undefined) => (v == null ? null : v);19 return [20 {21 key: 'worker', label: 'Worker', ok: w ? w.alive : null,22 detail: !w ? DASH23 : w.alive === null ? 'unknown — no heartbeat recorded yet'24 : w.alive ? `${w.workers.length > 1 ? `${w.workers.length} workers · ` : ''}${w.lastSeenAt ? `heartbeat ${fmtRelative(w.lastSeenAt, nowMs)}` : 'alive'}`25 : w.lastSeenAt ? `last seen ${fmtRelative(w.lastSeenAt, nowMs)}` : 'no heartbeat',26 },27 { key: 'db', label: 'Database', ok: db ? db.ok : null, detail: !db ? DASH : db.ok ? (db.latencyMs != null ? `${Math.round(db.latencyMs)} ms` : 'reachable') : 'unreachable' },28 { key: 'anthropic', label: 'Anthropic key', ok: key(p?.anthropic), detail: p?.anthropic == null ? DASH : p.anthropic ? 'present' : 'missing' },29 { key: 'openai', label: 'OpenAI key', ok: key(p?.openai), detail: p?.openai == null ? DASH : p.openai ? 'present' : 'missing' },30 ];31}32export function HealthDot({ ok }: { ok: boolean | null }) {33 // unknown is a hollow ring so that it can never be mistaken for a live state34 return ok == null ? (35 <span role="img" aria-label="unknown" className="inline-block size-2 shrink-0 rounded-full border-[1.5px] border-faint" />36 ) : (37 <span role="img" aria-label={ok ? 'ok' : 'problem'} className={cn('inline-block size-2 shrink-0 rounded-full', ok ? 'bg-success-vivid' : 'bg-error-vivid')} />38 );39}40export function HealthStrip({ items, className }: { items: HealthItem[]; className?: string }) {41 return (42 <ul className={cn('grid grid-cols-2 gap-2', className)} data-testid="health-strip">43 {items.map((i) => (44 <li key={i.key} className="flex min-w-0 items-center gap-2.5 rounded-[12px] bg-surface-2 px-3 py-2" data-testid={`health-${i.key}`} data-ok={i.ok == null ? 'unknown' : String(i.ok)}>45 <HealthDot ok={i.ok} />46 <span className="min-w-0">47 <span className="block truncate text-[12.5px] font-medium text-text">{i.label}</span>48 <span className={cn('tnum block truncate text-[11px]', i.ok === false ? 'font-medium text-error' : 'text-muted')}>{i.detail}</span>49 </span>50 </li>51 ))}52 </ul>53 );54}55