Introduces a new sidebar component in `client/src/App.tsx` for navigation, along with new pages for Dashboard, Detections, and Routers. The backend in `server/routes.ts` is updated to include API endpoints for managing routers, fetching network logs, and retrieving detection data. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 99f17f6a-6021-4354-9517-5610b878cb21 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/c9ITWqD
273 lines
12 KiB
TypeScript
273 lines
12 KiB
TypeScript
import { useQuery } from "@tanstack/react-query";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Activity, Shield, Server, AlertTriangle, CheckCircle2, TrendingUp } from "lucide-react";
|
|
import { format } from "date-fns";
|
|
import type { Detection, Router, TrainingHistory } from "@shared/schema";
|
|
|
|
interface StatsResponse {
|
|
routers: { total: number; enabled: number };
|
|
detections: { total: number; blocked: number; critical: number; high: number };
|
|
logs: { recent: number };
|
|
whitelist: { total: number };
|
|
latestTraining: TrainingHistory | null;
|
|
}
|
|
|
|
export default function Dashboard() {
|
|
const { data: stats } = useQuery<StatsResponse>({
|
|
queryKey: ["/api/stats"],
|
|
refetchInterval: 10000, // Refresh every 10s
|
|
});
|
|
|
|
const { data: recentDetections } = useQuery<Detection[]>({
|
|
queryKey: ["/api/detections"],
|
|
refetchInterval: 5000, // Refresh every 5s
|
|
});
|
|
|
|
const { data: routers } = useQuery<Router[]>({
|
|
queryKey: ["/api/routers"],
|
|
});
|
|
|
|
const getRiskBadge = (riskScore: string) => {
|
|
const score = parseFloat(riskScore);
|
|
if (score >= 85) return <Badge variant="destructive" data-testid={`badge-risk-critical`}>CRITICO</Badge>;
|
|
if (score >= 70) return <Badge className="bg-orange-500" data-testid={`badge-risk-high`}>ALTO</Badge>;
|
|
if (score >= 60) return <Badge className="bg-yellow-500" data-testid={`badge-risk-medium`}>MEDIO</Badge>;
|
|
if (score >= 40) return <Badge variant="secondary" data-testid={`badge-risk-low`}>BASSO</Badge>;
|
|
return <Badge variant="outline" data-testid={`badge-risk-normal`}>NORMALE</Badge>;
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 p-6" data-testid="page-dashboard">
|
|
<div>
|
|
<h1 className="text-3xl font-semibold" data-testid="text-dashboard-title">IDS Dashboard</h1>
|
|
<p className="text-muted-foreground" data-testid="text-dashboard-subtitle">
|
|
Monitoring real-time del sistema di rilevamento intrusioni
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<Card data-testid="card-routers">
|
|
<CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Router Attivi</CardTitle>
|
|
<Server className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-semibold" data-testid="text-routers-count">
|
|
{stats?.routers.enabled || 0}/{stats?.routers.total || 0}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Router configurati
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card data-testid="card-detections">
|
|
<CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Rilevamenti</CardTitle>
|
|
<AlertTriangle className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-semibold" data-testid="text-detections-count">
|
|
{stats?.detections.total || 0}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{stats?.detections.critical || 0} critici, {stats?.detections.high || 0} alti
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card data-testid="card-blocked">
|
|
<CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">IP Bloccati</CardTitle>
|
|
<Shield className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-semibold" data-testid="text-blocked-count">
|
|
{stats?.detections.blocked || 0}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
IP attualmente bloccati
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card data-testid="card-logs">
|
|
<CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Log Recenti</CardTitle>
|
|
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-semibold" data-testid="text-logs-count">
|
|
{stats?.logs.recent || 0}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Ultimi 1000 log analizzati
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Training Status */}
|
|
{stats?.latestTraining && (
|
|
<Card data-testid="card-training">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<TrendingUp className="h-5 w-5" />
|
|
Ultimo Training
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Data</p>
|
|
<p className="font-medium" data-testid="text-training-date">
|
|
{format(new Date(stats.latestTraining.trainedAt), "dd/MM/yyyy HH:mm")}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Record</p>
|
|
<p className="font-medium" data-testid="text-training-records">
|
|
{stats.latestTraining.recordsProcessed.toLocaleString()}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Feature</p>
|
|
<p className="font-medium" data-testid="text-training-features">
|
|
{stats.latestTraining.featuresCount}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Stato</p>
|
|
<Badge variant={stats.latestTraining.status === 'success' ? 'default' : 'destructive'} data-testid="badge-training-status">
|
|
{stats.latestTraining.status}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
{stats.latestTraining.notes && (
|
|
<p className="text-sm text-muted-foreground mt-2" data-testid="text-training-notes">
|
|
{stats.latestTraining.notes}
|
|
</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Recent Detections */}
|
|
<Card data-testid="card-recent-detections">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span className="flex items-center gap-2">
|
|
<AlertTriangle className="h-5 w-5" />
|
|
Rilevamenti Recenti
|
|
</span>
|
|
<Button variant="outline" size="sm" asChild data-testid="button-view-all-detections">
|
|
<a href="/detections">Vedi Tutti</a>
|
|
</Button>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{recentDetections && recentDetections.length > 0 ? (
|
|
recentDetections.slice(0, 5).map((detection) => (
|
|
<div
|
|
key={detection.id}
|
|
className="flex items-center justify-between p-3 rounded-lg border hover-elevate"
|
|
data-testid={`detection-item-${detection.sourceIp}`}
|
|
>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2">
|
|
<code className="font-mono font-semibold" data-testid={`text-ip-${detection.sourceIp}`}>
|
|
{detection.sourceIp}
|
|
</code>
|
|
{getRiskBadge(detection.riskScore)}
|
|
<Badge variant="outline" data-testid={`badge-type-${detection.sourceIp}`}>
|
|
{detection.anomalyType}
|
|
</Badge>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground truncate mt-1" data-testid={`text-reason-${detection.sourceIp}`}>
|
|
{detection.reason}
|
|
</p>
|
|
<div className="flex items-center gap-4 mt-1 text-xs text-muted-foreground">
|
|
<span data-testid={`text-score-${detection.sourceIp}`}>
|
|
Risk: {parseFloat(detection.riskScore).toFixed(1)}
|
|
</span>
|
|
<span data-testid={`text-confidence-${detection.sourceIp}`}>
|
|
Confidence: {parseFloat(detection.confidence).toFixed(1)}%
|
|
</span>
|
|
<span data-testid={`text-logs-${detection.sourceIp}`}>
|
|
{detection.logCount} log
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 ml-4">
|
|
{detection.blocked ? (
|
|
<Badge variant="destructive" className="flex items-center gap-1" data-testid={`badge-blocked-${detection.sourceIp}`}>
|
|
<Shield className="h-3 w-3" />
|
|
Bloccato
|
|
</Badge>
|
|
) : (
|
|
<Badge variant="outline" data-testid={`badge-active-${detection.sourceIp}`}>
|
|
Attivo
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<div className="text-center py-8 text-muted-foreground" data-testid="text-no-detections">
|
|
<CheckCircle2 className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
|
<p>Nessun rilevamento recente</p>
|
|
<p className="text-sm">Il sistema sta monitorando il traffico</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Routers Status */}
|
|
{routers && routers.length > 0 && (
|
|
<Card data-testid="card-routers-status">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Server className="h-5 w-5" />
|
|
Router MikroTik
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{routers.map((router) => (
|
|
<div
|
|
key={router.id}
|
|
className="p-4 rounded-lg border hover-elevate"
|
|
data-testid={`router-card-${router.name}`}
|
|
>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="font-medium" data-testid={`text-router-name-${router.name}`}>{router.name}</p>
|
|
<Badge
|
|
variant={router.enabled ? "default" : "secondary"}
|
|
data-testid={`badge-router-status-${router.name}`}
|
|
>
|
|
{router.enabled ? "Attivo" : "Disabilitato"}
|
|
</Badge>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground font-mono" data-testid={`text-router-ip-${router.name}`}>
|
|
{router.ipAddress}:{router.apiPort}
|
|
</p>
|
|
{router.lastSync && (
|
|
<p className="text-xs text-muted-foreground mt-1" data-testid={`text-router-sync-${router.name}`}>
|
|
Ultima sync: {format(new Date(router.lastSync), "HH:mm:ss")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|