Host map One cell per machine, sized so a fleet fits on a screen. It answers how many and where, and it stops working past the point where a cell is smaller than a comfortable target. Tokens this needs: --card, --muted-foreground, --border, --scale-seq-1, --scale-seq-2, --scale-seq-3, --scale-seq-4, --scale-seq-5 The status, chart, scale, state and direction names are an extension, not a rename. shadcn has --destructive and five --chart-* and nothing else in this territory. ──────────────────────────────────────────────────────────────────────── // HostMap.tsx ──────────────────────────────────────────────────────────────────────── /** * One cell per machine, coloured by one metric, arranged by one grouping. * The grouping is what makes it diagnostic: hosts are placed by zone first, * so "one machine is hot" and "one whole zone is hot" look different, and * they are different incidents. */ export type Host = { id: string; zone: string; /** 0–1. null means no sample arrived, which is drawn as its own thing * rather than as idle. */ value: number | null; }; /** Five steps of the sequential ramp. Full class names, because Tailwind * only emits a class it can see written out. */ const RAMP = ["fill-scale-seq-1", "fill-scale-seq-2", "fill-scale-seq-3", "fill-scale-seq-4", "fill-scale-seq-5"]; const SWATCH = ["bg-scale-seq-1", "bg-scale-seq-2", "bg-scale-seq-3", "bg-scale-seq-4", "bg-scale-seq-5"]; const step = (v: number) => RAMP[Math.min(4, Math.floor(v * RAMP.length))]; /** Pointy-top hexagon. Every neighbour shares an edge, so a cluster of hot * cells reads as a blob rather than a diagonal the eye has to assemble. */ const hex = (cx: number, cy: number, r: number) => { const w = Math.sqrt(3) * r; return [[cx, cy - r], [cx + w / 2, cy - r / 2], [cx + w / 2, cy + r / 2], [cx, cy + r], [cx - w / 2, cy + r / 2], [cx - w / 2, cy - r / 2]] .map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(" "); }; export function HostMap({ hosts, metric, onSelectHost, columns = 7, r = 13 }: { hosts: Host[]; /** What the colour means, for the legend and the cell tooltip. */ metric: string; /** A cell goes somewhere scoped to that host. */ onSelectHost: (host: Host) => void; columns?: number; r?: number; }) { const w = Math.sqrt(3) * r; const zones = [...new Set(hosts.map((h) => h.zone))]; const rows = Math.ceil(hosts.length / zones.length / columns); const width = w * (columns + 0.5) + 4; const height = r * (1.5 * rows + 0.5) + 4; return (
{zones.map((zone) => { const members = hosts.filter((h) => h.zone === zone); const sampled = members.filter((h): h is Host & { value: number } => h.value !== null); const mean = sampled.reduce((a, h) => a + h.value, 0) / (sampled.length || 1); return (

{zone} {Math.round(mean * 100)}% mean

{members.map((h, i) => { const row = Math.floor(i / columns), col = i % columns; const cx = 2 + w / 2 + col * w + (row % 2 ? w / 2 : 0); const cy = 2 + r + row * 1.5 * r; return ( onSelectHost(h)} onKeyDown={(e) => e.key === "Enter" && onSelectHost(h)} > {`${h.id}: ${h.value === null ? "no data" : `${Math.round(h.value * 100)}% ${metric}`}`} ); })}
); })}
Idle {SWATCH.map((c) => )} Saturated no data
); } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { useState } from "react"; import { HostMap, type Host } from "./HostMap"; /** * A hundred and five hosts across three zones, CPU as a fraction. Two zones * sit between a fifth and three fifths; the third is hot end to end, which is * the incident the grouping exists to show. Clicking a cell is where a real * app would open the host, scoped. */ const ZONES: [string, number[]][] = [ ["eu-west-1a", [0.37, 0.54, 0.33, 0.50, 0.28, 0.45, 0.23, 0.57, 0.35, 0.53, 0.31, 0.48, 0.27, 0.44, 0.22, 0.56, 0.34, 0.51, 0.29, 0.47, 0.25, 0.42, 0.37, 0.54, 0.33, 0.50, 0.28, 0.45, 0.23, 0.57, 0.35, 0.53, 0.31, 0.48, 0.27]], ["eu-west-1b", [0.37, 0.54, 0.33, 0.50, 0.28, 0.45, 0.23, 0.57, 0.35, 0.53, 0.31, 0.48, 0.27, 0.44, 0.22, 0.56, 0.34, 0.51, 0.29, 0.47, 0.25, 0.42, 0.37, 0.54, 0.33, 0.50, 0.28, 0.45, 0.23, 0.57, 0.35, 0.53, 0.31, 0.48, 0.27]], ["eu-west-1c", [0.76, 0.65, 0.71, 0.77, 0.67, 0.73, 0.62, 0.68, 0.74, 0.64, 0.70, 0.76, 0.65, 0.71, 0.77, 0.67, 0.73, 0.62, 0.68, 0.74, 0.64, 0.70, 0.76, 0.65, 0.71, 0.77, 0.67, 0.73, 0.62, 0.68, 0.74, 0.64, 0.70, 0.76, 0.65]], ]; const HOSTS: Host[] = ZONES.flatMap(([zone, values]) => values.map((value, i) => ({ id: `${zone}-${String(i + 1).padStart(2, "0")}`, zone, value })), ); export default function Demo() { const [picked, setPicked] = useState(null); return (
{picked && (

open {picked.id} at {Math.round((picked.value ?? 0) * 100)}% CPU

)}
); }