Dashboard: rebuild into an operator cockpit (0.38.0)
Replace the analytics-style dashboard (stack-health funnel, uptime %, operations/day grid, AI pill) with an attention-driven fleet cockpit: - New /api/dashboard/fleet endpoint: server-side fan-out across the local host and every agent into one payload — a prioritized "needs attention" list, headline KPIs, an honest stack-status breakdown and a per-host resource rollup. Each agent uses its own DB session so the fan-out is concurrency-safe; failures degrade to "offline" instead of stalling. - New frontend: AttentionStrip, FleetKpiRow, StackStatusBar and HostResourceTable; Dashboard.tsx rewritten around them. - Remove the funnel/summary endpoints, the uptime sampler loop and the ops-activity machinery; delete the now-unused chart components. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5c46e40866
commit
c830d28b65
@@ -0,0 +1,101 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
AlertTriangle,
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
ServerOff,
|
||||
HeartPulse,
|
||||
ArrowUpCircle,
|
||||
HardDrive,
|
||||
MemoryStick,
|
||||
Archive,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
import type { AttentionItem } from "@/api/dashboard";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const KIND_ICON: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
agent_offline: ServerOff,
|
||||
unhealthy: HeartPulse,
|
||||
stack_error: AlertTriangle,
|
||||
stack_partial: AlertCircle,
|
||||
updates: ArrowUpCircle,
|
||||
disk_pressure: HardDrive,
|
||||
mem_pressure: MemoryStick,
|
||||
backup_failed: Archive,
|
||||
backup_overdue: Archive,
|
||||
};
|
||||
|
||||
export function AttentionStrip({
|
||||
items,
|
||||
loading,
|
||||
}: {
|
||||
items?: AttentionItem[];
|
||||
loading: boolean;
|
||||
}) {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="sp-card p-4">
|
||||
<div className="sp-skeleton h-5 w-40" />
|
||||
<div className="sp-skeleton mt-3 h-10" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center gap-3 rounded-card border border-sp-green/40 bg-sp-green/10 px-4 py-3 text-sp-green">
|
||||
<CheckCircle2 className="h-5 w-5 shrink-0" />
|
||||
<p className="text-sm font-medium">All systems healthy — nothing needs your attention.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const errors = items.filter((i) => i.severity === "error").length;
|
||||
|
||||
return (
|
||||
<div className="sp-card overflow-hidden p-0">
|
||||
<div className="flex items-center justify-between border-b border-sp-border px-4 py-2.5">
|
||||
<h2 className="sp-label">Needs attention</h2>
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-pill px-2 py-0.5 text-xs font-semibold",
|
||||
errors > 0
|
||||
? "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300"
|
||||
: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300"
|
||||
)}
|
||||
>
|
||||
{items.length}
|
||||
</span>
|
||||
</div>
|
||||
<ul className="divide-y divide-sp-border">
|
||||
{items.map((item, i) => {
|
||||
const Icon = KIND_ICON[item.kind] ?? AlertCircle;
|
||||
const isError = item.severity === "error";
|
||||
return (
|
||||
<li key={`${item.kind}-${item.host}-${i}`}>
|
||||
<Link
|
||||
to={item.link}
|
||||
className="flex items-center gap-3 px-4 py-2.5 transition-colors hover:bg-sp-surface-2"
|
||||
>
|
||||
<Icon
|
||||
className={cn(
|
||||
"h-4 w-4 shrink-0",
|
||||
isError ? "text-red-600 dark:text-red-400" : "text-sp-amber"
|
||||
)}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-sp-text-1">{item.title}</p>
|
||||
{item.detail && (
|
||||
<p className="truncate text-xs text-sp-text-3">{item.detail}</p>
|
||||
)}
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-sp-text-3" />
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user