(chore) init commit and add tasks
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
hint?: string
|
||||
right?: ReactNode
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
/** Instrument panel chrome: hairline frame, corner ticks, eyebrow label. */
|
||||
export function Panel({ label, hint, right, children, className = '' }: Props) {
|
||||
return (
|
||||
<section className={`relative border border-wire bg-deck ${className}`}>
|
||||
<span className="pointer-events-none absolute -left-px -top-px h-2 w-2 border-l border-t border-wire-lit" />
|
||||
<span className="pointer-events-none absolute -right-px -top-px h-2 w-2 border-r border-t border-wire-lit" />
|
||||
<span className="pointer-events-none absolute -bottom-px -left-px h-2 w-2 border-b border-l border-wire-lit" />
|
||||
<span className="pointer-events-none absolute -bottom-px -right-px h-2 w-2 border-b border-r border-wire-lit" />
|
||||
|
||||
<header className="flex items-baseline justify-between gap-4 border-b border-wire px-4 py-2.5">
|
||||
<div className="flex items-baseline gap-3">
|
||||
<h2 className="font-data text-[11px] uppercase tracking-[0.18em] text-chalk">{label}</h2>
|
||||
{hint && <p className="font-data text-[10px] text-dim">{hint}</p>}
|
||||
</div>
|
||||
{right}
|
||||
</header>
|
||||
|
||||
<div className="p-4">{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
const SURFACES = [
|
||||
{ code: 'B', href: '/cluster', label: 'Topology' },
|
||||
{ code: 'E', href: '/cluster/delivery', label: 'Delivery' },
|
||||
{ code: 'C', href: '/cluster/terminal', label: 'Terminal' },
|
||||
{ code: 'D', href: '/cluster/chat', label: 'Chat' },
|
||||
]
|
||||
|
||||
export function Rail() {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<nav
|
||||
aria-label="Cluster surfaces"
|
||||
className="flex shrink-0 gap-px overflow-x-auto border-b border-wire bg-void md:w-16 md:flex-col md:overflow-visible md:border-b-0 md:border-r"
|
||||
>
|
||||
{SURFACES.map((s) => {
|
||||
const active = pathname === s.href
|
||||
return (
|
||||
<Link
|
||||
key={s.href}
|
||||
href={s.href}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
className={[
|
||||
'group flex flex-1 flex-row items-center gap-2 px-4 py-3 transition-colors md:flex-none md:flex-col md:gap-1 md:px-0 md:py-4',
|
||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-lamp',
|
||||
active ? 'bg-deck text-lamp' : 'text-dim hover:bg-riser hover:text-chalk',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="font-signage text-lg font-semibold leading-none">{s.code}</span>
|
||||
<span className="font-data text-[9px] uppercase tracking-[0.14em] md:[writing-mode:vertical-rl]">
|
||||
{s.label}
|
||||
</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { cluster, nodes } from '@/lib/clusterMock'
|
||||
import { SlotMeter } from './SlotMeter'
|
||||
|
||||
/** Fake liveness so the layout can be judged in motion. Replaced by SSE in Phase 2. */
|
||||
function useDrift() {
|
||||
const [age, setAge] = useState<number>(cluster.snapshotAge)
|
||||
const [slots, setSlots] = useState(3)
|
||||
|
||||
useEffect(() => {
|
||||
const tick = setInterval(() => setAge((a) => (a > 6 ? 0.4 : +(a + 0.9).toFixed(1))), 900)
|
||||
const churn = setInterval(() => setSlots(() => 1 + Math.floor(Math.random() * 5)), 3400)
|
||||
return () => {
|
||||
clearInterval(tick)
|
||||
clearInterval(churn)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { age, slots }
|
||||
}
|
||||
|
||||
export function Ribbon() {
|
||||
const { age, slots } = useDrift()
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 border-b border-wire bg-void px-4 py-2.5 font-data text-[11px]">
|
||||
<span className="flex items-center gap-2 text-chalk">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-flux" />
|
||||
{cluster.distro}
|
||||
</span>
|
||||
<span className="text-dim">
|
||||
k8s <span className="text-chalk">{cluster.kubernetes}</span>
|
||||
</span>
|
||||
<span className="text-dim">
|
||||
nodes <span className="text-chalk">{nodes.length}</span>
|
||||
</span>
|
||||
<span className="text-dim">
|
||||
snapshot <span className="text-chalk">{age.toFixed(1)}s</span>
|
||||
</span>
|
||||
|
||||
<span className="ml-auto flex items-center gap-3">
|
||||
<span className="uppercase tracking-[0.16em] text-dim">seq</span>
|
||||
<SlotMeter used={slots} />
|
||||
<span className="tabular-nums text-chalk">{slots}/8</span>
|
||||
</span>
|
||||
|
||||
<span className="border border-lamp/40 px-1.5 py-0.5 uppercase tracking-[0.16em] text-lamp">
|
||||
placeholder data
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client'
|
||||
|
||||
import { SEQUENCE_SLOTS, PUBLIC_SLOT_CAP } from '@/lib/clusterMock'
|
||||
|
||||
type Props = {
|
||||
used: number
|
||||
size?: 'sm' | 'lg'
|
||||
showCap?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature element. Eight discrete cells, one per vLLM sequence slot.
|
||||
* Cells past PUBLIC_SLOT_CAP are operator headroom and read as reserved.
|
||||
*/
|
||||
export function SlotMeter({ used, size = 'sm', showCap = false }: Props) {
|
||||
const cell = size === 'lg' ? 'h-6 w-3' : 'h-3 w-[7px]'
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex gap-[3px]" role="img" aria-label={`${used} of ${SEQUENCE_SLOTS} sequence slots in use`}>
|
||||
{Array.from({ length: SEQUENCE_SLOTS }, (_, i) => {
|
||||
const reserved = i >= PUBLIC_SLOT_CAP
|
||||
const active = i < used
|
||||
return (
|
||||
<span
|
||||
key={i}
|
||||
className={[
|
||||
cell,
|
||||
'rounded-[1px] transition-colors duration-500',
|
||||
active ? 'bg-lamp' : reserved ? 'bg-wire' : 'bg-wire-lit/40',
|
||||
reserved && !active ? 'opacity-50' : '',
|
||||
].join(' ')}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{showCap && (
|
||||
<span className="font-data text-[10px] uppercase tracking-widest text-dim">
|
||||
{used}/{PUBLIC_SLOT_CAP} public · {SEQUENCE_SLOTS} total
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user