484 lines
17 KiB
TypeScript
484 lines
17 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
Grid,
|
|
Card,
|
|
CardContent,
|
|
Button,
|
|
CircularProgress,
|
|
Divider,
|
|
Chip,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Alert
|
|
} from '@mui/material';
|
|
import {
|
|
PictureAsPdf as PdfIcon,
|
|
Assessment as AssessmentIcon,
|
|
Security as SecurityIcon,
|
|
Devices as DevicesIcon,
|
|
Apps as AppsIcon
|
|
} from '@mui/icons-material';
|
|
import api from '@/lib/axios';
|
|
import jsPDF from 'jspdf';
|
|
import { format } from 'date-fns';
|
|
|
|
interface ComplianceSummary {
|
|
totalDevices: number;
|
|
vulnerableDevices: number;
|
|
totalVulnerabilities: number;
|
|
criticalVulns: number;
|
|
highVulns: number;
|
|
mediumVulns: number;
|
|
lowVulns: number;
|
|
totalSoftware: number;
|
|
vulnerableSoftware: number;
|
|
lastUpdated: string;
|
|
}
|
|
|
|
interface TopVulnerability {
|
|
cveId: string;
|
|
title: string;
|
|
severity: string;
|
|
score: number;
|
|
affectedDevices: number;
|
|
}
|
|
|
|
interface TopVulnerableSoftware {
|
|
softwareName: string;
|
|
totalInstances: number;
|
|
vulnerableInstances: number;
|
|
totalCves: number;
|
|
}
|
|
|
|
export default function ReportingPage() {
|
|
const [summary, setSummary] = useState<ComplianceSummary | null>(null);
|
|
const [topVulns, setTopVulns] = useState<TopVulnerability[]>([]);
|
|
const [topSoftware, setTopSoftware] = useState<TopVulnerableSoftware[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [exporting, setExporting] = useState(false);
|
|
const reportRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
const fetchReportingData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Fetch compliance summary data (you'll need to create these endpoints)
|
|
const [summaryRes, vulnsRes, softwareRes] = await Promise.all([
|
|
api.get('/reporting/compliance-summary'),
|
|
api.get('/reporting/top-vulnerabilities'),
|
|
api.get('/reporting/vulnerable-software')
|
|
]);
|
|
|
|
setSummary(summaryRes.data);
|
|
setTopVulns(vulnsRes.data);
|
|
setTopSoftware(softwareRes.data);
|
|
} catch (error) {
|
|
console.error('Failed to fetch reporting data:', error);
|
|
// Create mock data for now since endpoints don't exist yet
|
|
createMockData();
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchReportingData();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
const createMockData = () => {
|
|
setSummary({
|
|
totalDevices: 127,
|
|
vulnerableDevices: 89,
|
|
totalVulnerabilities: 342,
|
|
criticalVulns: 23,
|
|
highVulns: 67,
|
|
mediumVulns: 156,
|
|
lowVulns: 96,
|
|
totalSoftware: 1247,
|
|
vulnerableSoftware: 89,
|
|
lastUpdated: new Date().toISOString()
|
|
});
|
|
|
|
setTopVulns([
|
|
{ cveId: 'CVE-2024-12345', title: 'Critical Remote Code Execution', severity: 'CRITICAL', score: 9.8, affectedDevices: 34 },
|
|
{ cveId: 'CVE-2024-12346', title: 'Privilege Escalation Vulnerability', severity: 'HIGH', score: 8.4, affectedDevices: 28 },
|
|
{ cveId: 'CVE-2024-12347', title: 'Information Disclosure', severity: 'HIGH', score: 7.9, affectedDevices: 22 },
|
|
{ cveId: 'CVE-2024-12348', title: 'Cross-Site Scripting', severity: 'MEDIUM', score: 6.1, affectedDevices: 19 },
|
|
{ cveId: 'CVE-2024-12349', title: 'Denial of Service', severity: 'MEDIUM', score: 5.8, affectedDevices: 15 }
|
|
]);
|
|
|
|
setTopSoftware([
|
|
{ softwareName: 'Apache HTTP Server', totalInstances: 45, vulnerableInstances: 23, totalCves: 12 },
|
|
{ softwareName: 'OpenSSL', totalInstances: 89, vulnerableInstances: 34, totalCves: 8 },
|
|
{ softwareName: 'Microsoft Office', totalInstances: 127, vulnerableInstances: 67, totalCves: 15 },
|
|
{ softwareName: 'Adobe Reader', totalInstances: 98, vulnerableInstances: 45, totalCves: 9 },
|
|
{ softwareName: 'Java Runtime Environment', totalInstances: 76, vulnerableInstances: 28, totalCves: 6 }
|
|
]);
|
|
};
|
|
|
|
const exportToPDF = async () => {
|
|
if (!reportRef.current || !summary) return;
|
|
|
|
try {
|
|
setExporting(true);
|
|
|
|
// Create a new jsPDF instance
|
|
const pdf = new jsPDF('p', 'mm', 'a4');
|
|
const pageWidth = pdf.internal.pageSize.getWidth();
|
|
const pageHeight = pdf.internal.pageSize.getHeight();
|
|
|
|
// Add header
|
|
pdf.setFontSize(20);
|
|
pdf.setFont('helvetica', 'bold');
|
|
pdf.text('Compliance Security Report', pageWidth / 2, 20, { align: 'center' });
|
|
|
|
pdf.setFontSize(12);
|
|
pdf.setFont('helvetica', 'normal');
|
|
pdf.text(`Generated: ${format(new Date(), 'PPP')}`, pageWidth / 2, 30, { align: 'center' });
|
|
|
|
let yPosition = 45;
|
|
|
|
// Executive Summary
|
|
pdf.setFontSize(16);
|
|
pdf.setFont('helvetica', 'bold');
|
|
pdf.text('Executive Summary', 20, yPosition);
|
|
yPosition += 10;
|
|
|
|
pdf.setFontSize(10);
|
|
pdf.setFont('helvetica', 'normal');
|
|
|
|
const summaryText = [
|
|
`Total Devices: ${summary.totalDevices}`,
|
|
`Vulnerable Devices: ${summary.vulnerableDevices} (${((summary.vulnerableDevices / summary.totalDevices) * 100).toFixed(1)}%)`,
|
|
`Total Vulnerabilities: ${summary.totalVulnerabilities}`,
|
|
`Critical: ${summary.criticalVulns} | High: ${summary.highVulns} | Medium: ${summary.mediumVulns} | Low: ${summary.lowVulns}`,
|
|
`Software Applications: ${summary.totalSoftware}`,
|
|
`Vulnerable Software: ${summary.vulnerableSoftware}`
|
|
];
|
|
|
|
summaryText.forEach(text => {
|
|
pdf.text(text, 20, yPosition);
|
|
yPosition += 6;
|
|
});
|
|
|
|
yPosition += 10;
|
|
|
|
// Top Vulnerabilities
|
|
pdf.setFontSize(16);
|
|
pdf.setFont('helvetica', 'bold');
|
|
pdf.text('Top Critical Vulnerabilities', 20, yPosition);
|
|
yPosition += 10;
|
|
|
|
pdf.setFontSize(9);
|
|
pdf.setFont('helvetica', 'normal');
|
|
|
|
topVulns.slice(0, 10).forEach((vuln, index) => {
|
|
if (yPosition > pageHeight - 30) {
|
|
pdf.addPage();
|
|
yPosition = 20;
|
|
}
|
|
|
|
pdf.text(`${index + 1}. ${vuln.cveId} - ${vuln.title}`, 20, yPosition);
|
|
yPosition += 5;
|
|
pdf.text(` Severity: ${vuln.severity} | Score: ${vuln.score} | Affected: ${vuln.affectedDevices} devices`, 20, yPosition);
|
|
yPosition += 8;
|
|
});
|
|
|
|
// Add page break if needed
|
|
if (yPosition > pageHeight - 80) {
|
|
pdf.addPage();
|
|
yPosition = 20;
|
|
} else {
|
|
yPosition += 10;
|
|
}
|
|
|
|
// Top Vulnerable Software
|
|
pdf.setFontSize(16);
|
|
pdf.setFont('helvetica', 'bold');
|
|
pdf.text('Most Vulnerable Software', 20, yPosition);
|
|
yPosition += 10;
|
|
|
|
pdf.setFontSize(9);
|
|
pdf.setFont('helvetica', 'normal');
|
|
|
|
topSoftware.slice(0, 10).forEach((software, index) => {
|
|
if (yPosition > pageHeight - 30) {
|
|
pdf.addPage();
|
|
yPosition = 20;
|
|
}
|
|
|
|
pdf.text(`${index + 1}. ${software.softwareName}`, 20, yPosition);
|
|
yPosition += 5;
|
|
pdf.text(` ${software.vulnerableInstances}/${software.totalInstances} instances vulnerable | ${software.totalCves} CVEs`, 20, yPosition);
|
|
yPosition += 8;
|
|
});
|
|
|
|
// Footer
|
|
const totalPages = pdf.internal.pages.length - 1;
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
pdf.setPage(i);
|
|
pdf.setFontSize(8);
|
|
pdf.text(`Page ${i} of ${totalPages}`, pageWidth - 30, pageHeight - 10);
|
|
pdf.text('Confidential - Internal Use Only', 20, pageHeight - 10);
|
|
}
|
|
|
|
// Save the PDF
|
|
const filename = `compliance-report-${format(new Date(), 'yyyy-MM-dd')}.pdf`;
|
|
pdf.save(filename);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to export PDF:', error);
|
|
} finally {
|
|
setExporting(false);
|
|
}
|
|
};
|
|
|
|
const getSeverityColor = (severity: string) => {
|
|
switch (severity?.toLowerCase()) {
|
|
case 'critical': return 'error';
|
|
case 'high': return 'warning';
|
|
case 'medium': return 'info';
|
|
case 'low': return 'success';
|
|
default: return 'default';
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="60vh">
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!summary) {
|
|
return (
|
|
<Box p={4}>
|
|
<Alert severity="error">Failed to load reporting data. Please try again later.</Alert>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box p={4} ref={reportRef}>
|
|
<Box display="flex" justifyContent="space-between" alignItems="center" mb={4}>
|
|
<Box>
|
|
<Typography variant="h4" gutterBottom>
|
|
<AssessmentIcon sx={{ mr: 2, verticalAlign: 'middle' }} />
|
|
Compliance Reporting
|
|
</Typography>
|
|
<Typography variant="subtitle1" color="text.secondary">
|
|
Comprehensive security compliance overview and vulnerability reporting
|
|
</Typography>
|
|
</Box>
|
|
<Button
|
|
variant="contained"
|
|
startIcon={exporting ? <CircularProgress size={20} /> : <PdfIcon />}
|
|
onClick={exportToPDF}
|
|
disabled={exporting}
|
|
size="large"
|
|
>
|
|
{exporting ? 'Generating...' : 'Export PDF'}
|
|
</Button>
|
|
</Box>
|
|
|
|
{/* Executive Summary Cards */}
|
|
<Grid container spacing={3} sx={{ mb: 4 }}>
|
|
<Grid size={{ xs: 12, md: 3 }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Box display="flex" alignItems="center" mb={2}>
|
|
<DevicesIcon color="primary" sx={{ mr: 1 }} />
|
|
<Typography variant="h6">Devices</Typography>
|
|
</Box>
|
|
<Typography variant="h3" color="primary">
|
|
{summary.totalDevices}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{summary.vulnerableDevices} vulnerable ({((summary.vulnerableDevices / summary.totalDevices) * 100).toFixed(1)}%)
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
|
|
<Grid size={{ xs: 12, md: 3 }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Box display="flex" alignItems="center" mb={2}>
|
|
<SecurityIcon color="error" sx={{ mr: 1 }} />
|
|
<Typography variant="h6">Vulnerabilities</Typography>
|
|
</Box>
|
|
<Typography variant="h3" color="error">
|
|
{summary.totalVulnerabilities}
|
|
</Typography>
|
|
<Box display="flex" gap={1} mt={1} flexWrap="wrap">
|
|
<Chip label={`Critical: ${summary.criticalVulns}`} color="error" size="small" />
|
|
<Chip label={`High: ${summary.highVulns}`} color="warning" size="small" />
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
|
|
<Grid size={{ xs: 12, md: 3 }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Box display="flex" alignItems="center" mb={2}>
|
|
<AppsIcon color="info" sx={{ mr: 1 }} />
|
|
<Typography variant="h6">Software</Typography>
|
|
</Box>
|
|
<Typography variant="h3" color="info">
|
|
{summary.totalSoftware}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{summary.vulnerableSoftware} with vulnerabilities
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
|
|
<Grid size={{ xs: 12, md: 3 }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" mb={2}>Risk Level</Typography>
|
|
<Typography
|
|
variant="h3"
|
|
color={summary.criticalVulns > 0 ? "error" : summary.highVulns > 10 ? "warning" : "success"}
|
|
>
|
|
{summary.criticalVulns > 0 ? "HIGH" : summary.highVulns > 10 ? "MEDIUM" : "LOW"}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Last updated: {format(new Date(summary.lastUpdated), 'PPp')}
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
{/* Detailed Reports */}
|
|
<Grid container spacing={3}>
|
|
<Grid size={{ xs: 12, lg: 6 }}>
|
|
<Paper sx={{ p: 3, height: 'fit-content' }}>
|
|
<Typography variant="h6" mb={3}>
|
|
<SecurityIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
|
Top Critical Vulnerabilities
|
|
</Typography>
|
|
<TableContainer>
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell><strong>CVE ID</strong></TableCell>
|
|
<TableCell><strong>Severity</strong></TableCell>
|
|
<TableCell align="right"><strong>Score</strong></TableCell>
|
|
<TableCell align="right"><strong>Devices</strong></TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{topVulns.slice(0, 8).map((vuln) => (
|
|
<TableRow key={vuln.cveId} hover>
|
|
<TableCell>
|
|
<Typography variant="body2" fontFamily="monospace">
|
|
{vuln.cveId}
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
{vuln.title.length > 40 ? `${vuln.title.substring(0, 40)}...` : vuln.title}
|
|
</Typography>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Chip
|
|
label={vuln.severity}
|
|
color={getSeverityColor(vuln.severity) as "error" | "warning" | "info" | "success" | "default"}
|
|
size="small"
|
|
/>
|
|
</TableCell>
|
|
<TableCell align="right">
|
|
<Typography fontWeight="bold">
|
|
{vuln.score}
|
|
</Typography>
|
|
</TableCell>
|
|
<TableCell align="right">
|
|
{vuln.affectedDevices}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
</Paper>
|
|
</Grid>
|
|
|
|
<Grid size={{ xs: 12, lg: 6 }}>
|
|
<Paper sx={{ p: 3, height: 'fit-content' }}>
|
|
<Typography variant="h6" mb={3}>
|
|
<AppsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
|
Most Vulnerable Software
|
|
</Typography>
|
|
<TableContainer>
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell><strong>Software</strong></TableCell>
|
|
<TableCell align="center"><strong>Vulnerable</strong></TableCell>
|
|
<TableCell align="center"><strong>CVEs</strong></TableCell>
|
|
<TableCell align="center"><strong>Risk</strong></TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{topSoftware.slice(0, 8).map((software) => {
|
|
const riskLevel = software.vulnerableInstances / software.totalInstances;
|
|
return (
|
|
<TableRow key={software.softwareName} hover>
|
|
<TableCell>
|
|
<Typography variant="body2">
|
|
{software.softwareName}
|
|
</Typography>
|
|
</TableCell>
|
|
<TableCell align="center">
|
|
<Typography variant="body2">
|
|
{software.vulnerableInstances}/{software.totalInstances}
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
({((riskLevel) * 100).toFixed(0)}%)
|
|
</Typography>
|
|
</TableCell>
|
|
<TableCell align="center">
|
|
<Typography fontWeight="bold" color="error">
|
|
{software.totalCves}
|
|
</Typography>
|
|
</TableCell>
|
|
<TableCell align="center">
|
|
<Chip
|
|
label={riskLevel > 0.5 ? "HIGH" : riskLevel > 0.2 ? "MEDIUM" : "LOW"}
|
|
color={riskLevel > 0.5 ? "error" : riskLevel > 0.2 ? "warning" : "success"}
|
|
size="small"
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
</Paper>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
<Box mt={4}>
|
|
<Divider />
|
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 2, display: 'block' }}>
|
|
This report contains confidential security information. Distribution should be limited to authorized personnel only.
|
|
Report generated on {format(new Date(), 'PPP')} from data last updated {format(new Date(summary.lastUpdated), 'PPp')}.
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
} |