import { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { AlertTriangle, Clock, RefreshCw } from "lucide-react";
import { toast } from "sonner";
import { Card } from "@/components/ui";
import { HostHeader } from "@/components/hosts/HostHeader";
import { StacksTable } from "@/components/stacks/StacksTable";
import { AttentionStrip } from "@/components/dashboard/AttentionStrip";
import { FleetKpiRow } from "@/components/dashboard/FleetKpiRow";
import { StackStatusBar } from "@/components/dashboard/StackStatusBar";
import { HostResourceTable } from "@/components/dashboard/HostResourceTable";
import { stacksApi } from "@/api/stacks";
import { systemApi } from "@/api/system";
import { agentsApi } from "@/api/agents";
import { dashboardApi } from "@/api/dashboard";
import { apiErrorMessage } from "@/api/client";
import { cn, relativeTime } from "@/lib/utils";
import { useAuthStore } from "@/store/auth";
import { useStackActions } from "@/hooks/useStackActions";
import type { Agent } from "@/types";
export function Dashboard() {
const isAdmin = useAuthStore((s) => s.user?.role === "admin");
const { isBusy, statusFor, dismissStatus, start, stop, restart } = useStackActions();
const qc = useQueryClient();
const [refreshing, setRefreshing] = useState(false);
const fleet = useQuery({
queryKey: ["dashboard-fleet"],
queryFn: () => dashboardApi.fleet(),
refetchInterval: 20000,
});
const stacks = useQuery({ queryKey: ["stacks"], queryFn: stacksApi.list, refetchInterval: 5000 });
const stats = useQuery({ queryKey: ["stack-stats"], queryFn: stacksApi.stats, refetchInterval: 5000 });
const info = useQuery({ queryKey: ["system"], queryFn: systemApi.info, refetchInterval: 30000 });
const audit = useQuery({ queryKey: ["audit"], queryFn: () => systemApi.audit(10), refetchInterval: 10000 });
const agents = useQuery({ queryKey: ["agents"], queryFn: () => agentsApi.list(), refetchInterval: 15000 });
const hasAgents = (agents.data?.length ?? 0) > 0;
const refreshFleet = async () => {
setRefreshing(true);
try {
const data = await dashboardApi.fleet(true);
qc.setQueryData(["dashboard-fleet"], data);
} catch (e) {
toast.error(apiErrorMessage(e));
} finally {
setRefreshing(false);
}
};
return (
{/* ---- Header ---- */}
Overview
{/* ---- Fleet cockpit (attention + KPIs + status + hosts) ---- */}
{fleet.isError && !fleet.data ? (
Couldn’t load fleet overview: {apiErrorMessage(fleet.error)}
) : (
<>
{/* Needs attention */}
{/* Fleet KPIs */}
{fleet.data ? (
) : (
{Array.from({ length: 6 }).map((_, i) => (
))}
)}
{/* Status + hosts */}
{fleet.data ? (
) : (
)}
{fleet.data ? (
) : (
)}
>
)}
{/* ---- Local host stacks ---- */}
{hasAgents ? : This host
}
{/* ---- Remote host stacks ---- */}
{agents.data?.map((agent) => (
))}
{/* ---- Recent activity ---- */}
Recent activity
{audit.data && audit.data.length > 0 ? (
{audit.data.map((a) => (
-
{a.user}{" "}
{a.action}{" "}
{a.target}
{relativeTime(a.timestamp)}
))}
) : (
No activity yet.
)}
);
}
/* ---------------------------------------------------------------------- */
/* Remote host stacks section */
/* ---------------------------------------------------------------------- */
function AgentDashboardSection({ agent, isAdmin }: { agent: Agent; isAdmin: boolean }) {
const qc = useQueryClient();
const online = agent.status === "online";
const [busyId, setBusyId] = useState(null);
const stacks = useQuery({
queryKey: ["agent-stacks", agent.id],
queryFn: () => agentsApi.stacks(agent.id),
enabled: online,
refetchInterval: 8000,
});
const stats = useQuery({
queryKey: ["agent-stack-stats", agent.id],
queryFn: () => agentsApi.stackStats(agent.id),
enabled: online,
refetchInterval: 5000,
});
const sys = useQuery({
queryKey: ["agent-system", agent.id],
queryFn: () => agentsApi.system(agent.id),
enabled: online,
refetchInterval: 30000,
});
const run = async (action: string, label: string, id: string) => {
setBusyId(id);
const t = toast.loading(`${label} ${id} on ${agent.name}…`);
try {
await agentsApi.action(agent.id, id, action);
toast.success(`${label} ${id} ✓`, { id: t });
qc.invalidateQueries({ queryKey: ["agent-stacks", agent.id] });
qc.invalidateQueries({ queryKey: ["agent-stack-stats", agent.id] });
} catch (e) {
toast.error(apiErrorMessage(e), { id: t });
} finally {
setBusyId(null);
}
};
return (
{!online ? (
Host is {agent.status}. Check it under Settings → Remote hosts.
) : (
busyId === id}
loading={stacks.isLoading}
linkBase={`/hosts/${agent.id}/stacks`}
onStart={(id) => run("start", "Starting", id)}
onStop={(id) => run("stop", "Stopping", id)}
onRestart={(id) => run("restart", "Restarting", id)}
emptyText="No stacks on this host."
/>
)}
);
}