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:
299
docs/design-system-redesign/ui_kits/reporting/ReportingPage.jsx
Normal file
299
docs/design-system-redesign/ui_kits/reporting/ReportingPage.jsx
Normal file
@@ -0,0 +1,299 @@
|
||||
// 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 };
|
||||
Reference in New Issue
Block a user