From 5a9dc007db865dc6e2e939f8464f722b92f79bbe Mon Sep 17 00:00:00 2001 From: root Date: Fri, 24 Apr 2026 21:49:04 +0000 Subject: [PATCH] Add bulk Atlas action plan creation from row selection toolbar --- .../src/components/pages/ReportingPage.js | 291 +++++++++++++++++- 1 file changed, 290 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/pages/ReportingPage.js b/frontend/src/components/pages/ReportingPage.js index f1900fa..9f1399f 100644 --- a/frontend/src/components/pages/ReportingPage.js +++ b/frontend/src/components/pages/ReportingPage.js @@ -3765,7 +3765,7 @@ function RowVisibilityManager({ hiddenRowIds, findings, onRestore, onRestoreAll // --------------------------------------------------------------------------- // BulkHideToolbar — appears when rows are selected for bulk hiding // --------------------------------------------------------------------------- -function BulkHideToolbar({ count, onHide, onClear }) { +function BulkHideToolbar({ count, onHide, onClear, onAtlasBulk, canWrite }) { return (
+ {/* Bulk Atlas Action Plan button */} + {canWrite && onAtlasBulk && ( + + )} + {/* Clear button */} +
+ + {/* Success state */} + {result ? ( +
+ +
+ Action plans created +
+
+ {hostIds.length} host{hostIds.length !== 1 ? 's' : ''} — {planType.replace(/_/g, ' ')} +
+ +
+ ) : ( +
+ {/* Host summary */} +
+
+ {hostEntries.length} unique host{hostEntries.length !== 1 ? 's' : ''} from {selectedFindings.length} selected finding{selectedFindings.length !== 1 ? 's' : ''} +
+
+ {hostEntries.map(h => ( +
+ {h.hostName} + {h.hostId} +
+ ))} +
+
+ + {/* Plan type dropdown */} +
+ +
+ + {typeOpen && ( +
+ {BULK_PLAN_TYPES.map(t => ( +
{ setPlanType(t); setTypeOpen(false); }} style={{ + padding: '0.5rem 0.625rem', cursor: 'pointer', + background: t === planType ? 'rgba(14,165,233,0.12)' : 'transparent', + color: BULK_PLAN_TYPE_COLORS[t], fontSize: '0.78rem', + fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', + }} + onMouseEnter={e => { if (t !== planType) e.currentTarget.style.background = 'rgba(14,165,233,0.06)'; }} + onMouseLeave={e => { if (t !== planType) e.currentTarget.style.background = 'transparent'; }} + > + {t.replace(/_/g, ' ')} +
+ ))} +
+ )} +
+
+ + {/* Commit date */} +
+ + setCommitDate(e.target.value)} + style={{ ...inputSt, colorScheme: 'dark' }} /> +
+ + {/* Optional fields — shown based on plan type */} + {(planType === 'remediation' || planType === 'false_positive') && ( +
+ + setQualysId(e.target.value)} + placeholder="QID-12345" style={inputSt} /> +
+ )} + {planType === 'false_positive' && ( +
+ + setJiraVnr(e.target.value)} + placeholder="VNR-67890" style={inputSt} /> +
+ )} + {(planType === 'risk_acceptance' || planType === 'scan_exclusion') && ( +
+ + setArcherExc(e.target.value)} + placeholder="EXC-54321" style={inputSt} /> +
+ )} + + {/* Error */} + {error && ( +
+ {error} +
+ )} + + {/* Submit */} + +
+ )} + + , + document.body + ); +} + // --------------------------------------------------------------------------- // Main ReportingPage // --------------------------------------------------------------------------- @@ -3864,6 +4143,7 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) { const [atlasSelectedHostId, setAtlasSelectedHostId] = useState(null); const [atlasSelectedHostName, setAtlasSelectedHostName] = useState(null); const [atlasSelectedFindingId, setAtlasSelectedFindingId] = useState(null); + const [bulkAtlasOpen, setBulkAtlasOpen] = useState(false); // Atlas metrics state (for Atlas Coverage tab donut charts) const [atlasMetrics, setAtlasMetrics] = useState(null); @@ -4919,6 +5199,8 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) { count={selectedRowIds.size} onHide={hideSelectedRows} onClear={() => setSelectedRowIds(new Set())} + onAtlasBulk={() => setBulkAtlasOpen(true)} + canWrite={canWrite()} /> )} @@ -5216,6 +5498,13 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) { onPlanChange={fetchAtlasStatus} /> )} + {bulkAtlasOpen && ( + selectedRowIds.has(String(f.id)))} + onClose={() => setBulkAtlasOpen(false)} + onSuccess={() => { fetchAtlasStatus(); }} + /> + )} ); }