import { ShieldOff } from 'lucide-react'; import { Skeleton } from '@/components/ui/skeleton'; import { SignalRail, type SignalTile } from '@/components/ui/SignalRail'; import { cn } from '@/lib/utils'; import { formatTimeAgo } from '@/lib/relativeTime'; import type { SecurityOverview, ScanSummary, SecurityRiskTrendPoint } from '@/types/security'; import type { SecurityTab } from '@/lib/events'; import { SeverityDonutChart, RiskTrendChart, TopExposedImagesChart, FindingsByTypeChart, } from './SecurityCharts'; import { ScanNodeLauncher } from './ScanNodeLauncher'; interface OverviewTabProps { overview: SecurityOverview | null; /** 'unsupported' = node has no overview endpoint (benign); 'failed' = a real error. */ loadError: 'unsupported' | 'failed' | null; summaries: Record; trend: SecurityRiskTrendPoint[]; onNavigate: (tab: SecurityTab) => void; onInspect: (scanId: number) => void; /** Admin on a node with a ready scanner; enables the node-scan launcher. */ canScan: boolean; /** Refresh the overview after a node-wide scan completes. */ onScanComplete: () => void; } const STATUS_ROW_TONE: Record<'value' | 'warn' | 'subtitle', string> = { value: 'text-stat-value', warn: 'text-warning', subtitle: 'text-stat-subtitle', }; function StatusRow({ label, value, tone }: { label: string; value: string; tone?: 'value' | 'warn' | 'subtitle' }) { const toneClass = STATUS_ROW_TONE[tone ?? 'value']; return (
{label} {value}
); } function ChartCard({ title, className, children }: { title: string; className?: string; children: React.ReactNode }) { return (

{title}

{children}
); } export function OverviewTab({ overview, loadError, summaries, trend, onNavigate, onInspect, canScan, onScanComplete }: OverviewTabProps) { if (loadError === 'unsupported') { return (

Overview unavailable on this node

This node does not report a security overview. Browse images, history, and scanner setup directly.

); } if (loadError === 'failed') { return (

Couldn't load the overview

The security overview failed to load for this node. Switch nodes and back, or try again shortly.

); } if (!overview) { return (
); } const summaryList = Object.values(summaries); const tiles: SignalTile[] = [ { kicker: 'Scanned images', value: String(overview.scannedImages) }, { kicker: 'Fixable', value: String(overview.fixable), tone: overview.fixable > 0 ? 'warn' : 'value' }, { kicker: 'Secrets', value: String(overview.secrets), tone: overview.secrets > 0 ? 'error' : 'value' }, { kicker: 'Misconfigs', value: String(overview.misconfigs), tone: overview.misconfigs > 0 ? 'warn' : 'value' }, { kicker: 'Stale', value: String(overview.staleScans), tone: overview.staleScans > 0 ? 'warn' : 'value' }, { kicker: 'Failed', value: String(overview.failedScans), tone: overview.failedScans > 0 ? 'error' : 'value' }, ]; const scannerValue = overview.scanner.available ? `${overview.scanner.source}${overview.scanner.version ? ` · v${overview.scanner.version}` : ''}` : 'not installed'; return (
{canScan && (

{overview.scannedImages === 0 ? 'No images scanned on this node yet.' : `${overview.scannedImages} image${overview.scannedImages === 1 ? '' : 's'} scanned.`}

)} {/* Charts lead the dashboard. */}
{/* Supporting counts + posture, secondary to the charts above. */}

Scanner

{overview.scanner.source === 'managed' && ( )} {!overview.scanner.available && ( )}

Deploy enforcement

0 ? 'value' : 'subtitle'} />

Manage enforcement policies on the Policies tab. This is a read-only posture for the active node.

); }