Checkpoint: Phase 14: Fixed hardcoded header metrics (UPTIME/NODES/AGENTS/CPU/MEM) — connected to real tRPC dashboard.stats endpoint with 30s polling. Fixed seed idempotency — now checks by isSystem=true instead of total count. Added dashboard.test.ts with 13 new tests. All 82 tests pass.
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ path: "/", icon: LayoutDashboard, label: "Дашборд" },
|
||||
@@ -38,6 +39,30 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
const [location] = useLocation();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
// Real-time cluster stats — refresh every 30s
|
||||
const { data: stats } = trpc.dashboard.stats.useQuery(undefined, {
|
||||
refetchInterval: 30_000,
|
||||
staleTime: 25_000,
|
||||
});
|
||||
|
||||
// Format memory: prefer GB if >= 1024 MB
|
||||
const formatMem = (mb: number) => {
|
||||
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`;
|
||||
return `${mb} MB`;
|
||||
};
|
||||
|
||||
// CPU color: green < 60%, amber 60-80%, red > 80%
|
||||
const cpuColor = (pct: number) =>
|
||||
pct > 80 ? "text-red-400" : pct > 60 ? "text-neon-amber" : "text-neon-green";
|
||||
|
||||
// MEM color: green < 50%, amber 50-80%, red > 80%
|
||||
const memColor = (mb: number) => {
|
||||
// We don't know total limit here, use absolute thresholds
|
||||
if (mb > 6000) return "text-red-400";
|
||||
if (mb > 3000) return "text-neon-amber";
|
||||
return "text-neon-green";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-background">
|
||||
{/* Sidebar */}
|
||||
@@ -124,16 +149,22 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
{/* Connection status */}
|
||||
<div className="px-3 py-3 border-t border-border/50">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-neon-green pulse-indicator" />
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
stats?.gatewayOnline ? "bg-neon-green pulse-indicator" : "bg-red-400"
|
||||
}`}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{!collapsed && (
|
||||
<motion.span
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="font-mono text-[11px] text-neon-green"
|
||||
className={`font-mono text-[11px] ${
|
||||
stats?.gatewayOnline ? "text-neon-green" : "text-red-400"
|
||||
}`}
|
||||
>
|
||||
GATEWAY ONLINE
|
||||
{stats?.gatewayOnline ? "GATEWAY ONLINE" : "GATEWAY OFFLINE"}
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@@ -146,15 +177,40 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
{/* Top status bar */}
|
||||
<header className="h-14 border-b border-border/50 bg-sidebar flex items-center justify-between px-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<StatusMetric icon={Activity} label="UPTIME" value="14d 7h 23m" color="text-neon-green" />
|
||||
<StatusMetric icon={Server} label="NODES" value="4 / 4" color="text-primary" />
|
||||
<StatusMetric icon={Bot} label="AGENTS" value="7 active" color="text-primary" />
|
||||
<StatusMetric icon={Cpu} label="CPU" value="34%" color="text-neon-green" />
|
||||
<StatusMetric icon={HardDrive} label="MEM" value="6.2 GB" color="text-neon-amber" />
|
||||
<StatusMetric
|
||||
icon={Activity}
|
||||
label="UPTIME"
|
||||
value={stats?.uptime ?? "—"}
|
||||
color="text-neon-green"
|
||||
/>
|
||||
<StatusMetric
|
||||
icon={Server}
|
||||
label="NODES"
|
||||
value={stats ? stats.nodes : "—"}
|
||||
color="text-primary"
|
||||
/>
|
||||
<StatusMetric
|
||||
icon={Bot}
|
||||
label="AGENTS"
|
||||
value={stats ? `${stats.agents} active` : "—"}
|
||||
color="text-primary"
|
||||
/>
|
||||
<StatusMetric
|
||||
icon={Cpu}
|
||||
label="CPU"
|
||||
value={stats ? `${stats.cpuPct}%` : "—"}
|
||||
color={stats ? cpuColor(stats.cpuPct) : "text-muted-foreground"}
|
||||
/>
|
||||
<StatusMetric
|
||||
icon={HardDrive}
|
||||
label="MEM"
|
||||
value={stats ? formatMem(stats.memUseMB) : "—"}
|
||||
color={stats ? memColor(stats.memUseMB) : "text-muted-foreground"}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-secondary/50 border border-border/50">
|
||||
<Wifi className="w-3.5 h-3.5 text-neon-green" />
|
||||
<Wifi className={`w-3.5 h-3.5 ${stats?.gatewayOnline ? "text-neon-green" : "text-red-400"}`} />
|
||||
<span className="font-mono text-[11px] text-muted-foreground">
|
||||
goclaw-swarm:18789
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user