Adding reporting, and some fake/dummy routes locally for testing functionality.
All checks were successful
Deploy Frontend / deploy (push) Successful in 22s
All checks were successful
Deploy Frontend / deploy (push) Successful in 22s
This commit is contained in:
483
src/app/(protected)/reporting/page.tsx
Normal file
483
src/app/(protected)/reporting/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user