import React, { useState, useCallback } from 'react'; import * as XLSX from 'xlsx'; import { Download, Loader, AlertCircle, BarChart2, FileText, Shield, Tag, CheckCircle, X } from 'lucide-react'; import { useAuth } from '../../contexts/AuthContext'; import AtlasIcon from '../AtlasIcon'; 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(teamsParam) { const url = teamsParam ? `${API_BASE}/ivanti/findings?teams=${encodeURIComponent(teamsParam)}` : `${API_BASE}/ivanti/findings`; const res = await fetch(url, { 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(); } async function fetchAtlasStatus() { const res = await fetch(`${API_BASE}/atlas/status`, { credentials: 'include' }); if (!res.ok) throw new Error(`Atlas status returned ${res.status}`); return res.json(); } async function fetchJiraTickets() { const res = await fetch(`${API_BASE}/jira-tickets`, { credentials: 'include' }); if (!res.ok) throw new Error(`Jira tickets returned ${res.status}`); return res.json(); } async function fetchCCPStats() { const res = await fetch(`${API_BASE}/compliance/vcl-multi/stats`, { credentials: 'include' }); if (!res.ok) throw new Error(`CCP stats returned ${res.status}`); return res.json(); } async function fetchCCPVerticals() { const res = await fetch(`${API_BASE}/compliance/vcl-multi/verticals`, { credentials: 'include' }); if (!res.ok) throw new Error(`CCP verticals returned ${res.status}`); return res.json(); } async function fetchCCPMetrics() { const res = await fetch(`${API_BASE}/compliance/vcl-multi/metrics`, { credentials: 'include' }); if (!res.ok) throw new Error(`CCP metrics returned ${res.status}`); return res.json(); } async function fetchCCPTrend() { const res = await fetch(`${API_BASE}/compliance/vcl-multi/trend`, { credentials: 'include' }); if (!res.ok) throw new Error(`CCP trend returned ${res.status}`); return res.json(); } async function fetchCCPVerticalMetrics(code) { const res = await fetch(`${API_BASE}/compliance/vcl-multi/vertical/${encodeURIComponent(code)}/metrics`, { credentials: 'include' }); if (!res.ok) throw new Error(`CCP vertical metrics returned ${res.status}`); return res.json(); } async function fetchAtlasAndFindings(teamsParam) { const [atlasRows, findings] = await Promise.all([fetchAtlasStatus(), fetchFindings(teamsParam)]); // Build a lookup from hostId → finding details (hostname, IP, BU, etc.) const hostMap = {}; findings.forEach(f => { if (f.hostId && !hostMap[f.hostId]) { hostMap[f.hostId] = { hostName: f.overrides?.hostName ?? f.hostName ?? '', ipAddress: f.ipAddress ?? '', dns: f.overrides?.dns ?? f.dns ?? '', buOwnership: f.buOwnership ?? '', findingCount: 0, }; } if (f.hostId && hostMap[f.hostId]) hostMap[f.hostId].findingCount++; }); return { atlasRows, hostMap }; } // --------------------------------------------------------------------------- // 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 (