import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { getDiscoveredCertificates, getDiscoverySummary, getDiscoveryScans, claimDiscoveredCertificate, dismissDiscoveredCertificate, getAgents, } from '../api/client'; import PageHeader from '../components/PageHeader'; import DataTable from '../components/DataTable'; import type { Column } from '../components/DataTable'; import StatusBadge from '../components/StatusBadge'; import ErrorState from '../components/ErrorState'; import { formatDateTime } from '../api/utils'; import type { DiscoveredCertificate, DiscoveryScan } from '../api/types'; function ClaimModal({ cert, onClose, onClaim }: { cert: DiscoveredCertificate; onClose: () => void; onClaim: (managedCertId: string) => void }) { const [managedCertId, setManagedCertId] = useState(''); return (
e.stopPropagation()}>

Claim Certificate

Link {cert.common_name} to a managed certificate

setManagedCertId(e.target.value)} placeholder="e.g., mc-api-prod" className="w-full border border-surface-border rounded px-3 py-2 text-sm text-ink bg-white font-mono focus:outline-none focus:ring-2 focus:ring-brand-500" />

Enter the ID of the managed certificate this discovered cert belongs to.

); } function ScanHistoryPanel({ scans }: { scans: DiscoveryScan[] }) { if (scans.length === 0) return

No scans recorded yet

; return (
{scans.map(s => ( ))}
Agent Directories Found New Errors Duration Started
{s.agent_id} {s.directories?.join(', ') || '—'} {s.certificates_found} {s.certificates_new} {s.errors_count > 0 ? {s.errors_count} : '0'} {s.scan_duration_ms}ms {formatDateTime(s.started_at)}
); } export default function DiscoveryPage() { const [statusFilter, setStatusFilter] = useState(''); const [agentFilter, setAgentFilter] = useState(''); const [claimingCert, setClaimingCert] = useState(null); const [showScans, setShowScans] = useState(false); const queryClient = useQueryClient(); const params: Record = {}; if (statusFilter) params.status = statusFilter; if (agentFilter) params.agent_id = agentFilter; const { data, isLoading, error, refetch } = useQuery({ queryKey: ['discovered-certificates', params], queryFn: () => getDiscoveredCertificates(params), refetchInterval: 30000, }); const { data: summary } = useQuery({ queryKey: ['discovery-summary'], queryFn: getDiscoverySummary, refetchInterval: 30000, }); const { data: scansData } = useQuery({ queryKey: ['discovery-scans'], queryFn: () => getDiscoveryScans(), enabled: showScans, }); const { data: agentsData } = useQuery({ queryKey: ['agents-for-filter'], queryFn: () => getAgents({ per_page: '200' }), }); const claimMutation = useMutation({ mutationFn: ({ id, managedCertId }: { id: string; managedCertId: string }) => claimDiscoveredCertificate(id, managedCertId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['discovered-certificates'] }); queryClient.invalidateQueries({ queryKey: ['discovery-summary'] }); setClaimingCert(null); }, }); const dismissMutation = useMutation({ mutationFn: dismissDiscoveredCertificate, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['discovered-certificates'] }); queryClient.invalidateQueries({ queryKey: ['discovery-summary'] }); }, }); const formatExpiry = (notAfter?: string) => { if (!notAfter) return '—'; const d = new Date(notAfter); const now = new Date(); const days = Math.floor((d.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); if (days < 0) return Expired {Math.abs(days)}d ago; if (days < 30) return {days}d left; return {days}d left; }; const discoveryStatusStyle: Record = { Unmanaged: 'badge badge-warning', Managed: 'badge badge-success', Dismissed: 'badge badge-neutral', }; const columns: Column[] = [ { key: 'common_name', label: 'Common Name', render: (c) => (
{c.common_name || '(no CN)'}
{c.sans?.length > 0 && (
{c.sans.slice(0, 2).join(', ')}{c.sans.length > 2 ? ` +${c.sans.length - 2}` : ''}
)}
), }, { key: 'status', label: 'Status', render: (c) => {c.status}, }, { key: 'source', label: 'Source', render: (c) => (
{c.agent_id}
{c.source_path}
), }, { key: 'issuer', label: 'Issuer', render: (c) => {c.issuer_dn?.split(',')[0] || '—'}, }, { key: 'expiry', label: 'Expiry', render: (c) => {formatExpiry(c.not_after)}, }, { key: 'key_info', label: 'Key', render: (c) => (
{c.key_algorithm}{c.key_size ? ` ${c.key_size}` : ''} {c.is_ca && ( CA )}
), }, { key: 'fingerprint', label: 'Fingerprint', render: (c) => {c.fingerprint_sha256?.substring(0, 16)}..., }, { key: 'actions', label: '', render: (c) => ( c.status === 'Unmanaged' ? (
) : null ), }, ]; return ( <> {/* Summary stats bar */} {summary && (
{summary.Unmanaged || 0} Unmanaged
{summary.Managed || 0} Managed
{summary.Dismissed || 0} Dismissed
)} {/* Scan history collapsible */} {showScans && (

Recent Scans

)} {/* Filters */}
{/* Table */}
{error ? ( refetch()} /> ) : ( )}
{claimingCert && ( setClaimingCert(null)} onClaim={(managedCertId) => claimMutation.mutate({ id: claimingCert.id, managedCertId })} /> )} ); }