// CompPrimitives.jsx — primitives for the Compliance page kit. // Lifted directly from frontend/src/components/pages/CompliancePage.js. // Identity color is teal (#14B8A6); status colors map green/amber/red onto // "Meets/Exceeds Target", "Within 15% of Target", and "Below 15% of Target". const { useState: useCompState, useRef: useCompRef } = React; /* ── Tokens ────────────────────────────────────────────────────── Two layers: • Status — drives every percentage display + the worst-status ribbon on metric cards. Always one of three. • Category — owns the colored MetricBadge that flags which program a failing metric belongs to. */ const C_COLORS = { teal: '#14B8A6', tealMid: '#5EEAD4', green: '#10B981', amber: '#F59E0B', red: '#EF4444', sky: '#0EA5E9', purple: '#8B5CF6', orange: '#F97316', slate: '#64748B', }; const STATUS_COLOR = { 'Meets/Exceeds Target': C_COLORS.green, 'Within 15% of Target': C_COLORS.amber, 'Below 15% of Target': C_COLORS.red, }; const CATEGORY_COLORS = { 'Vulnerability Management': C_COLORS.red, 'Access & MFA': C_COLORS.amber, 'Logging & Monitoring': C_COLORS.purple, 'End-of-Life OS': C_COLORS.orange, 'Decommissioned Assets': C_COLORS.slate, 'Asset Data Quality': C_COLORS.slate, 'Application Security': C_COLORS.sky, 'Disaster Recovery': C_COLORS.teal, 'Endpoint Protection': C_COLORS.orange, }; const statusColor = s => STATUS_COLOR[s] || C_COLORS.red; const pctDisplay = p => `${Math.round(p * 100)}%`; const cAlpha = (hex, a) => { const h = hex.replace('#', ''); return `rgba(${parseInt(h.slice(0,2),16)},${parseInt(h.slice(2,4),16)},${parseInt(h.slice(4,6),16)},${a})`; }; /* ── PageHeader ────────────────────────────────────────────────── AEO Compliance — title in teal w/ glow, last-report meta beneath, refresh + upload-report on the right. Mirrors the KB / Reporting header pattern but with teal instead of green. */ function CompPageHeader({ title = 'AEO Compliance', lastReport, networkScore, verticalScore, onRefresh, onUpload, onRollback, isAdmin }) { return (

{title}

{lastReport ? ( <> Last report: {lastReport} {isAdmin && ( )} ) : ( No reports uploaded )} {networkScore != null && ( Network: {networkScore} )} {verticalScore != null && ( Vertical: {verticalScore} )}
Upload Report
); } /* ── Buttons ───────────────────────────────────────────────────── */ function CompButton({ variant = 'neutral', icon, size = 'md', children, ...rest }) { const [hover, setHover] = useCompState(false); const v = { primary: { bg: hover ? cAlpha(C_COLORS.teal, 0.28) : cAlpha(C_COLORS.teal, 0.18), bd: C_COLORS.teal, fg: C_COLORS.teal }, neutral: { bg: hover ? cAlpha(C_COLORS.teal, 0.10) : 'transparent', bd: cAlpha(C_COLORS.teal, 0.30), fg: C_COLORS.teal }, danger: { bg: hover ? 'rgba(239,68,68,0.18)' : 'rgba(239,68,68,0.10)', bd: C_COLORS.red, fg: C_COLORS.red }, ghost: { bg: hover ? 'rgba(255,255,255,0.04)' : 'transparent', bd: 'rgba(100,116,139,0.40)', fg: 'var(--fg-2)' }, }[variant]; const padX = size === 'sm' ? 10 : 16; const padY = size === 'sm' ? 4 : 8; const fs = size === 'sm' ? 11 : 12; return ( ); } function CompIconButton({ icon, onClick, color = C_COLORS.teal }) { const [hover, setHover] = useCompState(false); return ( ); } /* ── TeamTabs ──────────────────────────────────────────────────── */ function TeamTabs({ teams, active, onChange }) { return (
{teams.map(team => { const on = active === team; return ( ); })}
); } /* ── VariantPill ───────────────────────────────────────────────── The compliance % pill that lives inside MetricHealthCard. One per priority/variant within a metric family. Dot only shown when the variant isn't already meeting target — green pills stay quiet. */ function VariantPill({ status, pct, label }) { const color = statusColor(status); const isOk = status === 'Meets/Exceeds Target'; return ( {!isOk && ( )} {label && {label}} {pctDisplay(pct)} ); } /* ── StatusRibbon ──────────────────────────────────────────────── The lozenge at the bottom of MetricHealthCard. "OK" when meeting, abbreviated status text otherwise. */ function StatusRibbon({ status }) { const color = statusColor(status); const isOk = status === 'Meets/Exceeds Target'; return (
{isOk ? 'OK' : status.replace(' of Target', '')}
); } /* ── MetricHealthCard ──────────────────────────────────────────── The big clickable cards in the metric strip. Click to filter the device table; click the info "i" to open the metric definition panel. Border + ID color shift when active. */ function MetricHealthCard({ family, active, onClick, onInfoClick, onHover, onLeave }) { const [h, setH] = useCompState(false); const color = statusColor(family.worstStatus); return ( ); } /* ── MetricBadge ───────────────────────────────────────────────── Compact category-tinted ID chip used in device-row "Failing Metrics" columns and inside detail panels. */ function MetricBadge({ metricId, category }) { const color = CATEGORY_COLORS[category] || C_COLORS.slate; return ( {metricId} ); } /* ── SeenBadge ─────────────────────────────────────────────────── "1×" / "3×" / "5×" — how many cycles a host has been failing the same set of metrics. Color escalates: slate → amber → red. */ function SeenBadge({ count }) { const color = count > 3 ? C_COLORS.red : count > 1 ? C_COLORS.amber : C_COLORS.slate; return ( {count}× ); } /* ── DeviceTable + DeviceRow ───────────────────────────────────── The non-compliant host list. Toolbar has Active/Resolved tabs + hostname search. Rows show hostname, IP, type, failing metric badges, seen count, and a notes indicator. */ function DeviceTable({ children }) { return (
{children}
); } function DeviceTableToolbar({ tab, onTabChange, count, search, onSearchChange }) { return (
{['active', 'resolved'].map(t => { const on = tab === t; return ( ); })}
); } function CompSearchInput({ value, onChange, placeholder, width = 240 }) { const [focus, setFocus] = useCompState(false); return ( setFocus(true)} onBlur={() => setFocus(false)} style={{ background: 'rgba(15,23,42,0.85)', border: `1px solid ${focus ? cAlpha(C_COLORS.teal, 0.60) : cAlpha(C_COLORS.teal, 0.20)}`, borderRadius: 4, color: 'var(--fg-1)', outline: 'none', padding: '6px 12px', fontSize: 12, fontFamily: 'var(--font-mono)', width, transition: 'border-color 160ms ease', boxShadow: focus ? `0 0 0 3px ${cAlpha(C_COLORS.teal, 0.10)}` : 'none', }} /> ); } const DEVICE_GRID = '2.5fr 1.1fr 1fr 2fr 0.5fr 0.4fr'; function DeviceTableHeader() { return (
HostnameIP AddressType Failing MetricsSeen
); } function DeviceRow({ hostname, ip, type, failingMetrics, seenCount, hasNotes, selected, onClick }) { const [hover, setHover] = useCompState(false); return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ display: 'grid', gridTemplateColumns: DEVICE_GRID, padding: '10px 16px', borderBottom: '1px solid rgba(255,255,255,0.04)', cursor: 'pointer', background: selected ? cAlpha(C_COLORS.teal, 0.08) : (hover ? 'rgba(255,255,255,0.025)' : 'transparent'), borderLeft: selected ? `2px solid ${C_COLORS.teal}` : '2px solid transparent', transition: 'all 160ms ease', alignItems: 'center', }} >
{hostname}
{ip || '—'}
{type || '—'}
{failingMetrics.map(m => )}
{hasNotes && }
); } /* ── EmptyState — for table body when there's nothing to show. ── */ function CompEmpty({ children }) { return (
{children}
); } /* ── ChartCard — wrapper around any of the 6 charts on the page. ── */ function ChartCard({ title, subtitle, children, height = 240 }) { return (
{title}
{subtitle && (
{subtitle}
)}
{children}
); } /* ── ChartLegend — shared legend row used at the top of stacked charts. ── */ function ChartLegend({ items }) { return (
{items.map(it => ( {it.label} ))}
); } /* ── DefinitionTooltip ─────────────────────────────────────────── The hover popover that surfaces a metric's title + business justification + data sources. */ function DefinitionTooltip({ title, justification, sources }) { return (
{title}
{justification && (
{justification}
)} {sources && (
Sources: {sources}
)}
); } /* ── RollbackDialog ────────────────────────────────────────────── Centered modal w/ red identity. "Reverses the most recent upload" message + danger confirm. */ function RollbackDialog({ reportLabel, onCancel, onConfirm, loading }) { return (
Rollback Upload
This will reverse the most recent upload:
File: {reportLabel}
New items will be deleted, resolved items will be reactivated, and the upload record will be removed.
Cancel
); } /* ── RollbackToast — bottom-right confirmation/error toast. ── */ function RollbackToast({ tone = 'success', message, detail, onDismiss }) { const c = tone === 'error' ? C_COLORS.red : C_COLORS.green; return (
{message}
{detail &&
{detail}
}
); } /* ── CompIcon — every icon used by the compliance page. ── */ function CompIcon({ name, size = 16, color = 'currentColor' }) { const p = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', style: { display: 'inline-block', verticalAlign: 'middle' }, }; switch (name) { case 'upload': return ; case 'refresh': return ; case 'rotate': return ; case 'message': return ; case 'info': return ; case 'alert': return ; case 'check': return ; case 'loader': return ; case 'x': return ; default: return ; } } window.COMP = { COLORS: C_COLORS, STATUS_COLOR, CATEGORY_COLORS, statusColor, pctDisplay, cAlpha, CompPageHeader, CompButton, CompIconButton, TeamTabs, VariantPill, StatusRibbon, MetricHealthCard, MetricBadge, SeenBadge, DeviceTable, DeviceTableToolbar, DeviceTableHeader, DeviceRow, CompSearchInput, CompEmpty, ChartCard, ChartLegend, DefinitionTooltip, RollbackDialog, RollbackToast, CompIcon, };