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
This commit is contained in:
marco370 2025-11-24 10:12:02 +00:00
parent 78f21d25a0
commit 3da26587f9
3 changed files with 118 additions and 39 deletions

View File

@ -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<string, number>;
attacksByType: Record<string, number>;
recentDetections: Detection[];
}
export default function DashboardLive() {
// Dati ultimi 3 giorni
const { data: detections = [], isLoading: loadingDetections } = useQuery<Detection[]>({
queryKey: ["/api/detections"],
// 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
});
const { data: logs = [], isLoading: loadingLogs } = useQuery<NetworkLog[]>({
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<string, { normal: number; attacks: number; flag: string }> = {};
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<string, number> = {};
detections.forEach(det => {
attacksByType[det.anomalyType] = (attacksByType[det.anomalyType] || 0) + 1;
});
const typeChartData = Object.entries(attacksByType).map(([name, value]) => ({
name: name.replace('_', ' ').toUpperCase(),
value,

View File

@ -70,6 +70,17 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
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();

View File

@ -56,6 +56,18 @@ export interface IStorage {
// Network Analytics
getAnalyticsByDateRange(startDate: Date, endDate: Date, hourly?: boolean): Promise<NetworkAnalytics[]>;
getRecentAnalytics(days: number, hourly?: boolean): Promise<NetworkAnalytics[]>;
// Dashboard Live Stats
getLiveDashboardStats(hours: number): Promise<{
totalPackets: number;
attackPackets: number;
normalPackets: number;
uniqueIps: number;
attackUniqueIps: number;
attacksByCountry: Record<string, number>;
attacksByType: Record<string, number>;
recentDetections: Detection[];
}>;
// System
testConnection(): Promise<boolean>;
@ -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<string, number> = {};
const attacksByType: Record<string, number> = {};
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<boolean> {
try {
await db.execute(sql`SELECT 1`);