import { useState } from 'react'; import { useQuery, useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from 'recharts'; import { getCertificates, getAgents, getJobs, getNotifications, getHealth, getDashboardSummary, getCertificatesByStatus, getExpirationTimeline, getJobTrends, getIssuanceRate, previewDigest, sendDigest, getIssuers, } from '../api/client'; import PageHeader from '../components/PageHeader'; import StatusBadge from '../components/StatusBadge'; import { daysUntil, expiryColor, formatDate } from '../api/utils'; import OnboardingWizard from './OnboardingWizard'; // Convert PascalCase status like "RenewalInProgress" to "Renewal In Progress" const formatStatus = (s: string) => s.replace(/([a-z])([A-Z])/g, '$1 $2'); const STATUS_COLORS: Record = { Active: '#10b981', Expiring: '#f59e0b', Expired: '#ef4444', Revoked: '#8b5cf6', Pending: '#6366f1', RenewalInProgress: '#2ea88f', Failed: '#f43f5e', Archived: '#64748b', }; function StatCard({ label, value, icon, color }: { label: string; value: string | number; icon: string; color: string }) { const colorMap: Record = { success: { bg: 'bg-emerald-50', border: 'border-t-emerald-500', text: 'text-emerald-700' }, warning: { bg: 'bg-amber-50', border: 'border-t-amber-500', text: 'text-amber-700' }, danger: { bg: 'bg-red-50', border: 'border-t-red-500', text: 'text-red-700' }, info: { bg: 'bg-blue-50', border: 'border-t-brand-400', text: 'text-brand-500' }, }; const config = colorMap[color] || colorMap.info; return (

{label}

{value}

); } function ChartCard({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
); } const CustomTooltip = ({ active, payload, label }: any) => { if (!active || !payload?.length) return null; return (

{label}

{payload.map((entry: any, i: number) => (

{entry.name}: {typeof entry.value === 'number' && entry.name?.includes('rate') ? `${entry.value.toFixed(1)}%` : entry.value}

))}
); }; function DigestCard() { const [previewHtml, setPreviewHtml] = useState(null); const [showPreview, setShowPreview] = useState(false); const previewMutation = useMutation({ mutationFn: previewDigest, onSuccess: (html) => { setPreviewHtml(html); setShowPreview(true); }, }); const sendMutation = useMutation({ mutationFn: sendDigest }); return ( <>

Certificate Digest

Send an email summary of certificate status to configured recipients

{sendMutation.isSuccess && (
Digest sent successfully.
)} {sendMutation.isError && (
Failed to send digest. Check SMTP configuration.
)} {previewMutation.isError && (
Digest not configured. Set CERTCTL_DIGEST_ENABLED=true and configure SMTP.
)}
{/* Preview Modal */} {showPreview && previewHtml && (
setShowPreview(false)}>
e.stopPropagation()}>

Digest Email Preview