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:
parent
78f21d25a0
commit
3da26587f9
@ -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 }> = {};
|
||||
// Usa dati aggregati già calcolati dal backend
|
||||
const attacksByCountry = stats?.attacksByCountry || {};
|
||||
const attacksByType = stats?.attacksByType || {};
|
||||
|
||||
detections.forEach(det => {
|
||||
const country = det.country || "Unknown";
|
||||
if (!trafficByCountry[country]) {
|
||||
trafficByCountry[country] = {
|
||||
const countryChartData = Object.entries(attacksByCountry)
|
||||
.map(([name, attacks]) => ({
|
||||
name: `${getFlag(name, name.substring(0, 2))} ${name}`,
|
||||
attacks,
|
||||
normal: 0,
|
||||
attacks: 0,
|
||||
flag: getFlag(det.country, det.countryCode)
|
||||
};
|
||||
}
|
||||
trafficByCountry[country].attacks++;
|
||||
});
|
||||
|
||||
const countryChartData = Object.entries(trafficByCountry)
|
||||
.map(([name, data]) => ({
|
||||
name: `${data.flag} ${name}`,
|
||||
attacks: data.attacks,
|
||||
normal: data.normal,
|
||||
}))
|
||||
.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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -57,6 +57,18 @@ export interface IStorage {
|
||||
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`);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user