Files
riotpiao.com/app/cluster/page.tsx
T

148 lines
6.3 KiB
TypeScript
Raw Normal View History

2026-08-18 18:33:49 -07:00
'use client'
import { useState } from 'react'
import { Panel } from '@/components/cluster/Panel'
import { SlotMeter } from '@/components/cluster/SlotMeter'
import { models, namespaces, nodes, type Node } from '@/lib/clusterMock'
function Cells({ filled, total, tone = 'flux' }: { filled: number; total: number; tone?: 'flux' | 'rose' }) {
return (
<div className="flex gap-[2px]" aria-label={`${filled} of ${total} running`}>
{Array.from({ length: total }, (_, i) => (
<span
key={i}
className={`h-2.5 w-[5px] rounded-[1px] ${i < filled ? (tone === 'rose' ? 'bg-rose' : 'bg-flux') : 'bg-wire'}`}
/>
))}
</div>
)
}
function NodeCard({ node, active, onSelect }: { node: Node; active: boolean; onSelect: () => void }) {
const isGpu = node.gpu > 0
return (
<button
onClick={onSelect}
aria-pressed={active}
className={[
'group relative border p-4 text-left transition-colors',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-lamp',
active ? 'border-lamp bg-riser' : 'border-wire bg-deck hover:border-wire-lit',
].join(' ')}
>
{isGpu && <span className="absolute right-0 top-0 h-6 w-px bg-lamp" />}
<div className="flex items-baseline justify-between">
<h3 className="font-signage text-xl font-semibold tracking-signage">{node.name}</h3>
<span className={`h-1.5 w-1.5 rounded-full ${node.health === 'healthy' ? 'bg-flux' : 'bg-rose'}`} />
</div>
<p className={`mt-1 font-data text-[10px] uppercase tracking-[0.14em] ${isGpu ? 'text-lamp' : 'text-dim'}`}>
{node.role}
</p>
<dl className="mt-4 space-y-1.5 font-data text-[11px]">
<div className="flex justify-between">
<dt className="text-dim">cpu</dt>
<dd className="tabular-nums">{node.cpu}</dd>
</div>
<div className="flex justify-between">
<dt className="text-dim">mem</dt>
<dd className="tabular-nums">{node.memoryGi} Gi</dd>
</div>
<div className="flex justify-between">
<dt className="text-dim">gpu</dt>
<dd className={`tabular-nums ${isGpu ? 'text-lamp' : 'text-dim'}`}>{node.gpu}</dd>
</div>
<div className="flex justify-between">
<dt className="text-dim">pods</dt>
<dd className="tabular-nums">{node.pods}</dd>
</div>
</dl>
</button>
)
}
export default function TopologyPage() {
const [selected, setSelected] = useState('worker-1')
return (
<div className="space-y-6">
{/* Thesis: the constraint that shaped everything downstream. */}
<header className="border border-wire bg-deck p-6 md:p-8">
<p className="font-data text-[10px] uppercase tracking-[0.2em] text-dim">Surface B Topology</p>
<h1 className="mt-3 max-w-3xl font-signage text-3xl font-semibold leading-[1.1] tracking-signage md:text-5xl">
Four nodes. One GPU. <span className="text-lamp">Eight sequence slots</span> for everything that thinks.
</h1>
<div className="mt-6 flex flex-wrap items-center gap-4">
<SlotMeter used={3} size="lg" showCap />
</div>
<p className="mt-6 max-w-2xl font-plex text-sm leading-relaxed text-dim">
Every workload below runs on hardware sitting in one room. The scarcest thing in it is inference
capacity, so that is the number this page keeps in front of you.
</p>
</header>
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
{nodes.map((n) => (
<NodeCard key={n.name} node={n} active={selected === n.name} onSelect={() => setSelected(n.name)} />
))}
</div>
<div className="grid gap-6 lg:grid-cols-[1.4fr_1fr]">
<Panel label="Namespaces" hint="pods running / scheduled">
<ul className="space-y-1.5">
{namespaces.map((ns) => {
const short = ns.running < ns.total
return (
<li key={ns.name} className="flex items-center gap-4 py-0.5">
<span className="w-40 shrink-0 truncate font-data text-[11px] text-chalk">{ns.name}</span>
<Cells filled={ns.running} total={ns.total} tone={ns.running === 0 ? 'rose' : 'flux'} />
<span
className={`ml-auto shrink-0 font-data text-[11px] tabular-nums ${short ? 'text-lamp' : 'text-dim'}`}
>
{ns.running}/{ns.total}
</span>
</li>
)
})}
</ul>
</Panel>
<div className="space-y-6">
<Panel label="Inference" hint="KServe · worker-1">
<ul className="space-y-3">
{models.map((m) => (
<li key={m.name} className="border-b border-wire pb-3 last:border-0 last:pb-0">
<div className="flex items-baseline justify-between">
<span className="font-signage text-base font-semibold tracking-signage">{m.name}</span>
<span className={`font-data text-[10px] uppercase tracking-[0.14em] ${m.ready ? 'text-flux' : 'text-rose'}`}>
{m.ready ? 'ready' : 'down'}
</span>
</div>
<p className="mt-1 font-data text-[10px] text-dim">
{m.replicas}× replica · {m.seqPerReplica} seq · {m.contextTokens.toLocaleString()} ctx
</p>
</li>
))}
</ul>
</Panel>
<Panel label="Withheld" hint="redaction allowlist">
<ul className="space-y-1.5 font-data text-[10px] text-dim">
{['node and pod addresses', 'container arguments', 'image tags and digests', 'source repository URLs', 'Secret names', 'condition messages'].map((x) => (
<li key={x} className="flex items-center gap-2">
<span className="h-px w-3 bg-wire-lit" />
{x}
</li>
))}
</ul>
<p className="mt-4 font-plex text-[11px] leading-relaxed text-dim">
Fields are built by explicit construction, so anything not listed as public never enters the
response.
</p>
</Panel>
</div>
</div>
</div>
)
}