spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import Link from "next/link";2import { Sparkline } from "@/components/ui/sparkline";3import { cx, formatDuration } from "@/lib/format";4import type { DataHealth } from "@/lib/types";56/** Compact source-health panel for the homepage (server component; data from /v1/data-health). */7export function HealthPanel({ health, className }: { health: DataHealth; className?: string }) {8 const c = health.connectors;9 const ratio = health.healthy_ratio == null ? null : Math.round(health.healthy_ratio * 100);10 const segs: Array<[number, string, string]> = [11 [c.healthy, "bg-positive", "healthy"],12 [c.degraded, "bg-warning", "degraded"],13 [c.recovering, "bg-accent", "recovering"],14 [c.failed, "bg-negative", "failed"],15 [c.paused, "bg-stale", "paused"],16 ];17 return (18 <div className={cx("rounded-md border border-rule bg-surface p-4", className)}>19 <div className="flex items-baseline justify-between">20 <h3 className="text-[11px] font-medium uppercase tracking-wide text-ink-3">Source health</h3>21 <Link href="/data-health" className="text-xs text-accent hover:underline">22 Data health →23 </Link>24 </div>25 <div className="mono mt-1 text-2xl font-semibold tnum">26 {ratio == null ? "—" : `${ratio}%`} <span className="text-sm font-normal text-ink-3">connectors healthy</span>27 </div>28 <div className="mt-2 flex h-2 overflow-hidden rounded-full bg-surface-3">29 {segs.map(([n, cls, label]) => (n > 0 ? <span key={label} className={cls} style={{ width: `${(n / Math.max(1, c.total)) * 100}%` }} title={`${n} ${label}`} /> : null))}30 </div>31 <div className="mt-2 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-ink-3">32 {segs.map(([n, cls, label]) => (33 <span key={label} className="inline-flex items-center gap-1">34 <span className={cx("inline-block h-1.5 w-1.5 rounded-full", cls)} />35 {n} {label}36 </span>37 ))}38 </div>39 <dl className="mt-3 grid grid-cols-2 gap-x-4 text-sm">40 <Row k="Median freshness" v={formatDuration(health.median_freshness_ms)} />41 <Row k="Multi-source quotes" v={`${health.multi_source_quotes} / ${health.quotes_public}`} />42 <Row k="Mean confidence" v={health.mean_confidence == null ? "—" : `${Math.round(health.mean_confidence * 100)}%`} />43 <Row k="Observations / s" v={health.observations_per_sec.toFixed(1)} />44 </dl>45 {health.history.length > 2 && (46 <div className="mt-3 flex items-center justify-between text-[11px] text-ink-3">47 <span>48 h · healthy ratio</span>48 <Sparkline values={health.history.map((h) => h.healthy_ratio)} width={140} height={24} stroke="var(--accent)" />49 </div>50 )}51 </div>52 );53}5455function Row({ k, v }: { k: string; v: string }) {56 return (57 <div className="flex items-baseline justify-between border-b border-rule py-1">58 <dt className="text-xs text-ink-3">{k}</dt>59 <dd className="mono text-xs tnum">{v}</dd>60 </div>61 );62}63