7 Commits

Author SHA1 Message Date
8b3ea22fa0 Merge feature/reporting-page: full-width layout + in-panel scroll
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 13:24:03 -06:00
75b8ecc61d fix(reporting): full-width layout and in-panel vertical scroll
- Reporting page breaks out of max-w-7xl container to use full viewport width
- Table body scrolls within the panel (maxHeight: calc(100vh - 420px)) so you
  no longer need to scroll the entire page to reach the horizontal scrollbar
- Column headers are sticky (position: sticky, top 0) with opaque background
  so they remain visible while scrolling vertically through findings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 13:23:56 -06:00
ade3cc25ad Merge feature/reporting-page: Add CVEs column
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 13:17:05 -06:00
3fd6158eb3 feat(reporting): add CVEs column from vulnerabilities.vulnInfoList
- Backend extracts cves[] array from f.vulnerabilities.vulnInfoList[].cve
- Frontend shows up to 2 CVE badges (purple) with "+N more" overflow tooltip
- Filter is multi-value aware: selecting a CVE matches any finding containing it
- FilterDropdown expands multi-value arrays into individual checkbox options
- Sort by CVE count (number of associated CVEs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 13:17:01 -06:00
5bbaaf5918 Merge feature/reporting-page: BU Ownership column + column filters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 13:03:20 -06:00
1f36d302ea feat(reporting): add BU Ownership column and per-column Excel-style filters
- buOwnership field extracted from assetCustomAttributes['1550_host_1'][0]
  and stored in SQLite cache; badge-styled cell (sky=STEAM, amber=ACCESS-ENG)
- All columns except Notes get a funnel filter button in the header
- FilterDropdown uses ReactDOM.createPortal + fixed positioning to escape
  overflowX:auto clipping; shows unique value checkboxes with search input,
  Select All, Clear, and a selected/total count footer
- Severity filter groups by vrrGroup label (CRITICAL/HIGH) not numeric value
- columnFilters state gates a useMemo filtered array before sorting
- Active filter count shown in panel header with amber badge; Clear Filters
  button appears in the toolbar when any filters are active
- Empty Set filter (Clear All) hides all rows, consistent with Excel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 13:03:17 -06:00
8697ba4ef3 Reporting page: add Due Date, column manager (hide/reorder), remove Discovered/Source
Backend:
- Extract dueDate from statusEmbedded.dueDate (strip time portion)
- Remove discoveredOn and source from extractFinding (not needed)

Frontend:
- Add Due Date column (color-coded: red=past due, amber=within 30d, gray=future)
- Remove Discovered and Source columns
- ColumnManager component: gear button opens popover with drag-to-reorder and
  eye toggle per column; column state persisted to localStorage
- Column order/visibility survives page refresh and syncs
- SortIcon, TableCell, NoteCell all driven by current visible column list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 12:47:11 -06:00
3 changed files with 694 additions and 185 deletions

View File

@@ -120,6 +120,16 @@ function initTables(db) {
// Extract only the fields we need from a raw finding object // Extract only the fields we need from a raw finding object
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function extractFinding(f) { function extractFinding(f) {
// statusEmbedded.dueDate = "2026-03-06T00:00:00" — strip to date part
const rawDueDate = f.statusEmbedded?.dueDate || '';
const dueDate = rawDueDate ? rawDueDate.split('T')[0] : '';
// BU ownership: assetCustomAttributes['1550_host_1'] is an array like ["NTS-AEO-STEAM"]
const buOwnership = f.assetCustomAttributes?.['1550_host_1']?.[0] || '';
// CVE list: vulnerabilities.vulnInfoList[].cve
const cves = (f.vulnerabilities?.vulnInfoList || []).map(v => v.cve).filter(Boolean);
return { return {
id: String(f.id), id: String(f.id),
title: f.title || '', title: f.title || '',
@@ -130,11 +140,10 @@ function extractFinding(f) {
dns: f.dns || f.host?.fqdn || '', dns: f.dns || f.host?.fqdn || '',
status: f.status || '', status: f.status || '',
slaStatus: f.slaStatus || '', slaStatus: f.slaStatus || '',
discoveredOn: f.discoveredOn || '', dueDate,
lastFoundOn: f.lastFoundOn || '', lastFoundOn: f.lastFoundOn || '',
source: f.scannerPrettyName || f.scannerName || f.source || '', buOwnership,
pluginFamily: f.pluginFamily || '', cves
findingType: f.findingType || ''
}; };
} }

View File

@@ -965,7 +965,7 @@ export default function App() {
{/* Scanning line effect */} {/* Scanning line effect */}
<div className="scan-line"></div> <div className="scan-line"></div>
<div className="max-w-7xl mx-auto relative z-10"> <div className={`${currentPage === 'reporting' ? 'w-full' : 'max-w-7xl mx-auto'} relative z-10`}>
{/* Header */} {/* Header */}
<div className="mb-8"> <div className="mb-8">
<div className="flex justify-between items-start mb-6"> <div className="flex justify-between items-start mb-6">

File diff suppressed because it is too large Load Diff