import * as React from 'react'; import { useNavigate } from 'react-router-dom'; import type { Agent, AgentStatus } from '@sentryagent/idp-sdk'; import { Badge } from '@/components/ui/badge'; import { getClient } from '@/lib/client'; const PAGE_LIMIT = 20; /** Maps AgentStatus to a Badge variant. */ function statusVariant(status: AgentStatus): 'success' | 'warning' | 'danger' | 'muted' { switch (status) { case 'active': return 'success'; case 'suspended': return 'warning'; case 'decommissioned': return 'danger'; } } /** Formats an ISO timestamp to a short local date string. */ function formatDate(iso: string): string { return new Date(iso).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } /** Skeleton row shown while loading. */ function SkeletonRow(): React.JSX.Element { return ( {Array.from({ length: 6 }).map((_, i) => (
))} ); } /** * Agents list page — displays all registered agents with search, status filter, and pagination. * Clicking a row navigates to the Agent Detail page. */ export default function Agents(): React.JSX.Element { const navigate = useNavigate(); const [agents, setAgents] = React.useState([]); const [total, setTotal] = React.useState(0); const [page, setPage] = React.useState(1); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); // Filters (client-side email search, server-side status) const [searchInput, setSearchInput] = React.useState(''); const [debouncedSearch, setDebouncedSearch] = React.useState(''); const [statusFilter, setStatusFilter] = React.useState(''); // Debounce search input 300ms React.useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(searchInput); }, 300); return () => { clearTimeout(timer); }; }, [searchInput]); // Reset to page 1 on filter change React.useEffect(() => { setPage(1); }, [debouncedSearch, statusFilter]); React.useEffect(() => { let cancelled = false; setLoading(true); setError(null); const fetchAgents = async (): Promise => { try { const client = getClient(); const result = await client.agents.listAgents({ page, limit: PAGE_LIMIT, status: statusFilter !== '' ? statusFilter : undefined, }); if (!cancelled) { setAgents(result.data); setTotal(result.total); } } catch (err) { if (!cancelled) { setError(err instanceof Error ? err.message : 'Failed to load agents.'); } } finally { if (!cancelled) setLoading(false); } }; void fetchAgents(); return () => { cancelled = true; }; }, [page, statusFilter]); // Client-side email filter applied after API results arrive const filteredAgents = React.useMemo(() => { if (!debouncedSearch.trim()) return agents; const lower = debouncedSearch.toLowerCase(); return agents.filter((a) => a.email.toLowerCase().includes(lower)); }, [agents, debouncedSearch]); const totalPages = Math.max(1, Math.ceil(total / PAGE_LIMIT)); return (

Agents

{ setSearchInput(e.target.value); }} placeholder="Search by email…" className="w-60 rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500" />
{error && (
{error}
)}
{['Name (Email)', 'Type', 'Status', 'Environment', 'Owner', 'Created'].map((col) => ( ))} {loading ? Array.from({ length: 5 }).map((_, i) => ) : filteredAgents.length === 0 ? ( ) : filteredAgents.map((agent) => ( { navigate(`/dashboard/agents/${agent.agentId}`); }} className="cursor-pointer hover:bg-slate-50" > )) }
{col}
No agents found.
{agent.email} {agent.agentType} {agent.status} {agent.deploymentEnv} {agent.owner} {formatDate(agent.createdAt)}
{/* Pagination */} {!loading && total > 0 && (
Page {page} of {totalPages} ({total} total)
)}
); }