Initial commit - frontend
This commit is contained in:
142
src/app/(protected)/admin/statistics/page.tsx
Normal file
142
src/app/(protected)/admin/statistics/page.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user