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>
60 lines
2.3 KiB
TypeScript
60 lines
2.3 KiB
TypeScript
'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>
|
|
);
|
|
}
|