Initial commit - frontend
This commit is contained in:
32
src/app/(protected)/admin/devices/page.tsx
Normal file
32
src/app/(protected)/admin/devices/page.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
// src/app/admin/devices/page.tsx
|
||||
import { cookies } from 'next/headers';
|
||||
import { requireAuthOrRedirect } from '@/lib/authServer';
|
||||
import axiosServer from '@/lib/axiosServer';
|
||||
import DeviceTableSection from '@/components/admin/DeviceTableSection';
|
||||
|
||||
interface DeviceDTO {
|
||||
deviceId: number;
|
||||
hostname: string;
|
||||
lastCheckedIn: string;
|
||||
clientId: number;
|
||||
clientName: string;
|
||||
clientIdentifier: string;
|
||||
}
|
||||
|
||||
|
||||
export default async function DeviceManagementPage() {
|
||||
const user = await requireAuthOrRedirect();
|
||||
const cookieStore = cookies();
|
||||
// @ts-expect-error cookies() isn't really async
|
||||
const token = cookieStore.get('authToken')?.value;
|
||||
|
||||
const res = await axiosServer.get('/admin/devices', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const devices: DeviceDTO[] = res.data;
|
||||
|
||||
return <DeviceTableSection initialDevices={devices} />;
|
||||
}
|
||||
14
src/app/(protected)/admin/settings/page.tsx
Normal file
14
src/app/(protected)/admin/settings/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
// src/app/admin/settings/page.tsx
|
||||
|
||||
'use client';
|
||||
|
||||
import AdminControlsPanel from '@/components/admin/AdminControlsPanel';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export default function SettingsPage() {
|
||||
return (
|
||||
<Box sx={{ p: 4 }}>
|
||||
<AdminControlsPanel />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
36
src/app/(protected)/admin/users/page.tsx
Normal file
36
src/app/(protected)/admin/users/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
// src/app/admin/users/page.tsx
|
||||
import { cookies } from 'next/headers';
|
||||
import { requireAuthOrRedirect } from '@/lib/authServer';
|
||||
import axiosServer from '@/lib/axiosServer';
|
||||
import UserTableSection from '@/components/admin/UserTableSection';
|
||||
|
||||
|
||||
interface UserDTO {
|
||||
id: number;
|
||||
username: string;
|
||||
displayName: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
role: string;
|
||||
clientName: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export default async function UserManagementPage() {
|
||||
const user = await requireAuthOrRedirect();
|
||||
const cookieStore = cookies();
|
||||
// @ts-expect-error - cookies() is not actually async, type is misleading
|
||||
const token = cookieStore.get('authToken')?.value;
|
||||
|
||||
|
||||
const res = await axiosServer.get('/admin/users', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`, // token is guaranteed to exist
|
||||
},
|
||||
});
|
||||
|
||||
const users: UserDTO[] = res.data;
|
||||
|
||||
return <UserTableSection initialUsers={users} />;
|
||||
}
|
||||
Reference in New Issue
Block a user