invocations-sparkline.tsx67 lines · main
1interface Hour {
2 hour: string;
3 count: number;
4 errCount: number;
5}
6
7/**
8 * Tiny inline SVG bar chart. No deps, no client JS — server-rendered.
9 * Renders one bar per hour; height scales to the max bar in the series.
10 * Error count overlays the bar in red.
11 */
12export function InvocationsSparkline({ hours }: { hours: Hour[] }) {
13 if (hours.length === 0) return null;
14 const total = hours.reduce((acc, h) => acc + h.count, 0);
15 if (total === 0) return null;
16 const max = Math.max(...hours.map((h) => h.count));
17 const W = 280;
18 const H = 40;
19 const barW = W / hours.length;
20 const gap = 1;
21
22 return (
23 <div className="flex flex-col gap-1">
24 <svg
25 viewBox={`0 0 ${W} ${H}`}
26 width="100%"
27 height={H}
28 preserveAspectRatio="none"
29 aria-label="invocations per hour, last 24 hours"
30 >
31 {hours.map((h, i) => {
32 const barH = max === 0 ? 0 : Math.max(1, (h.count / max) * H);
33 const errH = max === 0 ? 0 : (h.errCount / max) * H;
34 return (
35 <g key={i}>
36 <rect
37 x={i * barW + gap / 2}
38 y={H - barH}
39 width={barW - gap}
40 height={barH}
41 fill="var(--color-primary)"
42 opacity={0.6}
43 >
44 <title>{`${new Date(h.hour).toISOString().slice(11, 16)} · ${h.count} invocations${h.errCount > 0 ? ` · ${h.errCount} err` : ''}`}</title>
45 </rect>
46 {errH > 0 ? (
47 <rect
48 x={i * barW + gap / 2}
49 y={H - errH}
50 width={barW - gap}
51 height={errH}
52 fill="#f87171"
53 opacity={0.9}
54 />
55 ) : null}
56 </g>
57 );
58 })}
59 </svg>
60 <div className="flex justify-between font-mono text-[9px] text-[var(--color-text-subtle)]">
61 <span>−24h</span>
62 <span>{total.toLocaleString()} invocations</span>
63 <span>now</span>
64 </div>
65 </div>
66 );
67}