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 (
);
}
// ---------------------------------------------------------------------------
// Main page
// ---------------------------------------------------------------------------
export default function ExportsPage() {
const { canExport, getActiveTeamsParam } = useAuth();
const teamsParam = getActiveTeamsParam();
const [loading, setLoading] = useState(null);
const [error, setError] = useState(null);
const [cveStatus, setCveStatus] = useState('');
const [missingOnly, setMissingOnly] = useState(false);
const run = useCallback(async (key, fn) => {
setLoading(key);
setError(null);
try {
await fn();
} catch (e) {
console.error('[Export]', e);
setError(e.message || 'Export failed — check console for details');
} finally {
setLoading(null);
}
}, []);
// ---- Card 1: Ivanti Findings ----
const exportFullFindings = () => run('ivanti-full', async () => {
const findings = await fetchFindings(teamsParam);
const scopeLabel = teamsParam || 'ALL';
toXLSX(
[FINDING_HEADERS, ...findings.map(findingRow)],
'All Findings',
`findings-full-${scopeLabel}-${dateStr()}.xlsx`,
);
});
const exportPending = () => run('ivanti-pending', async () => {
const findings = await fetchFindings(teamsParam);
const scopeLabel = teamsParam || 'ALL';
const rows = findings.filter(f => classifyFinding(f) === 'pending').map(findingRow);
toXLSX([FINDING_HEADERS, ...rows], 'Pending Action', `findings-pending-${scopeLabel}-${dateStr()}.xlsx`);
});
const exportOverdue = () => run('ivanti-overdue', async () => {
const findings = await fetchFindings(teamsParam);
const scopeLabel = teamsParam || 'ALL';
const today = dateStr();
const rows = findings.filter(f => {
if (!f.dueDate && !(f.slaStatus || '').toLowerCase().includes('overdue')) return false;
return f.dueDate < today || (f.slaStatus || '').toUpperCase() === 'OVERDUE';
}).map(findingRow);
toXLSX([FINDING_HEADERS, ...rows], 'Overdue', `findings-overdue-${scopeLabel}-${dateStr()}.xlsx`);
});
const exportByBU = () => run('ivanti-bu', async () => {
const findings = await fetchFindings(teamsParam);
const groups = {};
findings.forEach(f => {
const bu = f.buOwnership || 'Unknown';
if (!groups[bu]) groups[bu] = [];
groups[bu].push(f);
});
const sheets = Object.entries(groups)
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, rows]) => ({ name, rows: [FINDING_HEADERS, ...rows.map(findingRow)] }));
if (sheets.length === 0) sheets.push({ name: 'No Data', rows: [FINDING_HEADERS] });
toMultiXLSX(sheets, `findings-by-bu-${dateStr()}.xlsx`);
});
// ---- Card 2: FP Workflow Summary ----
const exportFPSummary = () => run('fp-summary', async () => {
const findings = await fetchFindings(teamsParam);
const fpMap = {};
findings.forEach(f => {
if (!f.workflow?.id) return;
const id = f.workflow.id;
if (!fpMap[id]) fpMap[id] = { id, state: f.workflow.state || '', count: 0, hosts: new Set(), bus: new Set(), cves: new Set() };
fpMap[id].count++;
const host = f.overrides?.hostName ?? f.hostName;
if (host) fpMap[id].hosts.add(host);
if (f.buOwnership) fpMap[id].bus.add(f.buOwnership);
(f.cves || []).forEach(c => fpMap[id].cves.add(c));
});
const headers = ['FP# ID', 'State', 'Finding Count', 'Hosts', 'Business Units', 'CVEs'];
const rows = Object.values(fpMap)
.sort((a, b) => a.id.localeCompare(b.id))
.map(e => [e.id, e.state, e.count, [...e.hosts].join(', '), [...e.bus].join(', '), [...e.cves].join(', ')]);
toXLSX([headers, ...rows], 'FP Workflows', `fp-workflow-summary-${dateStr()}.xlsx`);
});
// ---- Card 3: CVE Database ----
const exportCVEs = (fmt) => run(`cves-${fmt}`, async () => {
const data = await fetchCVEs(cveStatus);
const headers = ['CVE ID', 'Vendor', 'Severity', 'Status', 'Published Date', 'Description', 'Documents'];
const rows = data.map(c => [c.cve_id, c.vendor, c.severity, c.status, c.published_date ?? '', c.description ?? '', c.document_count ?? 0]);
if (fmt === 'csv') {
toCSV([headers, ...rows], `cve-database-${dateStr()}.csv`);
} else {
toXLSX([headers, ...rows], 'CVEs', `cve-database-${dateStr()}.xlsx`);
}
});
// ---- Card 4: Archer Tickets ----
const exportArcher = () => run('archer', async () => {
const data = await fetchArcher();
const headers = ['EXC Number', 'Status', 'CVE ID', 'Vendor', 'Archer URL', 'Created'];
const rows = data.map(t => [t.exc_number, t.status, t.cve_id ?? '', t.vendor ?? '', t.archer_url ?? '', t.created_at ?? '']);
toXLSX([headers, ...rows], 'Archer Tickets', `archer-tickets-${dateStr()}.xlsx`);
});
// ---- Card 5: Compliance Report ----
const exportCompliance = () => run('compliance', async () => {
const data = await fetchCompliance();
const filtered = missingOnly ? data.filter(r => r.compliance_status !== 'Complete') : data;
const headers = ['CVE ID', 'Vendor', 'Severity', 'Status', 'Total Docs', 'Advisory Docs', 'Email Docs', 'Screenshot Docs', 'Compliance Status'];
const rows = filtered.map(r => [r.cve_id, r.vendor, r.severity, r.status, r.total_documents, r.advisory_count, r.email_count, r.screenshot_count, r.compliance_status]);
toXLSX([headers, ...rows], 'Compliance', `compliance-report-${dateStr()}.xlsx`);
});
// ---- Card 6: Atlas Action Plans ----
const ATLAS_HEADERS = ['Host ID', 'Hostname', 'IP Address', 'Business Unit', 'Open Findings', 'Active Plans', 'Plan Type', 'Commit Date', 'Status', 'Qualys ID', 'Findings ID', 'VNR', 'EXC', 'Last Synced'];
function atlasRow(atlasEntry, hostInfo) {
const plans = JSON.parse(atlasEntry.plans_json || '[]');
const activePlans = plans.filter(p => p.status === 'active');
const h = hostInfo || {};
if (activePlans.length === 0) {
return [[
atlasEntry.host_id, h.hostName || '', h.ipAddress || '', h.buOwnership || '',
h.findingCount || '', 0, '', '', 'No Plan', '', '', '', '', atlasEntry.synced_at || '',
]];
}
return activePlans.map(p => [
atlasEntry.host_id, h.hostName || '', h.ipAddress || '', h.buOwnership || '',
h.findingCount || '', activePlans.length,
(p.plan_type || '').replace(/_/g, ' '), p.commit_date || '', p.status || '',
p.qualys_id || '', p.active_host_findings_id || '',
p.jira_vnr || '', p.archer_exc || '', atlasEntry.synced_at || '',
]);
}
const exportAtlasStatus = () => run('atlas-status', async () => {
const { atlasRows, hostMap } = await fetchAtlasAndFindings(teamsParam);
const rows = atlasRows.flatMap(a => atlasRow(a, hostMap[a.host_id]));
toXLSX([ATLAS_HEADERS, ...rows], 'Atlas Status', `atlas-action-plans-${dateStr()}.xlsx`);
});
const exportAtlasGaps = () => run('atlas-gaps', async () => {
const { atlasRows, hostMap } = await fetchAtlasAndFindings(teamsParam);
const gaps = atlasRows.filter(a => !a.has_action_plan);
const rows = gaps.flatMap(a => atlasRow(a, hostMap[a.host_id]));
toXLSX([ATLAS_HEADERS, ...rows], 'Coverage Gaps', `atlas-coverage-gaps-${dateStr()}.xlsx`);
});
const exportAtlasFull = () => run('atlas-full', async () => {
const { atlasRows, hostMap } = await fetchAtlasAndFindings(teamsParam);
const withPlans = atlasRows.filter(a => a.has_action_plan);
const withoutPlans = atlasRows.filter(a => !a.has_action_plan);
const sheets = [
{ name: 'Active Plans', rows: [ATLAS_HEADERS, ...withPlans.flatMap(a => atlasRow(a, hostMap[a.host_id]))] },
{ name: 'No Plan', rows: [ATLAS_HEADERS, ...withoutPlans.flatMap(a => atlasRow(a, hostMap[a.host_id]))] },
];
// Add history sheet with inactive plans
const historyHeaders = ['Host ID', 'Hostname', 'Plan Type', 'Commit Date', 'Status', 'Qualys ID', 'Findings ID', 'VNR', 'EXC', 'Created'];
const historyRows = [];
atlasRows.forEach(a => {
const plans = JSON.parse(a.plans_json || '[]');
const inactive = plans.filter(p => p.status !== 'active');
const h = hostMap[a.host_id] || {};
inactive.forEach(p => {
historyRows.push([
a.host_id, h.hostName || '',
(p.plan_type || '').replace(/_/g, ' '), p.commit_date || '', p.status || '',
p.qualys_id || '', p.active_host_findings_id || '',
p.jira_vnr || '', p.archer_exc || '', p.created_at ? p.created_at.split('T')[0] : '',
]);
});
});
sheets.push({ name: 'History', rows: [historyHeaders, ...historyRows] });
toMultiXLSX(sheets, `atlas-full-report-${dateStr()}.xlsx`);
});
// ---- Card 7: Jira Tickets ----
const exportJiraAll = () => run('jira-all', async () => {
const tickets = await fetchJiraTickets();
const headers = ['Ticket Key', 'CVE', 'Vendor', 'Summary', 'Status', 'Source', 'URL', 'Last Synced', 'Created'];
const rows = tickets.map(t => [
t.ticket_key, t.cve_id, t.vendor || '', t.summary || '', t.status || 'Open',
t.source_context || 'cve', t.url || '',
t.last_synced_at ? new Date(t.last_synced_at).toLocaleDateString() : 'Never',
t.created_at ? new Date(t.created_at).toLocaleDateString() : '',
]);
toXLSX([headers, ...rows], 'All Tickets', `jira-tickets-all-${dateStr()}.xlsx`);
});
const exportJiraOpen = () => run('jira-open', async () => {
const tickets = await fetchJiraTickets();
const closedStatuses = ['closed', 'done', 'resolved', 'complete', 'completed', 'cancelled', 'canceled', "won't do", 'declined'];
const open = tickets.filter(t => {
const lower = (t.status || '').toLowerCase();
return !closedStatuses.some(s => lower.includes(s));
});
const headers = ['Ticket Key', 'CVE', 'Vendor', 'Summary', 'Status', 'Source', 'URL', 'Last Synced', 'Created'];
const rows = open.map(t => [
t.ticket_key, t.cve_id, t.vendor || '', t.summary || '', t.status || 'Open',
t.source_context || 'cve', t.url || '',
t.last_synced_at ? new Date(t.last_synced_at).toLocaleDateString() : 'Never',
t.created_at ? new Date(t.created_at).toLocaleDateString() : '',
]);
toXLSX([headers, ...rows], 'Open Tickets', `jira-tickets-open-${dateStr()}.xlsx`);
});
const exportJiraByCVE = () => run('jira-by-cve', async () => {
const tickets = await fetchJiraTickets();
const groups = {};
tickets.forEach(t => {
const key = t.cve_id || 'No CVE';
if (!groups[key]) groups[key] = [];
groups[key].push(t);
});
const headers = ['Ticket Key', 'Vendor', 'Summary', 'Status', 'Source', 'URL', 'Last Synced'];
const sheets = Object.entries(groups)
.sort(([a], [b]) => a.localeCompare(b))
.map(([cve, tix]) => ({
name: cve.slice(0, 31),
rows: [headers, ...tix.map(t => [
t.ticket_key, t.vendor || '', t.summary || '', t.status || 'Open',
t.source_context || 'cve', t.url || '',
t.last_synced_at ? new Date(t.last_synced_at).toLocaleDateString() : 'Never',
])],
}));
if (sheets.length === 0) sheets.push({ name: 'No Data', rows: [headers] });
toMultiXLSX(sheets, `jira-tickets-by-cve-${dateStr()}.xlsx`);
});
// ---- Card 8: CCP Metrics ----
const exportCCPSnapshot = () => run('ccp-snapshot', async () => {
const stats = await fetchCCPStats();
const verticals = stats.verticals || [];
const headers = ['Vertical', 'Total Devices', 'Non-Compliant', 'Compliance %', 'Failing Metrics', 'Report Date'];
const rows = verticals.map(v => [
v.vertical || v.code || '',
v.total_devices ?? v.totalDevices ?? '',
v.non_compliant_devices ?? v.nonCompliantDevices ?? '',
v.compliance_pct != null ? `${Number(v.compliance_pct).toFixed(1)}%` : (v.compliancePct != null ? `${Number(v.compliancePct).toFixed(1)}%` : ''),
v.failing_metrics ?? v.failingMetrics ?? '',
v.report_date ?? v.reportDate ?? '',
]);
toXLSX([headers, ...rows], 'CCP Snapshot', `ccp-compliance-snapshot-${dateStr()}.xlsx`);
});
const exportCCPNonCompliant = () => run('ccp-noncompliant', async () => {
const verticals = await fetchCCPVerticals();
const allRows = [];
for (const v of verticals) {
const code = v.code || v.vertical;
if (!code) continue;
try {
const metrics = await fetchCCPVerticalMetrics(code);
const metricList = metrics.metrics || metrics || [];
metricList.forEach(m => {
const devices = m.devices || [];
devices.forEach(d => {
allRows.push([
code, m.metric_id || m.metricId || '', m.metric_desc || m.metricDesc || '',
d.hostname || '', d.ip_address || d.ipAddress || '', d.device_type || d.deviceType || '',
d.team || '',
]);
});
});
} catch (e) {
// Skip verticals that fail
}
}
const headers = ['Vertical', 'Metric ID', 'Metric Description', 'Hostname', 'IP Address', 'Device Type', 'Team'];
toXLSX([headers, ...allRows], 'Non-Compliant Devices', `ccp-non-compliant-devices-${dateStr()}.xlsx`);
});
const exportCCPTrend = () => run('ccp-trend', async () => {
const trend = await fetchCCPTrend();
const snapshots = trend.snapshots || trend || [];
const headers = ['Date', 'Vertical', 'Total Devices', 'Non-Compliant', 'Compliance %'];
const rows = snapshots.flatMap(s => {
const date = s.report_date || s.reportDate || s.date || '';
const verts = s.verticals || [s];
return verts.map(v => [
date,
v.vertical || v.code || '',
v.total_devices ?? v.totalDevices ?? '',
v.non_compliant_devices ?? v.nonCompliantDevices ?? '',
v.compliance_pct != null ? `${Number(v.compliance_pct).toFixed(1)}%` : '',
]);
});
toXLSX([headers, ...rows], 'Trend', `ccp-compliance-trend-${dateStr()}.xlsx`);
});
const exportCCPFull = () => run('ccp-full', async () => {
const [stats, trend] = await Promise.all([fetchCCPStats(), fetchCCPTrend()]);
const verticals = stats.verticals || [];
const snapshots = trend.snapshots || trend || [];
// Sheet 1: Summary
const summaryHeaders = ['Vertical', 'Total Devices', 'Non-Compliant', 'Compliance %', 'Failing Metrics', 'Report Date'];
const summaryRows = verticals.map(v => [
v.vertical || v.code || '',
v.total_devices ?? v.totalDevices ?? '',
v.non_compliant_devices ?? v.nonCompliantDevices ?? '',
v.compliance_pct != null ? `${Number(v.compliance_pct).toFixed(1)}%` : '',
v.failing_metrics ?? v.failingMetrics ?? '',
v.report_date ?? v.reportDate ?? '',
]);
// Sheet 2: Trend
const trendHeaders = ['Date', 'Vertical', 'Total Devices', 'Non-Compliant', 'Compliance %'];
const trendRows = snapshots.flatMap(s => {
const date = s.report_date || s.reportDate || s.date || '';
const verts = s.verticals || [s];
return verts.map(v => [
date, v.vertical || v.code || '',
v.total_devices ?? v.totalDevices ?? '',
v.non_compliant_devices ?? v.nonCompliantDevices ?? '',
v.compliance_pct != null ? `${Number(v.compliance_pct).toFixed(1)}%` : '',
]);
});
toMultiXLSX([
{ name: 'Summary', rows: [summaryHeaders, ...summaryRows] },
{ name: 'Trend', rows: [trendHeaders, ...trendRows] },
], `ccp-full-report-${dateStr()}.xlsx`);
});
// ---- Card 9: Remediation Status (Cross-Domain) ----
const exportRemediationStatus = () => run('remediation', async () => {
const [cves, tickets, archer, findings] = await Promise.all([
fetchCVEs(''),
fetchJiraTickets(),
fetchArcher(),
fetchFindings(teamsParam),
]);
// Build lookup maps
const ticketsByCVE = {};
tickets.forEach(t => {
const key = `${t.cve_id}|${t.vendor || ''}`;
if (!ticketsByCVE[key]) ticketsByCVE[key] = [];
ticketsByCVE[key].push(t);
});
const archerByCVE = {};
archer.forEach(a => {
const key = `${a.cve_id}|${a.vendor || ''}`;
if (!archerByCVE[key]) archerByCVE[key] = [];
archerByCVE[key].push(a);
});
const findingsByCVE = {};
findings.forEach(f => {
(f.cves || []).forEach(cve => {
if (!findingsByCVE[cve]) findingsByCVE[cve] = [];
findingsByCVE[cve].push(f);
});
});
const headers = [
'CVE ID', 'Vendor', 'Severity', 'CVE Status',
'Jira Tickets', 'Jira Statuses',
'Archer EXC#', 'Archer Status',
'Ivanti Findings', 'Overdue Findings',
'Overall Progress',
];
const rows = cves.map(c => {
const key = `${c.cve_id}|${c.vendor}`;
const cveTickets = ticketsByCVE[key] || [];
const cveArcher = archerByCVE[key] || [];
const cveFindings = findingsByCVE[c.cve_id] || [];
const today = dateStr();
const overdueCount = cveFindings.filter(f => f.dueDate && f.dueDate < today).length;
// Determine overall progress
let progress = 'Not Started';
if (cveTickets.length > 0 || cveArcher.length > 0) {
const closedKeywords = ['closed', 'done', 'resolved', 'complete', 'completed'];
const allTicketsClosed = cveTickets.length > 0 && cveTickets.every(t => closedKeywords.some(s => (t.status || '').toLowerCase().includes(s)));
const allArcherAccepted = cveArcher.length > 0 && cveArcher.every(a => a.status === 'Accepted');
if (allTicketsClosed && (cveArcher.length === 0 || allArcherAccepted)) {
progress = 'Complete';
} else {
progress = 'In Progress';
}
}
return [
c.cve_id, c.vendor, c.severity, c.status,
cveTickets.map(t => t.ticket_key).join(', '),
cveTickets.map(t => `${t.ticket_key}: ${t.status || 'Open'}`).join('; '),
cveArcher.map(a => a.exc_number).join(', '),
cveArcher.map(a => `${a.exc_number}: ${a.status}`).join('; '),
cveFindings.length,
overdueCount,
progress,
];
});
toXLSX([headers, ...rows], 'Remediation Status', `remediation-status-${dateStr()}.xlsx`);
});
// ---- Render ----
if (!canExport()) {
return (
You do not have permission to export data.
);
}
return (
{/* Page header */}
Exports
{/* Error banner */}
{error && (
{error}
)}
{/* Card grid */}
{/* ── Card 1: Ivanti Findings ── */}
"By Business Unit" creates one sheet per BU in a single workbook.
{/* ── Card 2: FP Workflow Summary ── */}
{/* ── Card 3: CVE Database ── */}
Status
exportCVEs('csv')} />
exportCVEs('xlsx')} />
{/* ── Card 4: Archer Tickets ── */}
{/* ── Card 5: Compliance Report ── */}
{/* ── Card 6: Atlas Action Plans ── */}
"Full Report" creates three sheets: Active Plans, No Plan, and History (overridden plans).
{/* ── Card 7: Jira Tickets ── */}
{/* ── Card 8: CCP Metrics ── */}
"Non-Compliant Devices" fetches per-metric device lists for all verticals — may take a moment.
{/* ── Card 9: Remediation Status (Cross-Domain) ── */}
Pulls from CVE database, Jira tickets, Archer tickets, and Ivanti findings cache. Best for leadership status updates.
);
}