Adding reporting, and some fake/dummy routes locally for testing functionality.
All checks were successful
Deploy Frontend / deploy (push) Successful in 22s

This commit is contained in:
Bailey Taylor
2025-10-29 08:20:06 +08:00
parent 271d454bb3
commit 1d882fbfee
7 changed files with 865 additions and 0 deletions

View File

@@ -0,0 +1,483 @@
'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();
}, [fetchReportingData]);
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>
);
}