143 lines
5.0 KiB
TypeScript
143 lines
5.0 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import { Card, CardContent, Typography, Grid, CircularProgress, Box } from '@mui/material';
|
|
import api from '@/lib/axios';
|
|
|
|
interface Stats {
|
|
total: number;
|
|
missing_title: number;
|
|
missing_severity: number;
|
|
missing_cvss_score: number;
|
|
missing_cvss_vector: number;
|
|
missing_references: number;
|
|
missing_published_date: number;
|
|
missing_description: number;
|
|
missing_cwe: number;
|
|
missing_cisa_kev: number;
|
|
missing_cert_notes: number;
|
|
missing_cert_alerts: number;
|
|
}
|
|
|
|
export default function CVEStatisticsPage() {
|
|
const [stats, setStats] = useState<Stats | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const fetchStats = async () => {
|
|
try {
|
|
const response = await api.get('/admin/statistics');
|
|
setStats(response.data);
|
|
} catch (error) {
|
|
console.error('Failed to fetch CVE stats:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchStats();
|
|
}, []);
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box display="flex" flexDirection="column" alignItems="center" justifyContent="center" mt={4}>
|
|
<CircularProgress sx={{ mb: 2 }} />
|
|
<Typography variant="body1">Fetching CVE statistics...</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!stats) {
|
|
return <Typography variant="h6">No data available</Typography>;
|
|
}
|
|
|
|
const {
|
|
total,
|
|
missing_title,
|
|
missing_severity,
|
|
missing_cvss_score,
|
|
missing_cvss_vector,
|
|
missing_references,
|
|
missing_published_date,
|
|
missing_description,
|
|
missing_cwe,
|
|
missing_cisa_kev,
|
|
missing_cert_notes,
|
|
missing_cert_alerts,
|
|
} = stats;
|
|
|
|
const complete = (value: number) => (total > 0 ? (((total - value) / total) * 100).toFixed(2) : '0');
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<Typography variant="h4" gutterBottom>
|
|
CVE Enrichment Statistics
|
|
</Typography>
|
|
|
|
<Grid container spacing={3}>
|
|
<StatCard label="Total CVEs" value={total} />
|
|
<StatCard label="Missing Title" value={missing_title} />
|
|
<StatCard label="Missing Severity" value={missing_severity} />
|
|
<StatCard label="Missing CVSS Score" value={missing_cvss_score} />
|
|
<StatCard label="Missing CVSS Vector" value={missing_cvss_vector} />
|
|
<StatCard label="Missing References" value={missing_references} />
|
|
<StatCard label="Missing Published Date" value={missing_published_date} />
|
|
<StatCard label="Missing Description" value={missing_description} />
|
|
<StatCard label="Missing CWE" value={missing_cwe} />
|
|
<StatCard label="Missing CISA KEV" value={missing_cisa_kev} />
|
|
<StatCard label="Missing CERT Notes" value={missing_cert_notes} />
|
|
<StatCard label="Missing CERT Alerts" value={missing_cert_alerts} />
|
|
</Grid>
|
|
|
|
<Typography variant="h5" gutterBottom sx={{ mt: 5 }}>
|
|
Completion Rates
|
|
</Typography>
|
|
|
|
<Grid container spacing={3}>
|
|
<StatCard label="Title Completion" value={Number(complete(missing_title))} isPercentage />
|
|
<StatCard label="Severity Completion" value={Number(complete(missing_severity))} isPercentage />
|
|
<StatCard label="CVSS Score Completion" value={Number(complete(missing_cvss_score))} isPercentage />
|
|
<StatCard label="CVSS Vector Completion" value={Number(complete(missing_cvss_vector))} isPercentage />
|
|
<StatCard label="References Completion" value={Number(complete(missing_references))} isPercentage />
|
|
<StatCard label="Published Date Completion" value={Number(complete(missing_published_date))} isPercentage />
|
|
<StatCard label="Description Completion" value={Number(complete(missing_description))} isPercentage />
|
|
<StatCard label="CWE Completion" value={Number(complete(missing_cwe))} isPercentage />
|
|
<StatCard label="CISA KEV Completion" value={Number(complete(missing_cisa_kev))} isPercentage />
|
|
<StatCard label="CERT Notes Completion" value={Number(complete(missing_cert_notes))} isPercentage />
|
|
<StatCard label="CERT Alerts Completion" value={Number(complete(missing_cert_alerts))} isPercentage />
|
|
</Grid>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface StatCardProps {
|
|
label: string;
|
|
value: number;
|
|
isPercentage?: boolean;
|
|
}
|
|
|
|
function StatCard({ label, value, isPercentage = false }: StatCardProps) {
|
|
let color: 'error' | 'warning' | 'success' | 'textPrimary' = 'textPrimary';
|
|
|
|
if (isPercentage) {
|
|
if (value >= 90) color = 'success';
|
|
else if (value >= 70) color = 'warning';
|
|
else color = 'error';
|
|
}
|
|
|
|
return (
|
|
<Grid>
|
|
<Card sx={{ borderRadius: 2, boxShadow: 3 }}>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
{label}
|
|
</Typography>
|
|
<Typography variant="h4" color={color}>
|
|
{isPercentage ? `${value.toFixed(2)}%` : value.toLocaleString()}
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
);
|
|
}
|