SPB Git

Repository Public

health.tsx 2.7 KB 55 lines tsx

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
Files main
Menu

Theme