import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from 'recharts'; import { getAgents } from '../api/client'; import PageHeader from '../components/PageHeader'; import StatusBadge from '../components/StatusBadge'; import type { Agent } from '../api/types'; const OS_COLORS: Record = { linux: '#f97316', darwin: '#3b82f6', windows: '#8b5cf6', unknown: '#64748b', }; const STATUS_COLORS: Record = { Online: '#10b981', Offline: '#ef4444', Unknown: '#64748b', }; interface GroupedAgents { os: string; arch: string; agents: Agent[]; online: number; offline: number; } function groupAgents(agents: Agent[]): GroupedAgents[] { const groups = new Map(); for (const agent of agents) { const os = agent.os || 'unknown'; const arch = agent.architecture || 'unknown'; const key = `${os}/${arch}`; if (!groups.has(key)) { groups.set(key, { os, arch, agents: [], online: 0, offline: 0 }); } const group = groups.get(key)!; group.agents.push(agent); if (agent.status === 'Online') { group.online++; } else { group.offline++; } } return Array.from(groups.values()).sort((a, b) => b.agents.length - a.agents.length); } const CustomTooltip = ({ active, payload }: any) => { if (!active || !payload?.length) return null; return (
{payload.map((entry: any, i: number) => (

{entry.name}: {entry.value}

))}
); }; export default function AgentFleetPage() { const navigate = useNavigate(); const { data: agentsResponse, isLoading } = useQuery({ queryKey: ['agents'], queryFn: () => getAgents(), refetchInterval: 15000, }); const agents = agentsResponse?.data || []; const groups = groupAgents(agents); // Summary stats const totalAgents = agents.length; const onlineAgents = agents.filter(a => a.status === 'Online').length; const offlineAgents = totalAgents - onlineAgents; // OS distribution for pie chart const osDistribution = agents.reduce>((acc, a) => { const os = a.os || 'unknown'; acc[os] = (acc[os] || 0) + 1; return acc; }, {}); const osPieData = Object.entries(osDistribution).map(([name, value]) => ({ name, value, fill: OS_COLORS[name.toLowerCase()] || '#64748b', })); // Status for pie chart const statusPieData = [ { name: 'Online', value: onlineAgents, fill: STATUS_COLORS.Online }, { name: 'Offline', value: offlineAgents, fill: STATUS_COLORS.Offline }, ].filter(s => s.value > 0); // Version distribution const versionCounts = agents.reduce>((acc, a) => { const v = a.version || 'unknown'; acc[v] = (acc[v] || 0) + 1; return acc; }, {}); return ( <>
{/* Summary Cards */}

Total Agents

{totalAgents}

Online

{onlineAgents}

Offline

{offlineAgents}

{/* Charts */}
{/* OS Distribution */}

OS Distribution

{osPieData.length > 0 ? ( `${name}: ${value}`} labelLine={false}> {osPieData.map((entry, index) => ( ))} } /> ) : (
No data
)}
{/* Status Distribution */}

Status Distribution

{statusPieData.length > 0 ? ( `${name}: ${value}`} labelLine={false}> {statusPieData.map((entry, index) => ( ))} } /> ) : (
No data
)}
{/* Version Breakdown */}

Agent Versions

{Object.entries(versionCounts) .sort(([, a], [, b]) => b - a) .map(([version, count]) => (
{version}
{count}
))} {Object.keys(versionCounts).length === 0 && (

No version data

)}
{/* Environment Groups */}

Fleet by Platform

{isLoading ? (

Loading fleet data...

) : groups.length === 0 ? (

No agents registered

) : (
{groups.map(group => (

{group.os} / {group.arch}

{group.agents.length} agent{group.agents.length !== 1 ? 's' : ''}
{group.online} online {group.offline > 0 && {group.offline} offline}
{group.agents.map(agent => (
navigate(`/agents/${agent.id}`)} className="px-5 py-3 flex items-center justify-between hover:bg-slate-700/30 cursor-pointer transition-colors" >
{agent.name || agent.hostname}
{agent.ip_address || agent.id}
{agent.version && ( {agent.version} )}
))}
))}
)}
); }