import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { getCertificates, createCertificate, triggerRenewal, revokeCertificate, updateCertificate, getOwners } from '../api/client'; import { REVOCATION_REASONS } from '../api/types'; 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 { formatDate, daysUntil, expiryColor } from '../api/utils'; import type { Certificate } from '../api/types'; function CreateCertificateModal({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) { const [form, setForm] = useState({ id: '', common_name: '', environment: 'production', issuer_id: '', owner_id: '', team_id: '', renewal_policy_id: '', }); const [error, setError] = useState(''); const mutation = useMutation({ mutationFn: () => createCertificate(form), onSuccess: () => onSuccess(), onError: (err: Error) => setError(err.message), }); return (
e.stopPropagation()}>

New Certificate

{error &&
{error}
}
setForm(f => ({ ...f, id: e.target.value }))} className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500" placeholder="mc-api-prod (auto-generated if empty)" />
setForm(f => ({ ...f, common_name: e.target.value }))} className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500" placeholder="api.example.com" />
setForm(f => ({ ...f, issuer_id: e.target.value }))} className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500" placeholder="iss-local" />
setForm(f => ({ ...f, owner_id: e.target.value }))} className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500" placeholder="o-alice" />
setForm(f => ({ ...f, team_id: e.target.value }))} className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500" placeholder="t-platform" />
setForm(f => ({ ...f, renewal_policy_id: e.target.value }))} className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500" placeholder="rp-standard" />
); } function BulkRevokeModal({ ids, onClose, onSuccess }: { ids: string[]; onClose: () => void; onSuccess: () => void }) { const [reason, setReason] = useState('unspecified'); const [progress, setProgress] = useState(0); const [error, setError] = useState(''); const [running, setRunning] = useState(false); const handleRevoke = async () => { setRunning(true); setError(''); let succeeded = 0; for (const id of ids) { try { await revokeCertificate(id, reason); succeeded++; setProgress(succeeded); } catch (err) { setError(`Failed on ${id}: ${err instanceof Error ? err.message : 'Unknown error'}`); break; } } if (!error) onSuccess(); }; return (
e.stopPropagation()}>

Bulk Revoke

Revoke {ids.length} certificate{ids.length > 1 ? 's' : ''}. This cannot be undone.

{error &&
{error}
} {running && (
Progress {progress}/{ids.length}
)}
); } function BulkReassignModal({ ids, onClose, onSuccess }: { ids: string[]; onClose: () => void; onSuccess: () => void }) { const [ownerId, setOwnerId] = useState(''); const [progress, setProgress] = useState(0); const [error, setError] = useState(''); const [running, setRunning] = useState(false); const { data: owners } = useQuery({ queryKey: ['owners'], queryFn: () => getOwners(), }); const handleReassign = async () => { if (!ownerId) return; setRunning(true); setError(''); let succeeded = 0; for (const id of ids) { try { await updateCertificate(id, { owner_id: ownerId } as Partial); succeeded++; setProgress(succeeded); } catch (err) { setError(`Failed on ${id}: ${err instanceof Error ? err.message : 'Unknown error'}`); break; } } if (!error) onSuccess(); }; return (
e.stopPropagation()}>

Reassign Owner

Reassign {ids.length} certificate{ids.length > 1 ? 's' : ''} to a new owner.

{error &&
{error}
} {running && (
Progress {progress}/{ids.length}
)}
); } export default function CertificatesPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const [statusFilter, setStatusFilter] = useState(''); const [envFilter, setEnvFilter] = useState(''); const [showCreate, setShowCreate] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); const [showBulkRevoke, setShowBulkRevoke] = useState(false); const [showBulkReassign, setShowBulkReassign] = useState(false); const [bulkRenewProgress, setBulkRenewProgress] = useState<{ done: number; total: number; running: boolean } | null>(null); const params: Record = {}; if (statusFilter) params.status = statusFilter; if (envFilter) params.environment = envFilter; const { data, isLoading, error, refetch } = useQuery({ queryKey: ['certificates', params], queryFn: () => getCertificates(params), refetchInterval: 30000, }); const handleBulkRenewal = async () => { const ids = Array.from(selectedIds); setBulkRenewProgress({ done: 0, total: ids.length, running: true }); for (let i = 0; i < ids.length; i++) { try { await triggerRenewal(ids[i]); } catch { // continue on individual failures } setBulkRenewProgress({ done: i + 1, total: ids.length, running: i + 1 < ids.length }); } queryClient.invalidateQueries({ queryKey: ['certificates'] }); setSelectedIds(new Set()); setTimeout(() => setBulkRenewProgress(null), 3000); }; const columns: Column[] = [ { key: 'name', label: 'Certificate', render: (c) => (
{c.common_name}
{c.id}
), }, { key: 'status', label: 'Status', render: (c) => }, { key: 'expires', label: 'Expires', render: (c) => { const days = daysUntil(c.expires_at); return (
{formatDate(c.expires_at)}
{days <= 0 ? 'Expired' : `${days} days`}
); }, }, { key: 'env', label: 'Environment', render: (c) => {c.environment || '—'} }, { key: 'issuer', label: 'Issuer', render: (c) => {c.issuer_id} }, { key: 'owner', label: 'Owner', render: (c) => {c.owner_id} }, ]; const selectedArray = Array.from(selectedIds); const hasSelection = selectedArray.length > 0; return ( <> setShowCreate(true)} className="btn btn-primary text-xs"> + New Certificate } /> {/* Bulk Action Bar */} {hasSelection && (
{selectedArray.length} selected
)} {/* Bulk Renewal Success */} {bulkRenewProgress && !bulkRenewProgress.running && (
Triggered renewal for {bulkRenewProgress.done} certificate{bulkRenewProgress.done > 1 ? 's' : ''}.
)}
{error ? ( refetch()} /> ) : ( navigate(`/certificates/${c.id}`)} emptyMessage="No certificates found" selectable selectedKeys={selectedIds} onSelectionChange={setSelectedIds} /> )}
{showCreate && ( setShowCreate(false)} onSuccess={() => { setShowCreate(false); queryClient.invalidateQueries({ queryKey: ['certificates'] }); }} /> )} {showBulkRevoke && ( setShowBulkRevoke(false)} onSuccess={() => { setShowBulkRevoke(false); setSelectedIds(new Set()); queryClient.invalidateQueries({ queryKey: ['certificates'] }); }} /> )} {showBulkReassign && ( setShowBulkReassign(false)} onSuccess={() => { setShowBulkReassign(false); setSelectedIds(new Set()); queryClient.invalidateQueries({ queryKey: ['certificates'] }); }} /> )} ); }