Repository Public
1'use client';2import { RefreshCw } from 'lucide-react';3import { useAudit, useModels, useSystem } from '@/lib/queries';4import { useNow } from '@/hooks/use-viewport';5import { useConn } from '@/stores/conn';6import { DASH, fmtDateTime, fmtNum, fmtTime, fmtTokens } from '@/lib/format';7import { PROVIDER_LABEL, TIER_LABEL } from '@/lib/status';8import { Badge } from '@/components/ui/badge';9import { Button } from '@/components/ui/button';10import { ErrorState, Section, Skeleton } from '@/components/ui/misc';11import { HealthDot, HealthStrip, healthItems } from '@/components/system/health';1213const COUNT_LABELS: Record<string, string> = {14 missionsQueued: 'Missions queued', missionsRunning: 'Missions running', missionsWaiting: 'Missions waiting for you', pendingWaits: 'Pending approvals and questions',15 wsClients: 'Console connections',16};17const humanize = (k: string) => COUNT_LABELS[k] ?? k.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (c) => c.toUpperCase());1819export default function SystemPage() {20 const q = useSystem();21 const models = useModels();22 const audit = useAudit(40);23 const now = useNow(5000);24 const events = useConn((s) => s.events);25 const s = q.data;26 return (27 <div className="h-full overflow-y-auto" data-testid="system-page">28 <div className="mx-auto flex w-full max-w-4xl flex-col gap-5 px-4 pb-32 pt-1 md:px-8 md:pb-12 md:pt-6">29 <div className="flex items-end gap-3">30 <div className="min-w-0 flex-1">31 <h1 className="hidden text-xl font-bold tracking-tight md:block">System</h1>32 <p className="tnum text-[13px] text-text-2">33 {s?.version ? `Agent OS ${s.version}` : 'Agent OS'} · {s?.collectedAt ? `collected at ${fmtTime(s.collectedAt)}` : q.isPending ? 'collecting…' : 'collection time unknown'}34 </p>35 </div>36 <Button variant="secondary" size="sm" onClick={() => q.refetch()} loading={q.isFetching}><RefreshCw className="size-3.5" /> Refresh</Button>37 </div>38 {q.isError ? (39 <div className="card"><ErrorState error={q.error} onRetry={() => q.refetch()} /></div>40 ) : !s ? (41 <Skeleton className="h-40 rounded-md" />42 ) : (43 <>44 <Section title="Health">45 <div className="p-3">46 <HealthStrip items={healthItems(s, now)} className="sm:grid-cols-4" />47 <p className="mt-2.5 flex items-center gap-2 px-1 text-xs text-muted" data-testid="system-events">48 <HealthDot ok={events === 'open' ? true : events === 'closed' ? false : null} />49 Live updates to this browser: {events === 'open' ? 'connected' : events === 'connecting' ? 'connecting' : 'disconnected — reconnecting'}50 </p>51 </div>52 </Section>53 <Section title="Activity">54 {s.counts && Object.keys(s.counts).length > 0 ? (55 <dl className="tnum grid grid-cols-2 gap-px bg-line sm:grid-cols-3" data-testid="system-counts">56 {Object.entries(s.counts).map(([k, v]) => (57 <div key={k} className="bg-surface px-4 py-3">58 <dd className="text-[22px] font-bold leading-tight tracking-tight">{fmtNum(v)}</dd>59 <dt className="text-xs text-muted">{humanize(k)}</dt>60 </div>61 ))}62 </dl>63 ) : (64 <p className="px-4 py-5 text-[13px] text-muted">The server did not report counters.</p>65 )}66 </Section>67 </>68 )}69 <Section title="Models" aside="from the providers' model lists, cached 10 min">70 {models.isError ? (71 <ErrorState error={models.error} onRetry={() => models.refetch()} />72 ) : models.isPending ? (73 <div className="p-4"><Skeleton className="h-24" /></div>74 ) : (75 <div className="overflow-x-auto">76 <table className="w-full min-w-[560px] text-[13px]" data-testid="system-models">77 <thead>78 <tr className="text-left text-[11px] uppercase tracking-wide text-muted">79 <th scope="col" className="px-4 py-2 font-semibold">Model</th>80 <th scope="col" className="px-3 py-2 font-semibold">Tier</th>81 <th scope="col" className="px-3 py-2 text-right font-semibold">Context</th>82 <th scope="col" className="px-3 py-2 text-right font-semibold">$ / MTok in · out</th>83 <th scope="col" className="px-4 py-2 font-semibold">Status</th>84 </tr>85 </thead>86 <tbody className="tnum divide-y divide-line border-t border-line">87 {(models.data ?? []).map((m) => (88 <tr key={`${m.provider}/${m.id}`}>89 <th scope="row" className="px-4 py-2.5 text-left font-normal">90 <span className="block font-medium text-text">{m.label}</span>91 <span className="block text-[11px] text-muted">{PROVIDER_LABEL[m.provider]} · <span className="font-mono">{m.id}</span></span>92 </th>93 <td className="px-3 py-2.5 text-text-2">{TIER_LABEL[m.tier]}</td>94 <td className="px-3 py-2.5 text-right text-text-2">{m.contextTokens == null ? DASH : fmtTokens(m.contextTokens)}</td>95 <td className="px-3 py-2.5 text-right text-text-2">{m.inputUsd == null ? DASH : m.inputUsd} · {m.outputUsd == null ? DASH : m.outputUsd}</td>96 <td className="px-4 py-2.5">{m.available ? <Badge tone="success">Available</Badge> : <Badge tone="warning">Not available</Badge>}</td>97 </tr>98 ))}99 </tbody>100 </table>101 </div>102 )}103 </Section>104 {!audit.isError && (105 <Section title="Audit log" aside="most recent first">106 {audit.isPending ? (107 <div className="p-4"><Skeleton className="h-20" /></div>108 ) : (audit.data ?? []).length === 0 ? (109 <p className="px-4 py-5 text-[13px] text-muted">No audited action yet.</p>110 ) : (111 <ul className="divide-y divide-line" data-testid="audit-list">112 {(audit.data ?? []).map((e) => (113 <li key={e.id} className="flex items-baseline gap-3 px-4 py-2 text-xs">114 <span className="tnum w-36 shrink-0 text-muted">{fmtDateTime(e.at)}</span>115 <span className={`min-w-0 flex-1 truncate font-mono ${e.ok ? 'text-text-2' : 'text-error'}`}>{e.action}{e.target ? ` ${e.target}` : ''}</span>116 <span className="shrink-0 text-muted">{e.actor}</span>117 </li>118 ))}119 </ul>120 )}121 </Section>122 )}123 </div>124 </div>125 );126}127