Files
cve-dashboard/docs/design-system-redesign/ui_kits/reporting/ReportingPage.jsx
root 27192dd69f 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.
2026-04-29 14:20:23 +00:00

300 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ReportingPage.jsx — full-page assembly using only RPT primitives.
// Mirrors frontend/src/components/pages/ReportingPage.js after the KB pass.
const { useState: useRPSt } = React;
const {
COLORS: RC,
PageHeader, RptButton, KbCard, PillTab, FilterChip, StatusBanner,
ToolbarLabel, SeverityDot, SlaPill, WorkflowBadge, DonutSample,
RptIcon: RI,
} = window.RPT;
/* Sample findings rows. Static — purely for layout. */
const SAMPLE_ROWS = [
{ id: 'F-10241', host: 'web-prod-04.steam.local', os: 'Ubuntu 22.04', sev: 'Critical', cve: 'CVE-2024-3094', age: 4, sla: 'OVERDUE', state: 'OPEN', action: 'Patch', owner: 'platform' },
{ id: 'F-10238', host: 'kafka-broker-2.steam.local',os: 'RHEL 9.3', sev: 'Critical', cve: 'CVE-2024-21626', age: 11, sla: 'OVERDUE', state: 'FP', action: 'Investigate',owner: 'data-eng' },
{ id: 'F-10202', host: 'auth-prod-01.steam.local', os: 'Ubuntu 22.04', sev: 'High', cve: 'CVE-2024-1086', age: 3, sla: 'AT_RISK', state: 'OPEN', action: 'Patch', owner: 'platform' },
{ id: 'F-10197', host: 'edge-cdn-09.steam.local', os: 'Alpine 3.19', sev: 'High', cve: 'CVE-2024-23222', age: 2, sla: 'AT_RISK', state: 'EXC', action: 'Accept', owner: 'edge' },
{ id: 'F-10185', host: 'analytics-w-3.steam.local',os: 'Ubuntu 20.04', sev: 'Medium', cve: 'CVE-2023-50387', age: 14, sla: 'WITHIN_SLA', state: 'OPEN', action: 'Mitigate', owner: 'analytics' },
{ id: 'F-10180', host: 'mail-relay-1.steam.local', os: 'Debian 12', sev: 'Medium', cve: 'CVE-2024-22195', age: 9, sla: 'WITHIN_SLA', state: 'REMEDIATED', action: 'Patch', owner: 'platform' },
{ id: 'F-10164', host: 'jumphost-2.steam.local', os: 'Ubuntu 22.04', sev: 'Low', cve: 'CVE-2023-45288', age: 22, sla: 'WITHIN_SLA', state: 'OPEN', action: 'Defer', owner: 'sre' },
];
/* Tiny anomaly bar chart placeholder for the trend section. */
function TrendChartPlaceholder() {
const data = [22, 28, 21, 24, 30, 27, 26, 25, 31, 38, 42, 45, 41, 36, 33];
const closed = [10, 14, 12, 13, 18, 20, 22, 21, 24, 26, 28, 30, 31, 30, 29];
const max = Math.max(...data);
return (
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 4, height: 120, padding: '4px 0' }}>
{data.map((d, i) => (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', gap: 2 }}>
<div style={{
height: `${(d / max) * 100}%`,
background: 'linear-gradient(180deg, rgba(14,165,233,0.85), rgba(14,165,233,0.45))',
borderRadius: '2px 2px 0 0',
}} />
<div style={{
height: `${(closed[i] / max) * 60}%`,
background: 'rgba(16,185,129,0.55)',
borderRadius: '0 0 2px 2px',
}} />
</div>
))}
</div>
);
}
function ReportingPage() {
const [tab, setTab] = useRPSt('ivanti');
const [actionFilter, setActionFilter] = useRPSt(null);
/* Donut data (illustrative) */
const ivantiDonuts = [
{
label: 'Open vs Closed',
donut: <DonutSample
segments={[
{ label: 'Open', value: 184, color: RC.sky },
{ label: 'Closed', value: 712, color: RC.green },
]}
centerLabel="TOTAL" centerValue="896" />,
},
{
label: 'Action Coverage',
labelExtra: actionFilter && (
<span style={{ color: RC.amber, fontSize: 9 }}> filtered</span>
),
donut: <DonutSample
segments={[
{ label: 'Patch', value: 96, color: RC.sky },
{ label: 'Mitigate', value: 42, color: RC.green },
{ label: 'Accept', value: 28, color: '#A78BFA' },
{ label: 'Investigate', value: 18, color: RC.amber },
]}
centerLabel="ASSIGNED" centerValue="184" />,
},
{
label: 'FP Finding Status',
donut: <DonutSample
segments={[
{ label: 'Pending', value: 14, color: RC.amber },
{ label: 'Approved', value: 31, color: RC.green },
{ label: 'Rejected', value: 6, color: RC.red },
]}
centerLabel="FINDINGS" centerValue="51" />,
},
{
label: 'FP Workflow Status',
donut: <DonutSample
segments={[
{ label: 'In Review', value: 8, color: RC.sky },
{ label: 'Closed', value: 22, color: RC.green },
{ label: 'Escalated', value: 4, color: RC.red },
]}
centerLabel="FP TICKETS" centerValue="34" />,
},
];
const atlasDonuts = [
{
label: 'Host Coverage',
donut: <DonutSample
segments={[
{ label: 'With Plans', value: 312, color: RC.green },
{ label: 'Without Plans', value: 88, color: RC.amber },
]}
centerLabel="HOSTS" centerValue="400" />,
},
{
label: 'Plan Types',
donut: <DonutSample
segments={[
{ label: 'Patch', value: 142, color: RC.sky },
{ label: 'Mitigate', value: 68, color: RC.green },
{ label: 'Accept', value: 31, color: '#A78BFA' },
]}
centerLabel="PLANS" centerValue="241" />,
},
{
label: 'Plan Status',
donut: <DonutSample
segments={[
{ label: 'Active', value: 184, color: RC.green },
{ label: 'Pending', value: 42, color: RC.amber },
{ label: 'Stalled', value: 15, color: RC.red },
]}
centerLabel="STATUS" centerValue="241" />,
},
];
const donuts = tab === 'ivanti' ? ivantiDonuts : atlasDonuts;
return (
<div style={{
display: 'flex', flexDirection: 'column', gap: 20,
padding: 24, maxWidth: 1280, margin: '0 auto',
}}>
{/* Page header */}
<PageHeader
title="Reporting"
meta={
<>
Last sync: 2 minutes ago
<span style={{ marginLeft: 10, color: '#334155' }}>· 184 of 896 findings</span>
<span style={{ marginLeft: 8, color: RC.amber }}>(3 filters active)</span>
</>
}
>
<RptButton variant="neutral" icon={<RI.Atlas size={13} />}>Atlas</RptButton>
<RptButton variant="primary" icon={<RI.Refresh size={13} />}>Sync</RptButton>
</PageHeader>
{/* Header-level error */}
<StatusBanner tone="error">Atlas: connection refused retry in 30s</StatusBanner>
{/* Metrics tabs */}
<div style={{ display: 'flex', gap: 5, alignItems: 'center' }}>
<RI.PieChart size={14} style={{ color: '#334155', marginRight: 4 }} />
<PillTab active={tab === 'ivanti'} onClick={() => setTab('ivanti')}>Ivanti Findings</PillTab>
<PillTab active={tab === 'atlas'} onClick={() => setTab('atlas')}>Atlas Coverage</PillTab>
</div>
{/* Donut grid */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
gap: 14,
}}>
{donuts.map((d) => (
<KbCard key={d.label} label={d.label} labelExtra={d.labelExtra}>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 170 }}>
{d.donut}
</div>
</KbCard>
))}
</div>
{/* Trend section */}
<KbCard label="Open vs Closed · last 30 days" labelExtra={
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, color: RC.amber, fontSize: 10 }}>
<RI.AlertTri size={11} /> spike detected day 12
</span>
}>
<TrendChartPlaceholder />
<div style={{ display: 'flex', gap: 14, fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--fg-muted)', justifyContent: 'center' }}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
<span style={{ width: 9, height: 9, background: RC.sky, borderRadius: 2 }} /> Open
</span>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
<span style={{ width: 9, height: 9, background: 'rgba(16,185,129,0.55)', borderRadius: 2 }} /> Closed
</span>
</div>
</KbCard>
{/* Findings table panel */}
<div style={{
background: 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)',
border: '1.5px solid rgba(14,165,233,0.12)',
borderRadius: 8, padding: 20,
}}>
{/* Toolbar */}
<div style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
marginBottom: 12, paddingBottom: 10,
borderBottom: '1px solid rgba(255,255,255,0.04)',
}}>
<ToolbarLabel count="184 of 896">Host Findings</ToolbarLabel>
<div style={{ display: 'flex', gap: 6 }}>
<RptButton variant="subtle" icon={<RI.Download size={12} />}>Export</RptButton>
<RptButton variant="subtle" icon={<RI.ListTodo size={12} />}>Queue</RptButton>
<RptButton variant="subtle" icon={<RI.Settings size={12} />}>Columns</RptButton>
<RptButton variant="subtle" icon={<RI.EyeOff size={12} />}>Rows</RptButton>
</div>
</div>
{/* Search + filter chip row */}
<div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }}>
<div style={{
position: 'relative', flex: '1 1 280px', maxWidth: 360,
}}>
<RI.Search size={13} style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: 'var(--fg-disabled)' }} />
<input
defaultValue="kafka"
placeholder="Search host, CVE, owner…"
style={{
width: '100%', padding: '8px 10px 8px 30px',
background: 'rgba(15,23,42,0.6)',
border: '1px solid rgba(14,165,233,0.18)',
borderRadius: 6,
color: 'var(--fg-1)', fontFamily: 'var(--font-mono)', fontSize: 12,
outline: 'none',
}}
/>
</div>
<FilterChip color={RC.amber}>Severity: Critical, High</FilterChip>
<FilterChip color={RC.sky}>Action: Patch</FilterChip>
<FilterChip color={RC.red}>SLA: Overdue</FilterChip>
</div>
{/* Table */}
<div style={{ overflow: 'auto', borderRadius: 6, border: '1px solid rgba(255,255,255,0.04)' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontFamily: 'var(--font-mono)', fontSize: 11 }}>
<thead>
<tr style={{ background: 'rgba(14,165,233,0.06)' }}>
{['ID', 'Host', 'OS', 'Severity', 'CVE', 'Age', 'SLA', 'State', 'Action', 'Owner'].map((h) => (
<th key={h} style={{
textAlign: 'left', padding: '8px 12px',
color: RC.sky, textTransform: 'uppercase', letterSpacing: '0.08em',
fontWeight: 700, fontSize: 10,
borderBottom: '1px solid rgba(14,165,233,0.18)',
}}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
{h}
<RI.ChevronUpDn size={10} style={{ opacity: 0.5 }} />
</span>
</th>
))}
</tr>
</thead>
<tbody>
{SAMPLE_ROWS.map((r, i) => (
<tr key={r.id} style={{
background: i % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.015)',
borderBottom: '1px solid rgba(255,255,255,0.03)',
}}>
<td style={{ padding: '10px 12px', color: RC.sky, fontWeight: 600 }}>{r.id}</td>
<td style={{ padding: '10px 12px', color: 'var(--fg-1)' }}>{r.host}</td>
<td style={{ padding: '10px 12px', color: 'var(--fg-muted)' }}>{r.os}</td>
<td style={{ padding: '10px 12px' }}><SeverityDot level={r.sev} /></td>
<td style={{ padding: '10px 12px', color: 'var(--fg-2)' }}>{r.cve}</td>
<td style={{ padding: '10px 12px', color: 'var(--fg-muted)' }}>{r.age}d</td>
<td style={{ padding: '10px 12px' }}><SlaPill status={r.sla} /></td>
<td style={{ padding: '10px 12px' }}><WorkflowBadge state={r.state} /></td>
<td style={{ padding: '10px 12px', color: 'var(--fg-2)' }}>{r.action}</td>
<td style={{ padding: '10px 12px', color: 'var(--fg-muted)' }}>{r.owner}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Pagination footer */}
<div style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
paddingTop: 12, marginTop: 4,
fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-muted)',
}}>
<span>Showing 1{SAMPLE_ROWS.length} of 184</span>
<div style={{ display: 'flex', gap: 6 }}>
<RptButton variant="neutral"> Prev</RptButton>
<RptButton variant="neutral">Next </RptButton>
</div>
</div>
</div>
</div>
);
}
window.RPT_PAGE = { ReportingPage };