Files
cve-dashboard/docs/design-system-redesign/ui_kits/compliance/CompliancePage.jsx

320 lines
13 KiB
React
Raw Normal View History

// 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 };