diff --git a/backend/routes/incidents.py b/backend/routes/incidents.py index 58e5ca7..d9b6dc8 100644 --- a/backend/routes/incidents.py +++ b/backend/routes/incidents.py @@ -10,6 +10,14 @@ from ..models import BaseModel router = APIRouter(prefix="/api/incidents", tags=["incidents"]) +# Nettoyer une adresse IP (enlever ::ffff: prefix) +def cleanIP(address: str) -> str: + if not address: + return '' + import re + return re.sub(r'^::ffff:', '', address, flags=re.IGNORECASE) + + @router.get("/clusters") async def get_incident_clusters( hours: int = Query(24, ge=1, le=168, description="Fenêtre temporelle en heures"), @@ -25,7 +33,7 @@ async def get_incident_clusters( - Pattern temporel """ try: - # Cluster par subnet /24 + # Cluster par subnet /24 avec une IP exemple cluster_query = """ WITH subnet_groups AS ( SELECT @@ -42,7 +50,8 @@ async def get_incident_clusters( argMax(country_code, detected_at) AS country_code, argMax(asn_number, detected_at) AS asn_number, argMax(threat_level, detected_at) AS threat_level, - avg(anomaly_score) AS avg_score + avg(anomaly_score) AS avg_score, + any(src_ip) AS sample_ip FROM ml_detected_anomalies WHERE detected_at >= now() - INTERVAL %(hours)s HOUR GROUP BY subnet @@ -58,7 +67,8 @@ async def get_incident_clusters( country_code, asn_number, threat_level, - avg_score + avg_score, + sample_ip FROM subnet_groups ORDER BY avg_score ASC, total_detections DESC LIMIT %(limit)s @@ -105,6 +115,7 @@ async def get_incident_clusters( "total_detections": row[1], "unique_ips": row[2], "subnet": row[0], + "sample_ip": cleanIP(row[10]) if row[10] else None, "ja4": row[5] or "", "primary_ua": "python-requests", "primary_target": "Unknown", diff --git a/frontend/src/components/IncidentsView.tsx b/frontend/src/components/IncidentsView.tsx index 8674a16..8efe815 100644 --- a/frontend/src/components/IncidentsView.tsx +++ b/frontend/src/components/IncidentsView.tsx @@ -9,6 +9,7 @@ interface IncidentCluster { total_detections: number; unique_ips: number; subnet?: string; + sample_ip?: string; ja4?: string; primary_ua?: string; primary_target?: string; @@ -112,6 +113,18 @@ export function IncidentsView() { return address.replace(/^::ffff:/i, ''); }; + // Générer une IP exemple depuis un subnet + const getSampleIP = (subnet: string): string => { + const clean = cleanIP(subnet); + const ipParts = clean.replace('/24', '').split('.'); + if (ipParts.length === 4) { + // Remplacer le dernier octet par 1 + ipParts[3] = '1'; + return ipParts.join('.'); + } + return cleanIP(subnet.split('/')[0]); + }; + if (loading) { return (
@@ -309,13 +322,13 @@ export function IncidentsView() {