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
)}
)}
)
}