From 3da26587f9d0653f895bc50c1e83b3930cb1e18c Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Mon, 24 Nov 2025 10:12:02 +0000 Subject: [PATCH] Display accurate real-time attack statistics on the dashboard Introduce a new API endpoint to fetch aggregated dashboard statistics, replacing raw data retrieval with pre-calculated metrics for improved performance and accuracy in the live dashboard view. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: d36a1b00-13ef-4857-b703-6096a3bd4601 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/x5P9dcJ --- client/src/pages/DashboardLive.tsx | 66 ++++++++++-------------- server/routes.ts | 11 ++++ server/storage.ts | 80 ++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 39 deletions(-) diff --git a/client/src/pages/DashboardLive.tsx b/client/src/pages/DashboardLive.tsx index 60a03e8..270d064 100644 --- a/client/src/pages/DashboardLive.tsx +++ b/client/src/pages/DashboardLive.tsx @@ -7,58 +7,46 @@ 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; + attacksByType: Record; + recentDetections: Detection[]; +} + export default function DashboardLive() { - // Dati ultimi 3 giorni - const { data: detections = [], isLoading: loadingDetections } = useQuery({ - queryKey: ["/api/detections"], + // Fetch aggregated stats from analytics (ultimi 72h = 3 giorni) + const { data: stats, isLoading } = useQuery({ + queryKey: ["/api/dashboard/live?hours=72"], refetchInterval: 10000, // Aggiorna ogni 10s }); - const { data: logs = [], isLoading: loadingLogs } = useQuery({ - queryKey: ["/api/logs?limit=5000"], - refetchInterval: 10000, - }); - - const isLoading = loadingDetections || loadingLogs; - - // Calcola metriche - const totalTraffic = logs.length; - const totalAttacks = detections.length; + // 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 normalTraffic = totalTraffic - totalAttacks; + const detections = stats?.recentDetections || []; const blockedAttacks = detections.filter(d => d.blocked).length; - // Aggrega traffico per paese - const trafficByCountry: Record = {}; - - detections.forEach(det => { - const country = det.country || "Unknown"; - if (!trafficByCountry[country]) { - trafficByCountry[country] = { - normal: 0, - attacks: 0, - flag: getFlag(det.country, det.countryCode) - }; - } - trafficByCountry[country].attacks++; - }); + // Usa dati aggregati giĆ  calcolati dal backend + const attacksByCountry = stats?.attacksByCountry || {}; + const attacksByType = stats?.attacksByType || {}; - const countryChartData = Object.entries(trafficByCountry) - .map(([name, data]) => ({ - name: `${data.flag} ${name}`, - attacks: data.attacks, - normal: data.normal, + 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); - // Aggrega attacchi per tipo - const attacksByType: Record = {}; - detections.forEach(det => { - attacksByType[det.anomalyType] = (attacksByType[det.anomalyType] || 0) + 1; - }); - const typeChartData = Object.entries(attacksByType).map(([name, value]) => ({ name: name.replace('_', ' ').toUpperCase(), value, diff --git a/server/routes.ts b/server/routes.ts index 608b86e..7d7357e 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -70,6 +70,17 @@ export async function registerRoutes(app: Express): Promise { } }); + app.get("/api/dashboard/live", async (req, res) => { + try { + const hours = parseInt(req.query.hours as string) || 72; + const stats = await storage.getLiveDashboardStats(hours); + res.json(stats); + } catch (error) { + console.error('[DB ERROR] Failed to fetch dashboard stats:', error); + res.status(500).json({ error: "Failed to fetch dashboard stats" }); + } + }); + app.get("/api/detections/unblocked", async (req, res) => { try { const detections = await storage.getUnblockedDetections(); diff --git a/server/storage.ts b/server/storage.ts index c327229..e6c5b63 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -56,6 +56,18 @@ export interface IStorage { // Network Analytics getAnalyticsByDateRange(startDate: Date, endDate: Date, hourly?: boolean): Promise; getRecentAnalytics(days: number, hourly?: boolean): Promise; + + // Dashboard Live Stats + getLiveDashboardStats(hours: number): Promise<{ + totalPackets: number; + attackPackets: number; + normalPackets: number; + uniqueIps: number; + attackUniqueIps: number; + attacksByCountry: Record; + attacksByType: Record; + recentDetections: Detection[]; + }>; // System testConnection(): Promise; @@ -273,6 +285,74 @@ export class DatabaseStorage implements IStorage { return this.getAnalyticsByDateRange(startDate, new Date(), hourly); } + async getLiveDashboardStats(hours: number = 72) { + const cutoffDate = new Date(); + cutoffDate.setHours(cutoffDate.getHours() - hours); + + const analytics = await db + .select() + .from(networkAnalytics) + .where( + and( + gte(networkAnalytics.date, cutoffDate), + sql`hour IS NOT NULL` + ) + ) + .orderBy(desc(networkAnalytics.date), desc(networkAnalytics.hour)); + + let totalPackets = 0; + let attackPackets = 0; + let normalPackets = 0; + let uniqueIps = 0; + let attackUniqueIps = 0; + const attacksByCountry: Record = {}; + const attacksByType: Record = {}; + + analytics.forEach(record => { + totalPackets += record.totalPackets || 0; + attackPackets += record.attackPackets || 0; + normalPackets += record.normalPackets || 0; + uniqueIps += record.uniqueIps || 0; + attackUniqueIps += record.attackUniqueIps || 0; + + if (record.attacksByCountry) { + try { + const countries = JSON.parse(record.attacksByCountry); + Object.entries(countries).forEach(([country, count]) => { + attacksByCountry[country] = (attacksByCountry[country] || 0) + (count as number); + }); + } catch {} + } + + if (record.attacksByType) { + try { + const types = JSON.parse(record.attacksByType); + Object.entries(types).forEach(([type, count]) => { + attacksByType[type] = (attacksByType[type] || 0) + (count as number); + }); + } catch {} + } + }); + + const recentDetections = await db + .select() + .from(detections) + .where(gte(detections.detectedAt, cutoffDate)) + .orderBy(desc(detections.detectedAt)) + .limit(100); + + return { + totalPackets, + attackPackets, + normalPackets, + uniqueIps, + attackUniqueIps, + attacksByCountry, + attacksByType, + recentDetections, + }; + } + async testConnection(): Promise { try { await db.execute(sql`SELECT 1`);