WIP: Dashboard redesign — design system overhaul and component updates

Frontend redesign in progress: updated styles, layout, and components
across all pages to align with new design system. Includes Jira API
compliance specs, property tests, and load test script.
This commit is contained in:
root
2026-04-29 14:20:23 +00:00
parent 37119b9c8a
commit 27192dd69f
78 changed files with 9902 additions and 1368 deletions

View File

@@ -0,0 +1,618 @@
// 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 (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 24, gap: 16 }}>
<div>
<h2 style={{
margin: '0 0 6px 0',
fontFamily: 'var(--font-mono)', fontSize: 24, fontWeight: 700,
color: C_COLORS.teal, textTransform: 'uppercase', letterSpacing: '0.1em',
textShadow: `0 0 16px ${cAlpha(C_COLORS.teal, 0.4)}`,
}}>{title}</h2>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', fontFamily: 'var(--font-mono)', fontSize: 11 }}>
{lastReport ? (
<>
<span style={{ color: 'var(--fg-disabled)' }}>
Last report: <span style={{ color: 'var(--fg-2)' }}>{lastReport}</span>
</span>
{isAdmin && (
<button onClick={onRollback} style={{
background: 'transparent', border: '1px solid rgba(239,68,68,0.25)',
borderRadius: 4, padding: '2px 6px', cursor: 'pointer',
color: 'var(--fg-2)', display: 'inline-flex', alignItems: 'center', gap: 4,
fontSize: 10, fontFamily: 'var(--font-mono)',
}}>
<CompIcon name="rotate" size={10} color="currentColor" /> Rollback
</button>
)}
</>
) : (
<span style={{ color: 'var(--fg-disabled)' }}>No reports uploaded</span>
)}
{networkScore != null && (
<span style={{ color: 'var(--fg-2)' }}>Network: <span style={{ color: C_COLORS.teal }}>{networkScore}</span></span>
)}
{verticalScore != null && (
<span style={{ color: 'var(--fg-2)' }}>Vertical: <span style={{ color: C_COLORS.teal }}>{verticalScore}</span></span>
)}
</div>
</div>
<div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
<CompIconButton icon="refresh" onClick={onRefresh} />
<CompButton variant="primary" icon="upload" onClick={onUpload}>Upload Report</CompButton>
</div>
</div>
);
}
/* ── 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 (
<button
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
style={{
display: 'inline-flex', alignItems: 'center', gap: 6,
padding: `${padY}px ${padX}px`, borderRadius: 6,
background: v.bg, border: `1px solid ${v.bd}`, color: v.fg,
fontFamily: 'var(--font-mono)', fontSize: fs, fontWeight: 600,
textTransform: 'uppercase', letterSpacing: '0.05em',
cursor: 'pointer', transition: 'all 160ms ease', whiteSpace: 'nowrap',
}}
{...rest}
>
{icon && <CompIcon name={icon} size={fs + 2} color={v.fg} />}
{children}
</button>
);
}
function CompIconButton({ icon, onClick, color = C_COLORS.teal }) {
const [hover, setHover] = useCompState(false);
return (
<button onClick={onClick}
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
style={{
background: hover ? cAlpha(color, 0.10) : 'transparent',
border: `1px solid ${hover ? color : cAlpha(color, 0.25)}`,
borderRadius: 6, padding: 8, cursor: 'pointer',
color: hover ? color : 'var(--fg-2)',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
transition: 'all 160ms ease',
}}>
<CompIcon name={icon} size={16} color="currentColor" />
</button>
);
}
/* ── TeamTabs ──────────────────────────────────────────────────── */
function TeamTabs({ teams, active, onChange }) {
return (
<div style={{ display: 'flex', gap: 6, marginBottom: 24 }}>
{teams.map(team => {
const on = active === team;
return (
<button key={team} onClick={() => onChange(team)} style={{
padding: '8px 18px', cursor: 'pointer',
fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600,
textTransform: 'uppercase', letterSpacing: '0.06em',
borderRadius: 6,
border: `1px solid ${on ? C_COLORS.teal : cAlpha(C_COLORS.teal, 0.20)}`,
background: on ? cAlpha(C_COLORS.teal, 0.18) : 'transparent',
color: on ? C_COLORS.teal : 'var(--fg-disabled)',
transition: 'all 160ms ease',
}}>{team}</button>
);
})}
</div>
);
}
/* ── 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 (
<span style={{
display: 'inline-flex', alignItems: 'center', gap: 4,
padding: '2px 7px',
background: cAlpha(color, 0.12),
border: `1px solid ${cAlpha(color, 0.25)}`,
borderRadius: 3,
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-2)', whiteSpace: 'nowrap',
}}>
{!isOk && (
<span style={{
display: 'inline-block', width: 4, height: 4, borderRadius: '50%',
background: color, boxShadow: `0 0 5px ${color}`,
}} />
)}
{label && <span style={{ color: 'var(--fg-disabled)' }}>{label}</span>}
<span style={{ color, fontWeight: 600 }}>{pctDisplay(pct)}</span>
</span>
);
}
/* ── 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 (
<div style={{
display: 'inline-flex', alignItems: 'center', gap: 5,
fontFamily: 'var(--font-mono)', fontSize: 10,
textTransform: 'uppercase', letterSpacing: '0.04em',
color, padding: '3px 9px',
background: cAlpha(color, 0.10),
borderRadius: 999,
border: `1px solid ${cAlpha(color, 0.30)}`,
}}>
<span style={{
display: 'inline-block', width: 5, height: 5, borderRadius: '50%',
background: color, boxShadow: isOk ? 'none' : `0 0 6px ${color}`,
}} />
{isOk ? 'OK' : status.replace(' of Target', '')}
</div>
);
}
/* ── 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 (
<button
onClick={onClick}
onMouseEnter={(e) => { setH(true); onHover && onHover(e.currentTarget); }}
onMouseLeave={() => { setH(false); onLeave && onLeave(); }}
style={{
position: 'relative', textAlign: 'left', cursor: 'pointer',
background: active
? cAlpha(color, 0.15)
: 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)',
border: `1.5px solid ${active ? color : (h ? cAlpha(color, 0.50) : cAlpha(color, 0.25))}`,
borderRadius: 8,
padding: '14px 16px',
minWidth: 160, flex: '1 1 0',
transition: 'all 160ms ease',
}}
>
<span
onClick={(e) => { e.stopPropagation(); onInfoClick && onInfoClick(family.metricId); }}
style={{
position: 'absolute', top: 8, right: 8,
display: 'inline-flex', cursor: 'pointer', padding: 2,
color: 'var(--fg-disabled)', borderRadius: 3,
}}
>
<CompIcon name="info" size={13} color="currentColor" />
</span>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 15, fontWeight: 700,
color: active ? color : 'var(--fg-1)', marginBottom: 4, paddingRight: 20,
}}>{family.metricId}</div>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.05em',
marginBottom: 8,
}}>{family.category}</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 8 }}>
{family.entries.map((e, i) => (
<VariantPill
key={e.metric_id + '-' + i}
status={e.status} pct={e.compliance_pct}
label={family.entries.length > 1 ? (e.priority || `#${i + 1}`) : null}
/>
))}
</div>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-disabled)', marginBottom: 8,
}}>target {pctDisplay(family.target)}</div>
<StatusRibbon status={family.worstStatus} />
</button>
);
}
/* ── 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 (
<span style={{
display: 'inline-flex', alignItems: 'center',
padding: '2px 7px',
background: cAlpha(color, 0.12),
border: `1px solid ${cAlpha(color, 0.30)}`,
borderRadius: 3, color,
fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 600,
whiteSpace: 'nowrap',
}}>{metricId}</span>
);
}
/* ── 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 (
<span style={{
fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 700,
color, padding: '2px 7px',
background: cAlpha(color, 0.10),
border: `1px solid ${cAlpha(color, 0.30)}`,
borderRadius: 3, whiteSpace: 'nowrap',
}}>{count}×</span>
);
}
/* ── 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 (
<div style={{
background: 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)',
border: '1px solid rgba(20,184,166,0.15)',
borderRadius: 8, overflow: 'hidden',
}}>{children}</div>
);
}
function DeviceTableToolbar({ tab, onTabChange, count, search, onSearchChange }) {
return (
<div style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
padding: '14px 16px', borderBottom: '1px solid rgba(255,255,255,0.05)',
}}>
<div style={{ display: 'flex', gap: 4 }}>
{['active', 'resolved'].map(t => {
const on = tab === t;
return (
<button key={t} onClick={() => onTabChange(t)} style={{
padding: '6px 14px', cursor: 'pointer',
fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
textTransform: 'uppercase', letterSpacing: '0.04em',
borderRadius: 4,
border: `1px solid ${on ? cAlpha(C_COLORS.teal, 0.40) : 'transparent'}`,
background: on ? cAlpha(C_COLORS.teal, 0.10) : 'transparent',
color: on ? C_COLORS.teal : 'var(--fg-disabled)',
}}>
{t}
{on && <span style={{ marginLeft: 6, color: 'var(--fg-2)' }}>({count})</span>}
</button>
);
})}
</div>
<CompSearchInput value={search} onChange={onSearchChange} placeholder="Search hostname…" width={220} />
</div>
);
}
function CompSearchInput({ value, onChange, placeholder, width = 240 }) {
const [focus, setFocus] = useCompState(false);
return (
<input
value={value} onChange={onChange} placeholder={placeholder}
onFocus={() => 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 (
<div style={{
display: 'grid', gridTemplateColumns: DEVICE_GRID,
padding: '8px 16px',
borderBottom: '1px solid rgba(255,255,255,0.05)',
fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 600,
color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.08em',
}}>
<span>Hostname</span><span>IP Address</span><span>Type</span>
<span>Failing Metrics</span><span>Seen</span><span></span>
</div>
);
}
function DeviceRow({ hostname, ip, type, failingMetrics, seenCount, hasNotes, selected, onClick }) {
const [hover, setHover] = useCompState(false);
return (
<div
onClick={onClick}
onMouseEnter={() => 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',
}}
>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 12,
color: selected ? C_COLORS.teal : 'var(--fg-1)',
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
}}>{hostname}</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-2)' }}>{ip || '—'}</div>
<div style={{ fontSize: 11, color: 'var(--fg-disabled)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{type || '—'}</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
{failingMetrics.map(m => <MetricBadge key={m.metric_id} metricId={m.metric_id} category={m.category} />)}
</div>
<div><SeenBadge count={seenCount} /></div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
{hasNotes && <CompIcon name="message" size={13} color={cAlpha(C_COLORS.teal, 0.7)} />}
</div>
</div>
);
}
/* ── EmptyState — for table body when there's nothing to show. ── */
function CompEmpty({ children }) {
return (
<div style={{
padding: 48, textAlign: 'center',
color: 'var(--fg-disabled)', fontFamily: 'var(--font-mono)', fontSize: 12,
}}>{children}</div>
);
}
/* ── ChartCard — wrapper around any of the 6 charts on the page. ── */
function ChartCard({ title, subtitle, children, height = 240 }) {
return (
<div style={{
background: 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)',
border: '1px solid rgba(20,184,166,0.15)',
borderRadius: 8, padding: 16,
}}>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
color: 'var(--fg-1)', textTransform: 'uppercase', letterSpacing: '0.08em',
marginBottom: subtitle ? 2 : 12,
}}>{title}</div>
{subtitle && (
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-disabled)', marginBottom: 12,
}}>{subtitle}</div>
)}
<div style={{ height }}>{children}</div>
</div>
);
}
/* ── ChartLegend — shared legend row used at the top of stacked charts. ── */
function ChartLegend({ items }) {
return (
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 8 }}>
{items.map(it => (
<span key={it.label} style={{
display: 'inline-flex', alignItems: 'center', gap: 6,
fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--fg-2)',
}}>
<span style={{
display: 'inline-block', width: 10, height: 10, borderRadius: 2,
background: it.color,
}} />
{it.label}
</span>
))}
</div>
);
}
/* ── DefinitionTooltip ───────────────────────────────────────────
The hover popover that surfaces a metric's title + business
justification + data sources. */
function DefinitionTooltip({ title, justification, sources }) {
return (
<div style={{
width: 300,
background: 'linear-gradient(135deg, rgba(30,41,59,0.98) 0%, rgba(15,23,42,0.99) 100%)',
border: `1px solid ${cAlpha(C_COLORS.teal, 0.30)}`,
borderRadius: 8,
boxShadow: '0 8px 32px rgba(0,0,0,0.6)',
padding: '12px 14px',
}}>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 13, fontWeight: 700, color: 'var(--fg-1)', marginBottom: 6, lineHeight: 1.3 }}>{title}</div>
{justification && (
<div style={{ fontFamily: 'var(--font-display)', fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.4, marginBottom: 6 }}>{justification}</div>
)}
{sources && (
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--fg-disabled)' }}>Sources: {sources}</div>
)}
</div>
);
}
/* ── RollbackDialog ──────────────────────────────────────────────
Centered modal w/ red identity. "Reverses the most recent upload"
message + danger confirm. */
function RollbackDialog({ reportLabel, onCancel, onConfirm, loading }) {
return (
<div style={{
position: 'absolute', inset: 0, zIndex: 60,
background: 'rgba(10,14,39,0.92)', backdropFilter: 'blur(8px)',
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16,
}}>
<div style={{
background: 'linear-gradient(135deg, rgba(30,41,59,0.98) 0%, rgba(15,23,42,0.99) 100%)',
border: '1px solid rgba(239,68,68,0.30)', borderRadius: 12,
boxShadow: '0 20px 60px rgba(0,0,0,0.7)',
width: '100%', maxWidth: 420, padding: 28,
}}>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 15, fontWeight: 700, color: C_COLORS.red, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 12 }}>
Rollback Upload
</div>
<div style={{ fontSize: 13, color: 'var(--fg-1)', lineHeight: 1.5, marginBottom: 8, fontFamily: 'var(--font-display)' }}>
This will reverse the most recent upload:
</div>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--fg-2)',
background: 'rgba(15,23,42,0.6)', borderRadius: 6,
padding: '10px 12px', marginBottom: 18,
border: '1px solid rgba(239,68,68,0.15)',
}}>
<div><span style={{ color: 'var(--fg-disabled)' }}>File:</span> {reportLabel}</div>
<div style={{ marginTop: 4, fontSize: 10, color: 'var(--fg-disabled)' }}>
New items will be deleted, resolved items will be reactivated, and the upload record will be removed.
</div>
</div>
<div style={{ display: 'flex', gap: 10 }}>
<CompButton variant="ghost" onClick={onCancel} style={{ flex: 1, justifyContent: 'center' }}>Cancel</CompButton>
<button onClick={onConfirm} disabled={loading} style={{
flex: 2, padding: 10,
background: loading ? 'rgba(239,68,68,0.05)' : 'rgba(239,68,68,0.10)',
border: `1px solid ${C_COLORS.red}`, borderRadius: 6,
color: C_COLORS.red, cursor: loading ? 'wait' : 'pointer',
fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600,
textTransform: 'uppercase', letterSpacing: '0.05em',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6,
opacity: loading ? 0.6 : 1,
}}>
<CompIcon name="rotate" size={13} color="currentColor" />
{loading ? 'Rolling back…' : 'Confirm Rollback'}
</button>
</div>
</div>
</div>
);
}
/* ── RollbackToast — bottom-right confirmation/error toast. ── */
function RollbackToast({ tone = 'success', message, detail, onDismiss }) {
const c = tone === 'error' ? C_COLORS.red : C_COLORS.green;
return (
<div onClick={onDismiss} style={{
position: 'absolute', bottom: 24, right: 24, zIndex: 70,
background: 'linear-gradient(135deg, rgba(30,41,59,0.98) 0%, rgba(15,23,42,0.99) 100%)',
border: `1px solid ${cAlpha(c, 0.40)}`, borderRadius: 8,
boxShadow: '0 8px 32px rgba(0,0,0,0.5)',
padding: '14px 20px', maxWidth: 360,
fontFamily: 'var(--font-mono)', fontSize: 12, color: c, cursor: 'pointer',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: detail ? 4 : 0 }}>
<CompIcon name={tone === 'error' ? 'alert' : 'rotate'} size={14} color="currentColor" />
{message}
</div>
{detail && <div style={{ fontSize: 10, color: 'var(--fg-2)' }}>{detail}</div>}
</div>
);
}
/* ── 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 <svg {...p}><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>;
case 'refresh': return <svg {...p}><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/></svg>;
case 'rotate': return <svg {...p}><path d="M3 12a9 9 0 1 0 9-9"/><polyline points="3 4 3 9 8 9"/></svg>;
case 'message': return <svg {...p}><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>;
case 'info': return <svg {...p}><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>;
case 'alert': return <svg {...p}><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>;
case 'check': return <svg {...p}><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>;
case 'loader': return <svg {...p}><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/></svg>;
case 'x': return <svg {...p}><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>;
default: return <svg {...p}><circle cx="12" cy="12" r="10"/></svg>;
}
}
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,
};

View File

@@ -0,0 +1,319 @@
// CompliancePage.jsx — full-page assembly of the AEO Compliance view.
// Rebuilt from frontend/src/components/pages/CompliancePage.js with
// inline-rendered chart placeholders that match Recharts visually.
const {
COLORS: PC, statusColor: pStatusColor, pctDisplay: pPct, cAlpha: pAlpha,
CompPageHeader, CompButton, TeamTabs,
MetricHealthCard, DeviceTable, DeviceTableToolbar, DeviceTableHeader, DeviceRow, CompEmpty,
ChartCard, ChartLegend, RollbackDialog, RollbackToast, CompIcon: PIcon,
} = window.COMP;
const { useState: useCompPageState } = React;
/* ── Sample data — what summary + items endpoints look like ── */
const SAMPLE_FAMILIES = [
{
metricId: 'VM-CRITICAL', category: 'Vulnerability Management', target: 0.95, worstStatus: 'Below 15% of Target',
entries: [
{ metric_id: 'VM-CRITICAL', priority: 'P1', compliance_pct: 0.74, status: 'Below 15% of Target' },
{ metric_id: 'VM-CRITICAL', priority: 'P2', compliance_pct: 0.91, status: 'Within 15% of Target' },
],
},
{
metricId: 'AUTH-MFA', category: 'Access & MFA', target: 0.98, worstStatus: 'Within 15% of Target',
entries: [{ metric_id: 'AUTH-MFA', compliance_pct: 0.94, status: 'Within 15% of Target' }],
},
{
metricId: 'LOG-COVERAGE', category: 'Logging & Monitoring', target: 0.90, worstStatus: 'Meets/Exceeds Target',
entries: [{ metric_id: 'LOG-COVERAGE', compliance_pct: 0.97, status: 'Meets/Exceeds Target' }],
},
{
metricId: 'EOL-OS', category: 'End-of-Life OS', target: 1.00, worstStatus: 'Below 15% of Target',
entries: [{ metric_id: 'EOL-OS', compliance_pct: 0.62, status: 'Below 15% of Target' }],
},
{
metricId: 'EDR-DEPLOY', category: 'Endpoint Protection', target: 0.95, worstStatus: 'Meets/Exceeds Target',
entries: [{ metric_id: 'EDR-DEPLOY', compliance_pct: 0.96, status: 'Meets/Exceeds Target' }],
},
];
const SAMPLE_DEVICES = [
{ hostname: 'app-prod-04.steam.internal', ip: '10.42.18.4', type: 'Linux server', failingMetrics: [{ metric_id: 'VM-CRITICAL', category: 'Vulnerability Management' }, { metric_id: 'EOL-OS', category: 'End-of-Life OS' }], seenCount: 5, hasNotes: true },
{ hostname: 'db-staging-01.steam.internal', ip: '10.42.20.11', type: 'Linux server', failingMetrics: [{ metric_id: 'VM-CRITICAL', category: 'Vulnerability Management' }], seenCount: 2, hasNotes: false },
{ hostname: 'fileshare-02.steam.internal', ip: '10.42.16.32', type: 'Windows server', failingMetrics: [{ metric_id: 'AUTH-MFA', category: 'Access & MFA' }], seenCount: 1, hasNotes: false },
{ hostname: 'jumpbox-east.steam.internal', ip: '10.42.4.7', type: 'Linux server', failingMetrics: [{ metric_id: 'AUTH-MFA', category: 'Access & MFA' }, { metric_id: 'VM-CRITICAL', category: 'Vulnerability Management' }], seenCount: 4, hasNotes: true },
{ hostname: 'legacy-billing.steam.internal', ip: '10.42.8.18', type: 'Windows server', failingMetrics: [{ metric_id: 'EOL-OS', category: 'End-of-Life OS' }], seenCount: 7, hasNotes: false },
];
/* ── Inline chart visuals — semantic stand-ins for Recharts. ── */
function NetworkScoreChart() {
const points = [82, 84, 81, 86, 85, 87, 88];
return (
<ChartSvg>
<Line points={points} color={PC.teal} fill={pAlpha(PC.teal, 0.15)} />
<YAxisLabels labels={['100%', '80%', '60%']} />
</ChartSvg>
);
}
function StatusDistributionChart() {
const data = [
{ meets: 62, within: 22, below: 16 },
{ meets: 65, within: 20, below: 15 },
{ meets: 67, within: 21, below: 12 },
{ meets: 72, within: 18, below: 10 },
];
return <StackedBars data={data} keys={['meets', 'within', 'below']} colors={[PC.green, PC.amber, PC.red]} />;
}
function TeamHealthChart() {
return (
<ChartSvg>
<Line points={[78, 80, 79, 83, 85, 88]} color={PC.teal} />
<Line points={[68, 70, 73, 71, 74, 76]} color={PC.amber} />
</ChartSvg>
);
}
function NewRecurringResolvedChart() {
const data = [
{ new_count: 12, recurring_count: 7, resolved_count: -10 },
{ new_count: 8, recurring_count: 9, resolved_count: -14 },
{ new_count: 14, recurring_count: 5, resolved_count: -8 },
{ new_count: 9, recurring_count: 6, resolved_count: -12 },
];
return (
<ChartSvg>
<ChartLegend items={[
{ label: 'New', color: PC.red },
{ label: 'Recurring', color: PC.amber },
{ label: 'Resolved', color: PC.green },
]} />
<StackedBars data={data} keys={['new_count', 'recurring_count', 'resolved_count']} colors={[PC.red, PC.amber, PC.green]} centered />
</ChartSvg>
);
}
function AvgDaysToResolveChart() {
const rows = [
{ label: 'AUTH-MFA', v: 4 },
{ label: 'VM-CRITICAL', v: 12 },
{ label: 'EOL-OS', v: 28 },
{ label: 'EDR-DEPLOY', v: 6 },
];
return <HorizontalBars rows={rows} max={32} color={PC.teal} unit="days" />;
}
function PersistentFindingsChart() {
const rows = [
{ label: 'legacy-billing', v: 7 },
{ label: 'app-prod-04', v: 5 },
{ label: 'jumpbox-east', v: 4 },
{ label: 'db-staging-01', v: 2 },
];
return <HorizontalBars rows={rows} max={8} color={PC.amber} unit="cycles" />;
}
/* Tiny SVG primitives — flat, deterministic, no library. */
function ChartSvg({ children, height = 180 }) {
return (
<div style={{ position: 'relative', width: '100%', height }}>
{children}
</div>
);
}
function Line({ points, color, fill }) {
const max = Math.max(...points);
const min = Math.min(...points) * 0.85;
const range = max - min || 1;
const w = 100, h = 100;
const step = w / (points.length - 1);
const path = points.map((v, i) => `${i === 0 ? 'M' : 'L'} ${i * step} ${h - ((v - min) / range) * h}`).join(' ');
const fillPath = path + ` L ${w} ${h} L 0 ${h} Z`;
return (
<svg width="100%" height="100%" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ overflow: 'visible' }}>
{fill && <path d={fillPath} fill={fill} />}
<path d={path} fill="none" stroke={color} strokeWidth="1.5" />
{points.map((v, i) => (
<circle key={i} cx={i * step} cy={h - ((v - min) / range) * h} r="1.5" fill={color} />
))}
</svg>
);
}
function YAxisLabels({ labels }) {
return (
<div style={{
position: 'absolute', top: 0, bottom: 0, left: -2,
display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--fg-disabled)',
pointerEvents: 'none',
}}>
{labels.map(l => <span key={l}>{l}</span>)}
</div>
);
}
function StackedBars({ data, keys, colors, centered = false }) {
const total = (d) => keys.reduce((s, k) => s + Math.abs(d[k]), 0);
const maxTotal = Math.max(...data.map(total));
return (
<div style={{ display: 'flex', alignItems: centered ? 'center' : 'flex-end', gap: 12, height: '100%', paddingTop: 8 }}>
{data.map((d, i) => {
const segs = keys.map((k, ki) => ({ v: d[k], color: colors[ki], k }));
return (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%' }}>
<div style={{ flex: 1, width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
{segs.map((s, si) => (
<div key={si} style={{
width: '100%', height: `${(Math.abs(s.v) / maxTotal) * 100}%`,
background: s.color, opacity: 0.85,
borderTopLeftRadius: si === 0 ? 2 : 0,
borderTopRightRadius: si === 0 ? 2 : 0,
}} />
))}
</div>
</div>
);
})}
</div>
);
}
function HorizontalBars({ rows, max, color, unit }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 8 }}>
{rows.map(r => (
<div key={r.label} style={{ display: 'grid', gridTemplateColumns: '120px 1fr 50px', gap: 8, alignItems: 'center' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-2)', textAlign: 'right' }}>{r.label}</span>
<div style={{ height: 14, background: 'rgba(255,255,255,0.04)', borderRadius: 3, overflow: 'hidden' }}>
<div style={{ width: `${(r.v / max) * 100}%`, height: '100%', background: color, opacity: 0.85, borderRadius: 3 }} />
</div>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color, fontWeight: 600 }}>{r.v} {unit}</span>
</div>
))}
</div>
);
}
/* ── Page assembly ── */
function CompliancePage() {
const [team, setTeam] = useCompPageState('STEAM');
const [tab, setTab] = useCompPageState('active');
const [filter, setFilter] = useCompPageState(null);
const [search, setSearch] = useCompPageState('');
const [selected, setSelected] = useCompPageState(null);
const [rollback, setRollback] = useCompPageState(null);
const filteredDevices = SAMPLE_DEVICES
.filter(d => !filter || d.failingMetrics.some(m => filter.includes(m.metric_id)))
.filter(d => !search || d.hostname.toLowerCase().includes(search.toLowerCase()));
return (
<div data-screen-label="01 Compliance" style={{
position: 'relative',
padding: 32, minHeight: '100vh', background: 'var(--bg-page)',
fontFamily: 'var(--font-display)',
}}>
<CompPageHeader
lastReport="2026-04-21"
networkScore="88%"
verticalScore="84%"
isAdmin
onRollback={() => setRollback('confirm')}
/>
<TeamTabs teams={['STEAM', 'ACCESS-ENG']} active={team} onChange={setTeam} />
{/* Metric Health */}
<div style={{ marginBottom: 24 }}>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 600,
color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.1em',
marginBottom: 10,
}}>
Metric Health click to filter
{filter && (
<button onClick={() => setFilter(null)} style={{
marginLeft: 12, color: PC.teal, background: 'none', border: 'none',
cursor: 'pointer', fontSize: 10, fontFamily: 'var(--font-mono)',
}}>× clear filter</button>
)}
</div>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
{SAMPLE_FAMILIES.map(family => {
const ids = family.entries.map(e => e.metric_id);
const isActive = filter !== null && filter.length === ids.length && ids.every(id => filter.includes(id));
return (
<div key={family.metricId} style={{ display: 'flex', flex: '1 1 0', minWidth: 160 }}>
<MetricHealthCard
family={family}
active={isActive}
onClick={() => setFilter(isActive ? null : ids)}
onInfoClick={() => {}}
/>
</div>
);
})}
</div>
</div>
{/* Charts */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16, marginBottom: 24 }}>
<ChartCard title="Network Compliance" subtitle="Trailing 7 days">
<NetworkScoreChart />
</ChartCard>
<ChartCard title="Status Distribution" subtitle="Last 4 cycles">
<StatusDistributionChart />
</ChartCard>
<ChartCard title="Team Health" subtitle="STEAM vs ACCESS-ENG">
<TeamHealthChart />
</ChartCard>
<ChartCard title="New / Recurring / Resolved" subtitle="Per cycle" height={200}>
<NewRecurringResolvedChart />
</ChartCard>
<ChartCard title="Avg Days to Resolve" subtitle="By metric">
<AvgDaysToResolveChart />
</ChartCard>
<ChartCard title="Most Persistent Findings" subtitle="By cycles seen">
<PersistentFindingsChart />
</ChartCard>
</div>
{/* Device table */}
<DeviceTable>
<DeviceTableToolbar
tab={tab} onTabChange={setTab}
count={filteredDevices.length}
search={search} onSearchChange={e => setSearch(e.target.value)}
/>
<DeviceTableHeader />
{filteredDevices.length === 0 ? (
<CompEmpty>No non-compliant devices match the current filter</CompEmpty>
) : (
filteredDevices.map(d => (
<DeviceRow
key={d.hostname}
hostname={d.hostname} ip={d.ip} type={d.type}
failingMetrics={d.failingMetrics}
seenCount={d.seenCount} hasNotes={d.hasNotes}
selected={selected === d.hostname}
onClick={() => setSelected(selected === d.hostname ? null : d.hostname)}
/>
))
)}
</DeviceTable>
{rollback === 'confirm' && (
<RollbackDialog
reportLabel="2026-04-21"
onCancel={() => setRollback(null)}
onConfirm={() => setRollback('toast')}
/>
)}
{rollback === 'toast' && (
<RollbackToast
tone="success"
message="Upload rolled back"
detail="42 items deleted, 18 reactivated"
onDismiss={() => setRollback(null)}
/>
)}
</div>
);
}
window.COMP_PAGE = { CompliancePage };

View File

@@ -0,0 +1,363 @@
// KitDocs.jsx — browseable docs page for the Compliance kit.
const { useState: useDocsCompState } = React;
const {
COLORS: DCC, statusColor: dStatus, pctDisplay: dPct, cAlpha: dA,
CompPageHeader: DHeader, CompButton: DBtn, TeamTabs: DTabs,
VariantPill: DVPill, StatusRibbon: DRibbon, MetricHealthCard: DMHC,
MetricBadge: DMB, SeenBadge: DSB,
DeviceTable: DDT, DeviceTableToolbar: DDTT, DeviceTableHeader: DDTH, DeviceRow: DDR,
CompEmpty: DEmpty, ChartCard: DChart, ChartLegend: DLegend,
DefinitionTooltip: DTip, RollbackDialog: DRoll, RollbackToast: DToast,
CompIcon: DIcon,
} = window.COMP;
const { CompliancePage: DPage } = window.COMP_PAGE;
function CSection({ id, eyebrow, title, blurb, children }) {
return (
<section id={id} style={{ paddingTop: 32, scrollMarginTop: 120 }}>
<div style={{ marginBottom: 16 }}>
{eyebrow && (
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 700,
color: DCC.teal, textTransform: 'uppercase', letterSpacing: '0.18em',
marginBottom: 6,
}}>{eyebrow}</div>
)}
<h2 style={{
fontFamily: 'var(--font-mono)', fontSize: 22, fontWeight: 700,
color: 'var(--fg-1)', margin: 0, letterSpacing: '0.02em',
}}>{title}</h2>
{blurb && (
<p style={{
fontFamily: 'var(--font-display)', fontSize: 14, lineHeight: 1.6,
color: 'var(--fg-muted)', maxWidth: 660, margin: '8px 0 0 0',
}}>{blurb}</p>
)}
</div>
{children}
</section>
);
}
function CCode({ children }) {
return (
<code style={{
display: 'inline-block', padding: '2px 6px', borderRadius: 3,
background: dA(DCC.teal, 0.10), border: `1px solid ${dA(DCC.teal, 0.18)}`,
fontFamily: 'var(--font-mono)', fontSize: 11, color: DCC.tealMid,
}}>{children}</code>
);
}
function CSwatch({ name, value, role }) {
return (
<div style={{
display: 'grid', gridTemplateColumns: '52px 1fr auto', alignItems: 'center', gap: 14,
padding: '8px 0', borderBottom: '1px solid rgba(255,255,255,0.04)',
}}>
<div style={{ height: 36, borderRadius: 6, background: value, border: '1px solid rgba(255,255,255,0.08)' }} />
<div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--fg-1)', fontWeight: 600 }}>{name}</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--fg-disabled)' }}>{role}</div>
</div>
<CCode>{value}</CCode>
</div>
);
}
function CSpec({ label, children }) {
return (
<div style={{
display: 'grid', gridTemplateColumns: '160px 1fr', gap: 16,
padding: '10px 0', borderBottom: '1px solid rgba(255,255,255,0.04)',
fontFamily: 'var(--font-mono)', fontSize: 12,
}}>
<div style={{ color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: 10, fontWeight: 600 }}>{label}</div>
<div style={{ color: 'var(--fg-2)' }}>{children}</div>
</div>
);
}
function CSpecimen({ children, padding = 24 }) {
return (
<div style={{
padding,
background: 'rgba(15,23,42,0.5)',
border: '1px solid rgba(255,255,255,0.05)', borderRadius: 8,
}}>{children}</div>
);
}
const TABS = [
{ id: 'overview', label: 'Overview' },
{ id: 'tokens', label: 'Tokens' },
{ id: 'components', label: 'Components' },
{ id: 'assemblies', label: 'Assemblies' },
{ id: 'reference', label: 'Reference Page' },
];
const subhead = {
margin: '32px 0 6px 0',
fontFamily: 'var(--font-mono)', fontSize: 13, fontWeight: 700,
color: 'var(--fg-1)', textTransform: 'uppercase', letterSpacing: '0.08em',
};
const subblurb = {
margin: '0 0 12px 0',
fontFamily: 'var(--font-display)', fontSize: 13, lineHeight: 1.55,
color: 'var(--fg-muted)', maxWidth: 720,
};
const SAMPLE_FAMILY_BAD = {
metricId: 'VM-CRITICAL', category: 'Vulnerability Management', target: 0.95, worstStatus: 'Below 15% of Target',
entries: [
{ metric_id: 'VM-CRITICAL', priority: 'P1', compliance_pct: 0.74, status: 'Below 15% of Target' },
{ metric_id: 'VM-CRITICAL', priority: 'P2', compliance_pct: 0.91, status: 'Within 15% of Target' },
],
};
const SAMPLE_FAMILY_OK = {
metricId: 'EDR-DEPLOY', category: 'Endpoint Protection', target: 0.95, worstStatus: 'Meets/Exceeds Target',
entries: [{ metric_id: 'EDR-DEPLOY', compliance_pct: 0.96, status: 'Meets/Exceeds Target' }],
};
function CKitDocs() {
const [active, setActive] = useDocsCompState('overview');
const handle = (id) => {
setActive(id);
const el = document.getElementById(id);
if (el) {
const top = el.getBoundingClientRect().top + window.scrollY - 80;
window.scrollTo({ top, behavior: 'smooth' });
}
};
return (
<div style={{ minHeight: '100vh', background: 'var(--bg-page)' }}>
<header style={{ padding: '40px 48px 0 48px', maxWidth: 1280, margin: '0 auto' }}>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 700, color: DCC.teal, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: 8 }}>
STEAM Security · UI Kit
</div>
<h1 style={{
margin: 0, fontFamily: 'var(--font-mono)', fontSize: 36, fontWeight: 700,
color: DCC.teal, textTransform: 'uppercase', letterSpacing: '0.08em',
textShadow: `0 0 24px ${dA(DCC.teal, 0.30)}`,
}}>Compliance</h1>
<p style={{
margin: '12px 0 0 0', maxWidth: 720, fontSize: 15, lineHeight: 1.6,
color: 'var(--fg-muted)', fontFamily: 'var(--font-display)',
}}>
The AEO Compliance view: per-team metric health, six trend charts, and a non-compliant device
drilldown. Identity color is teal distinct from the green-titled CVE pages with status colors
that map green/amber/red onto target adherence.
</p>
</header>
<nav style={{
position: 'sticky', top: 0, zIndex: 10, marginTop: 28,
background: 'rgba(2,6,23,0.85)', backdropFilter: 'blur(8px)',
borderBottom: `1px solid ${dA(DCC.teal, 0.15)}`,
}}>
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px', display: 'flex', gap: 4 }}>
{TABS.map(t => {
const on = active === t.id;
return (
<button key={t.id} onClick={() => handle(t.id)} style={{
padding: '14px 16px', background: 'transparent', border: 'none',
borderBottom: `2px solid ${on ? DCC.teal : 'transparent'}`,
color: on ? DCC.teal : 'var(--fg-2)',
fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600,
textTransform: 'uppercase', letterSpacing: '0.08em',
cursor: 'pointer', transition: 'all 160ms ease',
}}>{t.label}</button>
);
})}
</div>
</nav>
<main style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px 96px 48px' }}>
<CSection id="overview" eyebrow="01 — Overview" title="Why this kit exists" blurb="Compliance has its own visual identity inside the suite — teal page title, status colors driven by target adherence, and a metric-card pattern that does double duty as a filter. This kit captures the vocabulary so other audit-style views can reuse it.">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
<CSpecimen>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: DCC.teal, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 12, fontWeight: 600 }}>Identity</div>
<p style={{ margin: 0, fontSize: 13, color: 'var(--fg-1)', lineHeight: 1.6, fontFamily: 'var(--font-display)' }}>
Teal owns the page header, the active team tab, the upload CTA, the active device row,
and any "neutral compliance signal" surface. Status colors (green/amber/red) own
everything that represents target adherence never decorative.
</p>
</CSpecimen>
<CSpecimen>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: DCC.teal, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 12, fontWeight: 600 }}>Layout</div>
<p style={{ margin: 0, fontSize: 13, color: 'var(--fg-1)', lineHeight: 1.6, fontFamily: 'var(--font-display)' }}>
Page header team tabs metric health row (one card per metric family)
3×2 chart grid device table with active/resolved tabs and hostname search.
Selecting a metric card filters the table; selecting a row opens a detail panel.
</p>
</CSpecimen>
</div>
</CSection>
<CSection id="tokens" eyebrow="02 — Tokens" title="Status, category, and identity color" blurb="Status colors are reserved for target adherence. Category colors tag failing-metric badges by program area so a host's failure mix is scannable.">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 32 }}>
<div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 8, fontWeight: 600 }}>Status (target adherence)</div>
<CSwatch name="green" value={DCC.green} role="Meets/Exceeds Target · success" />
<CSwatch name="amber" value={DCC.amber} role="Within 15% of Target · attention" />
<CSwatch name="red" value={DCC.red} role="Below 15% of Target · critical" />
<div style={{ marginTop: 24, fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 8, fontWeight: 600 }}>Identity</div>
<CSwatch name="teal" value={DCC.teal} role="Page title · CTA · selected row" />
</div>
<div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 8, fontWeight: 600 }}>Category</div>
<CSwatch name="red" value={DCC.red} role="Vulnerability Management" />
<CSwatch name="amber" value={DCC.amber} role="Access & MFA" />
<CSwatch name="purple" value={DCC.purple} role="Logging & Monitoring" />
<CSwatch name="orange" value={DCC.orange} role="End-of-Life OS · Endpoint Protection" />
<CSwatch name="sky" value={DCC.sky} role="Application Security" />
<CSwatch name="slate" value={DCC.slate} role="Asset Data Quality · Decommissioned" />
</div>
</div>
<div style={{ marginTop: 32 }}>
<CSpec label="Card chrome">background <CCode>linear-gradient(135deg, rgba(30,41,59,.95), rgba(15,23,42,.98))</CCode></CSpec>
<CSpec label="Metric card border">resting <CCode>1.5px solid {`{statusColor}`} @ 0.25</CCode> · hover <CCode>0.50</CCode> · active <CCode>1.0</CCode> + 15% bg fill</CSpec>
<CSpec label="Title type"><CCode>var(--font-mono)</CCode> · 24 / 700 · uppercase · 0.1em tracking · 16px text-shadow glow</CSpec>
<CSpec label="Worst-status logic">A family's <CCode>worstStatus</CCode> is the lowest-severity entry across all variants — drives card border + ribbon</CSpec>
</div>
</CSection>
<CSection id="components" eyebrow="03 — Components" title="The pieces" blurb="Every primitive used by the page assembly. All are exported on window.COMP.">
<h3 style={subhead}>CompPageHeader</h3>
<p style={subblurb}>Teal title with glow, last-report meta + optional rollback button, network/vertical scores, and a refresh + upload CTA on the right.</p>
<CSpecimen>
<DHeader lastReport="2026-04-21" networkScore="88%" verticalScore="84%" isAdmin onRollback={() => {}} />
</CSpecimen>
<h3 style={subhead}>TeamTabs</h3>
<p style={subblurb}>Two-team toggle pinned above the metric strip. The active tab fills with teal at 18% alpha.</p>
<CSpecimen>
<DTabs teams={['STEAM', 'ACCESS-ENG']} active="STEAM" onChange={() => {}} />
</CSpecimen>
<h3 style={subhead}>CompButton</h3>
<p style={subblurb}>Four variants. Primary is the lone teal CTA (Upload Report). Danger fronts the rollback flow.</p>
<CSpecimen>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
<DBtn variant="primary" icon="upload">Upload Report</DBtn>
<DBtn variant="neutral" icon="refresh">Refresh</DBtn>
<DBtn variant="danger" icon="rotate">Rollback</DBtn>
<DBtn variant="ghost">Cancel</DBtn>
</div>
</CSpecimen>
<h3 style={subhead}>MetricHealthCard</h3>
<p style={subblurb}>The big clickable card in the metric strip. Border + ID color follow the family's <em>worst</em> status, so a single bad variant turns the whole family red. Click filters the device table; the info "i" opens a definition panel.</p>
<CSpecimen>
<div style={{ display: 'flex', gap: 12 }}>
<div style={{ flex: 1 }}><DMHC family={SAMPLE_FAMILY_BAD} active={false} onClick={() => {}} onInfoClick={() => {}} /></div>
<div style={{ flex: 1 }}><DMHC family={SAMPLE_FAMILY_BAD} active={true} onClick={() => {}} onInfoClick={() => {}} /></div>
<div style={{ flex: 1 }}><DMHC family={SAMPLE_FAMILY_OK} active={false} onClick={() => {}} onInfoClick={() => {}} /></div>
</div>
</CSpecimen>
<h3 style={subhead}>VariantPill · StatusRibbon</h3>
<p style={subblurb}>Atoms inside MetricHealthCard. VariantPill = one priority's % readout. StatusRibbon = the bottom lozenge.</p>
<CSpecimen>
<div style={{ display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
<DVPill status="Meets/Exceeds Target" pct={0.97} />
<DVPill status="Within 15% of Target" pct={0.91} label="P2" />
<DVPill status="Below 15% of Target" pct={0.74} label="P1" />
</div>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<DRibbon status="Meets/Exceeds Target" />
<DRibbon status="Within 15% of Target" />
<DRibbon status="Below 15% of Target" />
</div>
</CSpecimen>
<h3 style={subhead}>MetricBadge · SeenBadge</h3>
<p style={subblurb}>Row-level chips in the device table. MetricBadge tints by category; SeenBadge escalates slate→amber→red as repeat-failure count grows.</p>
<CSpecimen>
<div style={{ display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
<DMB metricId="VM-CRITICAL" category="Vulnerability Management" />
<DMB metricId="AUTH-MFA" category="Access & MFA" />
<DMB metricId="LOG-COVERAGE" category="Logging & Monitoring" />
<DMB metricId="EOL-OS" category="End-of-Life OS" />
<DMB metricId="EDR-DEPLOY" category="Endpoint Protection" />
</div>
<div style={{ display: 'flex', gap: 8 }}>
<DSB count={1} /><DSB count={3} /><DSB count={5} /><DSB count={7} />
</div>
</CSpecimen>
<h3 style={subhead}>DeviceRow</h3>
<p style={subblurb}>One non-compliant host per row. Selected state shifts the left border + hostname color to teal.</p>
<CSpecimen padding={0}>
<DDT>
<DDTH />
<DDR hostname="app-prod-04.steam.internal" ip="10.42.18.4" type="Linux server" failingMetrics={[{ metric_id: 'VM-CRITICAL', category: 'Vulnerability Management' }, { metric_id: 'EOL-OS', category: 'End-of-Life OS' }]} seenCount={5} hasNotes={true} selected={true} onClick={() => {}} />
<DDR hostname="db-staging-01.steam.internal" ip="10.42.20.11" type="Linux server" failingMetrics={[{ metric_id: 'VM-CRITICAL', category: 'Vulnerability Management' }]} seenCount={2} hasNotes={false} onClick={() => {}} />
</DDT>
</CSpecimen>
<h3 style={subhead}>ChartCard</h3>
<p style={subblurb}>Wrapper for any of the six trend charts. Title in mono uppercase, optional subtitle in disabled grey, 240px chart well by default.</p>
<CSpecimen>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
<DChart title="Network Compliance" subtitle="Trailing 7 days" height={120}>
<div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-disabled)', fontFamily: 'var(--font-mono)', fontSize: 11 }}>chart well</div>
</DChart>
<DChart title="Status Distribution" subtitle="Last 4 cycles" height={120}>
<DLegend items={[{ label: 'Meets', color: DCC.green }, { label: 'Within 15%', color: DCC.amber }, { label: 'Below 15%', color: DCC.red }]} />
</DChart>
</div>
</CSpecimen>
<h3 style={subhead}>DefinitionTooltip</h3>
<p style={subblurb}>Hover popover used to surface a metric's title, business justification, and data sources.</p>
<CSpecimen>
<DTip title="VM-CRITICAL — Critical Vulnerabilities Patched" justification="Track the percentage of critical CVEs patched within the SLA window. Below-target performance creates exploitable risk on production assets." sources="Tenable, Atlas, JIRA" />
</CSpecimen>
</CSection>
<CSection id="assemblies" eyebrow="04 — Assemblies" title="How the parts compose">
<h3 style={subhead}>Metric health row</h3>
<p style={subblurb}>One MetricHealthCard per family, flexed evenly. Click a card to filter the device table to only its IDs; an "× clear filter" button appears in the section label when active.</p>
<CSpecimen>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 600, color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 10 }}>
Metric Health click to filter
<span style={{ marginLeft: 12, color: DCC.teal }}>× clear filter</span>
</div>
<div style={{ display: 'flex', gap: 10 }}>
<div style={{ flex: 1 }}><DMHC family={SAMPLE_FAMILY_BAD} active={true} onClick={() => {}} onInfoClick={() => {}} /></div>
<div style={{ flex: 1 }}><DMHC family={SAMPLE_FAMILY_OK} active={false} onClick={() => {}} onInfoClick={() => {}} /></div>
</div>
</CSpecimen>
<h3 style={subhead}>Device table</h3>
<p style={subblurb}>Toolbar (active/resolved tabs + hostname search) header row DeviceRows. Empty/loading/error states are centered messages inside the same chrome.</p>
<CSpecimen padding={0}>
<DDT>
<DDTT tab="active" onTabChange={() => {}} count={3} search="" onSearchChange={() => {}} />
<DDTH />
<DDR hostname="app-prod-04.steam.internal" ip="10.42.18.4" type="Linux server" failingMetrics={[{ metric_id: 'VM-CRITICAL', category: 'Vulnerability Management' }, { metric_id: 'EOL-OS', category: 'End-of-Life OS' }]} seenCount={5} hasNotes={true} onClick={() => {}} />
<DDR hostname="jumpbox-east.steam.internal" ip="10.42.4.7" type="Linux server" failingMetrics={[{ metric_id: 'AUTH-MFA', category: 'Access & MFA' }]} seenCount={4} hasNotes={true} onClick={() => {}} />
<DDR hostname="legacy-billing.steam.internal" ip="10.42.8.18" type="Windows server" failingMetrics={[{ metric_id: 'EOL-OS', category: 'End-of-Life OS' }]} seenCount={7} hasNotes={false} onClick={() => {}} />
</DDT>
</CSpecimen>
</CSection>
<CSection id="reference" eyebrow="05 — Reference" title="Full Compliance page" blurb="Every primitive composed exactly as CompliancePage.js renders. The frame below is scrollable.">
<div className="sample-frame" style={{
border: `1px solid ${dA(DCC.teal, 0.20)}`, borderRadius: 12,
overflow: 'hidden', maxHeight: 900, overflowY: 'auto',
background: 'var(--bg-page)',
}}>
<DPage />
</div>
</CSection>
</main>
</div>
);
}
window.COMP_DOCS = { CKitDocs };

View File

@@ -0,0 +1,36 @@
# Compliance UI Kit
Visual vocabulary for the AEO Compliance view (`CompliancePage.js`).
## Files
- `index.html` — entry point.
- `CompPrimitives.jsx``CompPageHeader`, `CompButton`, `TeamTabs`, `MetricHealthCard`, `VariantPill`, `StatusRibbon`, `MetricBadge`, `SeenBadge`, `DeviceTable`/`DeviceRow`, `ChartCard`, `DefinitionTooltip`, `RollbackDialog`, `RollbackToast`, `CompIcon`.
- `CompliancePage.jsx` — full-page assembly.
- `KitDocs.jsx` — Overview · Tokens · Components · Assemblies · Reference.
## Identity
| Surface | Color | Hex |
|----------------------|--------|-----------|
| Page title + glow | teal | `#14B8A6` |
| Active team tab | teal | `#14B8A6` |
| Upload Report CTA | teal | `#14B8A6` |
| Selected device row | teal | `#14B8A6` |
## Status colors (target adherence)
| Status | Color | Hex |
|-------------------------|--------|-----------|
| Meets/Exceeds Target | green | `#10B981` |
| Within 15% of Target | amber | `#F59E0B` |
| Below 15% of Target | red | `#EF4444` |
## Category colors (badge tinting)
red · Vulnerability Management — amber · Access & MFA — purple · Logging & Monitoring — orange · End-of-Life OS / Endpoint Protection — sky · Application Security — slate · Asset Data Quality / Decommissioned
## Layout
Page header → team tabs → metric health row → 3×2 chart grid → device table.
## Page-level rules
1. Status colors are reserved for target adherence; never decorative.
2. A family's `worstStatus` (lowest-severity variant) drives card border + ribbon — one bad variant turns the whole family red.
3. Clicking a metric card filters the device table to its IDs; an "× clear filter" button is the only escape hatch shown inline in the section label.
4. SeenBadge escalates slate (1×) → amber (23×) → red (4×+).

View File

@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>STEAM Security · Compliance UI Kit</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="../../colors_and_type.css" />
<style>
body { margin: 0; min-height: 100vh; }
.page-bg { min-height: 100vh; background: var(--bg-page); }
:target { scroll-margin-top: 120px; }
.sample-frame::-webkit-scrollbar { width: 6px; height: 6px; }
.sample-frame::-webkit-scrollbar-thumb { background: rgba(20,184,166,0.25); border-radius: 4px; }
</style>
</head>
<body>
<div id="root" class="page-bg"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="CompPrimitives.jsx"></script>
<script type="text/babel" src="CompliancePage.jsx"></script>
<script type="text/babel" src="KitDocs.jsx"></script>
<script type="text/babel">
const { CKitDocs } = window.COMP_DOCS;
function App() { return <main data-screen-label="Compliance Kit"><CKitDocs /></main>; }
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>