138 lines
5.9 KiB
TypeScript
138 lines
5.9 KiB
TypeScript
|
|
import type { GpuStatus } from '../types'
|
|||
|
|
|
|||
|
|
type Props = {
|
|||
|
|
gpu: GpuStatus | null
|
|||
|
|
compact?: boolean
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function memPct(used: number, total: number) {
|
|||
|
|
if (!total) return 0
|
|||
|
|
return Math.round((used / total) * 100)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GpuPanel({ gpu, compact }: Props) {
|
|||
|
|
if (!gpu) {
|
|||
|
|
return (
|
|||
|
|
<div className="panel rounded-2xl p-5 animate-pulse">
|
|||
|
|
<div className="h-4 w-32 rounded bg-[var(--surface-elevated)] mb-4" />
|
|||
|
|
<div className="h-24 rounded-xl bg-[var(--surface-elevated)]" />
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const online = gpu.ok
|
|||
|
|
const gpus = gpu.gpus || []
|
|||
|
|
const avgUtil = gpus.length ? gpus.reduce((s, g) => s + g.util_gpu, 0) / gpus.length : 0
|
|||
|
|
const avgVram = gpus.length ? gpus.reduce((s, g) => s + memPct(g.memory_used_mib, g.memory_total_mib), 0) / gpus.length : 0
|
|||
|
|
|
|||
|
|
if (compact) {
|
|||
|
|
return (
|
|||
|
|
<div className="panel rounded-2xl p-4 relative overflow-hidden">
|
|||
|
|
<div className="absolute -top-8 -right-8 w-24 h-24 rounded-full bg-[var(--accent-gpu)] opacity-[0.1] blur-2xl pointer-events-none" />
|
|||
|
|
<div className="flex items-center justify-between gap-2 mb-3 relative">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<span>⚡</span>
|
|||
|
|
<h2 className="font-bold text-sm text-[var(--accent-gpu)]">GPU Lab</h2>
|
|||
|
|
</div>
|
|||
|
|
<a href={gpu.ui_url} target="_blank" rel="noopener noreferrer" className="text-[10px] font-mono text-[var(--accent-gpu)] hover:underline">
|
|||
|
|
Open ↗
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
{gpu.active_model && (
|
|||
|
|
<div className="text-sm font-semibold text-[var(--text)] truncate">{gpu.active_model}</div>
|
|||
|
|
)}
|
|||
|
|
<div className="flex gap-3 mt-2 text-[10px] font-mono text-[var(--text-muted)]">
|
|||
|
|
<span>{gpu.gpu_count ?? gpus.length}× V100</span>
|
|||
|
|
<span>{Math.round(avgUtil)}% util</span>
|
|||
|
|
<span>{Math.round(avgVram)}% VRAM</span>
|
|||
|
|
<span className={online && gpu.inference_active ? 'text-[var(--status-ok)]' : 'text-[var(--status-warn)]'}>
|
|||
|
|
{online ? (gpu.inference_active ? 'ON' : 'STBY') : 'OFF'}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="h-1.5 rounded-full bg-[var(--surface-muted)] mt-3 overflow-hidden">
|
|||
|
|
<div
|
|||
|
|
className="h-full rounded-full transition-all duration-500"
|
|||
|
|
style={{
|
|||
|
|
width: `${Math.max(avgUtil, avgVram * 0.5)}%`,
|
|||
|
|
background: 'linear-gradient(90deg, var(--accent-gpu), var(--accent))',
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="panel rounded-2xl p-5 relative overflow-hidden">
|
|||
|
|
<div className="absolute -top-12 -right-12 w-40 h-40 rounded-full bg-[var(--accent-gpu)] opacity-[0.08] blur-2xl pointer-events-none" />
|
|||
|
|
|
|||
|
|
<div className="flex flex-wrap items-start justify-between gap-3 mb-4 relative">
|
|||
|
|
<div>
|
|||
|
|
<p className="section-eyebrow">Inference cluster</p>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<span className="text-lg" aria-hidden>⚡</span>
|
|||
|
|
<h2 className="font-display font-bold text-[var(--accent-gpu)] tracking-tight">GPU Lab</h2>
|
|||
|
|
<span
|
|||
|
|
className={`text-[10px] font-mono px-2 py-0.5 rounded-full border ${
|
|||
|
|
online && gpu.inference_active
|
|||
|
|
? 'border-[var(--status-ok)] text-[var(--status-ok)] bg-[var(--status-ok-bg)]'
|
|||
|
|
: 'border-[var(--status-warn)] text-[var(--status-warn)] bg-[var(--status-warn-bg)]'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{online ? (gpu.inference_active ? 'INFERENCE ON' : 'STANDBY') : 'OFFLINE'}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<p className="text-xs font-mono text-[var(--text-muted)] mt-1">
|
|||
|
|
atc-gpu-dev · {gpu.host} · {gpu.gpu_count ?? gpus.length}× V100
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<a href={gpu.ui_url} target="_blank" rel="noopener noreferrer" className="btn-secondary text-xs shrink-0">
|
|||
|
|
Open GPU Manager ↗
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{gpu.active_model && (
|
|||
|
|
<div className="rounded-xl border border-[var(--border)] bg-[var(--surface-elevated)] px-4 py-3 mb-4">
|
|||
|
|
<div className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-faint)]">Active model</div>
|
|||
|
|
<div className="font-semibold text-[var(--text)] mt-0.5">{gpu.active_model}</div>
|
|||
|
|
{gpu.vllm_url && (
|
|||
|
|
<div className="text-[11px] font-mono text-[var(--text-muted)] mt-1 truncate">{gpu.vllm_url}</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{!online && (
|
|||
|
|
<p className="text-sm text-[var(--status-down)]">{gpu.error || 'GPU manager unreachable'}</p>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div className="grid gap-2 sm:grid-cols-2">
|
|||
|
|
{gpus.map((g) => {
|
|||
|
|
const pct = memPct(g.memory_used_mib, g.memory_total_mib)
|
|||
|
|
return (
|
|||
|
|
<div key={g.index} className="gpu-card rounded-xl p-3 border border-[var(--border)]">
|
|||
|
|
<div className="flex justify-between items-center mb-2">
|
|||
|
|
<span className="text-xs font-mono font-semibold text-[var(--accent-gpu)]">GPU {g.index}</span>
|
|||
|
|
<span className="text-[10px] font-mono text-[var(--text-faint)]">{g.temperature_c}°C · {g.power_w}W</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-[11px] text-[var(--text-muted)] truncate mb-2">{g.name}</div>
|
|||
|
|
<div className="flex gap-3 text-[10px] font-mono mb-1.5">
|
|||
|
|
<span>Util {Math.round(g.util_gpu)}%</span>
|
|||
|
|
<span>VRAM {pct}%</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="h-1.5 rounded-full bg-[var(--surface-muted)] overflow-hidden">
|
|||
|
|
<div
|
|||
|
|
className="h-full rounded-full transition-all duration-500"
|
|||
|
|
style={{
|
|||
|
|
width: `${Math.max(g.util_gpu, pct * 0.3)}%`,
|
|||
|
|
background: 'linear-gradient(90deg, var(--accent-gpu), var(--accent))',
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|