import React, { useState, useCallback } from 'react'; import * as XLSX from 'xlsx'; import { Download, Loader, AlertCircle, BarChart2, FileText, Shield, Tag, CheckCircle, X } from 'lucide-react'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const EXC_PATTERN = /EXC-\d+/i; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function classifyFinding(f) { if (f.workflow != null) return 'fp'; if (EXC_PATTERN.test(f.note || '')) return 'archer'; return 'pending'; } const dateStr = () => new Date().toISOString().slice(0, 10); function triggerDownload(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function autoFit(ws, rows) { if (!rows[0]) return; ws['!cols'] = rows[0].map((_, ci) => ({ wch: Math.min(60, Math.max(10, ...rows.map(r => String(r[ci] ?? '').length))) })); } function toXLSX(rows, sheetName, filename) { const ws = XLSX.utils.aoa_to_sheet(rows); autoFit(ws, rows); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, sheetName); XLSX.writeFile(wb, filename); } function toMultiXLSX(sheets, filename) { const wb = XLSX.utils.book_new(); sheets.forEach(({ name, rows }) => { const ws = XLSX.utils.aoa_to_sheet(rows); autoFit(ws, rows); XLSX.utils.book_append_sheet(wb, ws, String(name || 'Unknown').slice(0, 31)); }); XLSX.writeFile(wb, filename); } function toCSV(rows, filename) { const csv = rows.map(row => row.map(cell => { const s = String(cell ?? ''); return (s.includes(',') || s.includes('"') || s.includes('\n')) ? `"${s.replace(/"/g, '""')}"` : s; }).join(',') ).join('\r\n'); triggerDownload(new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }), filename); } // --------------------------------------------------------------------------- // Finding column definitions // --------------------------------------------------------------------------- const FINDING_HEADERS = [ 'Finding ID', 'Title', 'Severity Score', 'Severity Group', 'Host', 'IP Address', 'DNS', 'Due Date', 'SLA Status', 'Business Unit', 'FP# ID', 'FP# State', 'Last Found', 'CVEs', 'Notes', ]; function findingRow(f) { return [ f.id, f.title, f.severity != null ? Number(f.severity).toFixed(2) : '', f.vrrGroup ?? '', f.overrides?.hostName ?? f.hostName ?? '', f.ipAddress ?? '', f.overrides?.dns ?? f.dns ?? '', f.dueDate ?? '', f.slaStatus ?? '', f.buOwnership ?? '', f.workflow?.id ?? '', f.workflow?.state ?? '', f.lastFoundOn ?? '', (f.cves || []).join(', '), f.note ?? '', ]; } // --------------------------------------------------------------------------- // API fetchers // --------------------------------------------------------------------------- async function fetchFindings() { const res = await fetch(`${API_BASE}/ivanti/findings`, { credentials: 'include' }); if (!res.ok) throw new Error(`Ivanti findings returned ${res.status}`); const data = await res.json(); return data.findings || []; } async function fetchCVEs(status) { const url = status ? `${API_BASE}/cves?status=${encodeURIComponent(status)}` : `${API_BASE}/cves`; const res = await fetch(url, { credentials: 'include' }); if (!res.ok) throw new Error(`CVE list returned ${res.status}`); return res.json(); } async function fetchArcher() { const res = await fetch(`${API_BASE}/archer-tickets`, { credentials: 'include' }); if (!res.ok) throw new Error(`Archer tickets returned ${res.status}`); return res.json(); } async function fetchCompliance() { const res = await fetch(`${API_BASE}/cves/compliance`, { credentials: 'include' }); if (!res.ok) throw new Error(`Compliance data returned ${res.status}`); return res.json(); } // --------------------------------------------------------------------------- // Sub-components // --------------------------------------------------------------------------- function ExportCard({ color, colorRgb, icon: Icon, title, description, children }) { return (

{title}

{description}

{children}
); } function ExportBtn({ label, exportKey, loading, color, colorRgb, onClick, disabled }) { const isLoading = loading === exportKey; return ( ); } function Toggle({ label, checked, onChange, color, colorRgb }) { return (