Add NUC Portal - infrastructure dashboard
Next.js 16 dashboard for managing NUC services via Coolify API. Features service cards with health indicators, deployment dashboard with live log streaming, S3-backed preview images, SSE real-time updates, and dark mode support. 18 services across 7 categories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
59
nuc-portal/src/components/VitalsBar.tsx
Normal file
59
nuc-portal/src/components/VitalsBar.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
'use client';
|
||||
|
||||
import { usePortal } from '@/lib/PortalContext';
|
||||
import { getVitalsBg, getVitalsTrack } from '@/lib/stats';
|
||||
|
||||
function MiniBar({ label, percent, detail }: { label: string; percent: number; detail?: string }) {
|
||||
const bg = getVitalsBg(percent);
|
||||
const track = getVitalsTrack(percent);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-stone-500 w-8 text-right">{label}</span>
|
||||
<div className={`w-16 h-1.5 rounded-full ${track}`}>
|
||||
<div
|
||||
className={`h-full rounded-full ${bg} transition-all duration-500`}
|
||||
style={{ width: `${Math.min(percent, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[11px] tabular-nums text-slate-600 dark:text-stone-400 w-8">{Math.round(percent)}%</span>
|
||||
{detail && (
|
||||
<span className="text-[10px] text-slate-400 dark:text-stone-600 hidden lg:inline">{detail}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function VitalsBar() {
|
||||
const { systemStats, statsLoading, statsError } = usePortal();
|
||||
|
||||
if (statsError && !systemStats) return null;
|
||||
|
||||
if (statsLoading && !systemStats) {
|
||||
return (
|
||||
<div className="hidden sm:flex items-center gap-4 px-4 sm:px-6 py-1 border-t border-slate-100 dark:border-stone-800/50">
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} className="flex items-center gap-1.5">
|
||||
<div className="w-8 h-2 rounded bg-slate-200 dark:bg-stone-800 animate-pulse" />
|
||||
<div className="w-16 h-1.5 rounded-full bg-slate-200 dark:bg-stone-800 animate-pulse" />
|
||||
<div className="w-8 h-2 rounded bg-slate-200 dark:bg-stone-800 animate-pulse" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!systemStats) return null;
|
||||
|
||||
const ramDetail = `${(systemStats.ram_used_mb / 1024).toFixed(1)}/${(systemStats.ram_total_mb / 1024).toFixed(1)}G`;
|
||||
const showSwap = systemStats.swap_percent > 50;
|
||||
|
||||
return (
|
||||
<div className="hidden sm:flex items-center gap-4 px-4 sm:px-6 py-1 border-t border-slate-100 dark:border-stone-800/50">
|
||||
<MiniBar label="CPU" percent={systemStats.cpu_percent} />
|
||||
<MiniBar label="RAM" percent={systemStats.ram_percent} detail={ramDetail} />
|
||||
<MiniBar label="Disk" percent={systemStats.disk_percent} />
|
||||
{showSwap && <MiniBar label="Swap" percent={systemStats.swap_percent} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user