import { useEffect, useState, useCallback } from 'react' import { Alert, Box, Card, CardContent, CircularProgress, Container, Grid, IconButton, LinearProgress, Toolbar, Tooltip, Typography, } from '@mui/material' import { CheckCircle, Warning, Error as ErrorIcon, HourglassEmpty, BugReport, RestartAlt, Refresh as RefreshIcon, Security as SecurityIcon, VerifiedUser as VerifiedUserIcon, } from '@mui/icons-material' import { fleetApi, certsApi } from '../api/client' import type { FleetStatus } from '../types' // ── StatCard ───────────────────────────────────────────────────────────────── function StatCard({ title, value, color, icon, }: { title: string value: number color: string icon: React.ReactNode }) { return ( {icon} {value} {title} ) } // ── DashboardPage ───────────────────────────────────────────────────────────── export default function DashboardPage() { const [status, setStatus] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const load = useCallback(async () => { setLoading(true) setError(null) try { const res = await fleetApi.getStatus() setStatus(res.data) } catch { setError('Failed to load fleet status') } finally { setLoading(false) } }, []) // Initial load useEffect(() => { load() }, [load]) // Auto-refresh every 60 seconds useEffect(() => { const t = setInterval(load, 60_000) return () => clearInterval(t) }, [load]) // ── Download Root CA ────────────────────────────────────────────────────── const handleDownloadRootCa = async () => { try { const res = await certsApi.downloadRootCa() const url = URL.createObjectURL(res.data as Blob) const a = document.createElement('a') a.href = url a.download = 'ca.crt' a.click() URL.revokeObjectURL(url) } catch { // silently ignore — user will see no download; no state change needed } } return ( Dashboard {loading ? : } {error && ( {error} )} {!loading && !status && !error && ( No fleet data available. )} {status && ( {/* ── Row 1: Status stat cards ── */} } /> } /> } /> } /> {/* ── Row 2: Compliance bar ── */} Compliance {status.compliance_pct.toFixed(1)}% = 90 ? '#2e7d32' : status.compliance_pct >= 70 ? '#ed6c02' : '#d32f2f', }, }} /> {status.total_hosts} total host{status.total_hosts !== 1 ? 's' : ''} in fleet {/* ── Row 3: Patches + Reboot ── */} {status.total_pending_patches.toLocaleString()} Pending Patches {status.hosts_requiring_reboot.toLocaleString()} Hosts Requiring Reboot {/* ── Row 4: CRL Status ── */} CRL Status {status.crl_valid} Valid {status.crl_expired} Expired {status.crl_missing} Missing {status.crl_invalid} Invalid {status.crl_not_reporting > 0 && ( {status.crl_not_reporting} host{status.crl_not_reporting !== 1 ? 's' : ''} not reporting CRL status )} )} ) }