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

148 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
)
}