ids.alfacom.it/client/src/pages/DashboardLive.tsx
marco370 4229c54d62 Improve system accuracy and router configuration for security monitoring
Fixes type mismatches in API responses, updates router configuration to use correct REST API ports, and refactors statistics calculation for improved accuracy.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 2601dca2-8641-4d91-9722-c30ebbbf23af
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/5aJYCET
2026-02-16 08:14:39 +00:00

298 lines
11 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Activity, Globe, Shield, TrendingUp, AlertTriangle } from "lucide-react";
import { AreaChart, Area, BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
import type { Detection, NetworkLog } from "@shared/schema";
import { getFlag } from "@/lib/country-flags";
import { format } from "date-fns";
interface DashboardStats {
totalPackets: number;
attackPackets: number;
normalPackets: number;
uniqueIps: number;
attackUniqueIps: number;
attacksByCountry: Record<string, number>;
attacksByType: Record<string, number>;
recentDetections: Detection[];
blockedCount: number;
}
export default function DashboardLive() {
// Fetch aggregated stats from analytics (ultimi 72h = 3 giorni)
const { data: stats, isLoading } = useQuery<DashboardStats>({
queryKey: ["/api/dashboard/live?hours=72"],
refetchInterval: 10000, // Aggiorna ogni 10s
});
// Usa dati aggregati precisi
const totalTraffic = stats?.totalPackets || 0;
const totalAttacks = stats?.attackPackets || 0;
const normalTraffic = stats?.normalPackets || 0;
const attackPercentage = totalTraffic > 0 ? ((totalAttacks / totalTraffic) * 100).toFixed(2) : "0";
const detections = stats?.recentDetections || [];
const blockedAttacks = stats?.blockedCount || 0;
// Usa dati aggregati già calcolati dal backend
const attacksByCountry = stats?.attacksByCountry || {};
const attacksByType = stats?.attacksByType || {};
const countryChartData = Object.entries(attacksByCountry)
.map(([name, attacks]) => ({
name: `${getFlag(name, name.substring(0, 2))} ${name}`,
attacks,
normal: 0,
}))
.sort((a, b) => b.attacks - a.attacks)
.slice(0, 10);
const typeChartData = Object.entries(attacksByType).map(([name, value]) => ({
name: name.replace('_', ' ').toUpperCase(),
value,
}));
// Traffico normale vs attacchi (gauge data)
const trafficDistribution = [
{ name: 'Normal', value: normalTraffic, color: '#22c55e' },
{ name: 'Attacks', value: totalAttacks, color: '#ef4444' },
];
// Ultimi eventi (stream)
const recentEvents = [...detections]
.sort((a, b) => new Date(b.detectedAt).getTime() - new Date(a.detectedAt).getTime())
.slice(0, 20);
const COLORS = ['#ef4444', '#f97316', '#f59e0b', '#eab308', '#84cc16'];
return (
<div className="flex flex-col gap-6 p-6" data-testid="page-dashboard-live">
{/* Header */}
<div>
<h1 className="text-3xl font-semibold flex items-center gap-2" data-testid="text-page-title">
<Activity className="h-8 w-8" />
Dashboard Live
</h1>
<p className="text-muted-foreground" data-testid="text-page-subtitle">
Monitoraggio real-time (ultimi 3 giorni)
</p>
</div>
{isLoading && (
<div className="text-center py-8" data-testid="text-loading">
Caricamento dati...
</div>
)}
{!isLoading && (
<>
{/* KPI Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card data-testid="card-total-traffic">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
Traffico Totale
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold" data-testid="text-total-traffic">
{totalTraffic.toLocaleString()}
</div>
<p className="text-xs text-muted-foreground mt-1">pacchetti</p>
</CardContent>
</Card>
<Card data-testid="card-normal-traffic">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
Traffico Normale
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-green-600" data-testid="text-normal-traffic">
{normalTraffic.toLocaleString()}
</div>
<p className="text-xs text-muted-foreground mt-1">
{(100 - parseFloat(attackPercentage)).toFixed(1)}% del totale
</p>
</CardContent>
</Card>
<Card data-testid="card-attacks">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
Attacchi Rilevati
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-red-600" data-testid="text-attacks">
{totalAttacks}
</div>
<p className="text-xs text-muted-foreground mt-1">
{attackPercentage}% del traffico
</p>
</CardContent>
</Card>
<Card data-testid="card-blocked">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
IP Bloccati
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-orange-600" data-testid="text-blocked">
{blockedAttacks}
</div>
<p className="text-xs text-muted-foreground mt-1">
{totalAttacks > 0 ? ((blockedAttacks / totalAttacks) * 100).toFixed(1) : 0}% degli attacchi
</p>
</CardContent>
</Card>
</div>
{/* Charts Row 1 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Traffic Distribution (Pie) */}
<Card data-testid="card-distribution">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
Distribuzione Traffico
</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={trafficDistribution}
cx="50%"
cy="50%"
labelLine={false}
label={(entry) => `${entry.name}: ${entry.value}`}
outerRadius={100}
fill="#8884d8"
dataKey="value"
>
{trafficDistribution.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* Attacks by Type (Pie) */}
<Card data-testid="card-attack-types">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5" />
Tipi di Attacco
</CardTitle>
</CardHeader>
<CardContent>
{typeChartData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={typeChartData}
cx="50%"
cy="50%"
labelLine={false}
label={(entry) => `${entry.name}: ${entry.value}`}
outerRadius={100}
fill="#8884d8"
dataKey="value"
>
{typeChartData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
) : (
<div className="text-center py-20 text-muted-foreground">
Nessun attacco rilevato
</div>
)}
</CardContent>
</Card>
</div>
{/* Top Countries (Bar Chart) */}
<Card data-testid="card-countries">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Globe className="h-5 w-5" />
Top 10 Paesi Attaccanti
</CardTitle>
</CardHeader>
<CardContent>
{countryChartData.length > 0 ? (
<ResponsiveContainer width="100%" height={400}>
<BarChart data={countryChartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="attacks" fill="#ef4444" name="Attacchi" />
</BarChart>
</ResponsiveContainer>
) : (
<div className="text-center py-20 text-muted-foreground">
Nessun dato disponibile
</div>
)}
</CardContent>
</Card>
{/* Real-time Event Stream */}
<Card data-testid="card-event-stream">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" />
Stream Eventi Recenti
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-96 overflow-y-auto">
{recentEvents.map(event => (
<div
key={event.id}
className="flex items-center justify-between p-3 rounded-lg border hover-elevate"
data-testid={`event-${event.id}`}
>
<div className="flex items-center gap-3">
{event.countryCode && (
<span className="text-xl">
{getFlag(event.country, event.countryCode)}
</span>
)}
<div>
<code className="font-mono font-semibold">{event.sourceIp}</code>
<p className="text-xs text-muted-foreground">
{event.anomalyType.replace('_', ' ')} {format(new Date(event.detectedAt), "HH:mm:ss")}
</p>
</div>
</div>
<Badge variant={event.blocked ? "destructive" : "secondary"}>
{event.blocked ? "Bloccato" : "Attivo"}
</Badge>
</div>
))}
</div>
</CardContent>
</Card>
</>
)}
</div>
);
}