Add Command Center v2: DQ/RAG integration, S3 browser, Jupyter, GPU matrix.
Mirror mo/atc-GPU layout with config/, docs/, scripts/ for Gitea deploy.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user