diff --git a/backend/routes/detections.py b/backend/routes/detections.py index 662e463..22e9097 100644 --- a/backend/routes/detections.py +++ b/backend/routes/detections.py @@ -20,7 +20,8 @@ async def get_detections( search: Optional[str] = Query(None, description="Recherche texte (IP, JA4, Host)"), sort_by: str = Query("detected_at", description="Trier par"), sort_order: str = Query("DESC", description="Ordre (ASC/DESC)"), - group_by_ip: bool = Query(False, description="Grouper par IP (first_seen/last_seen agrégés)") + group_by_ip: bool = Query(False, description="Grouper par IP (first_seen/last_seen agrégés)"), + score_type: Optional[str] = Query(None, description="Filtrer par type de score: BOT, REGLE, BOT_REGLE, SCORE") ): """ Récupère la liste des détections avec pagination et filtres @@ -51,7 +52,18 @@ async def get_detections( "(ilike(toString(src_ip), %(search)s) OR ilike(ja4, %(search)s) OR ilike(host, %(search)s))" ) params["search"] = f"%{search}%" - + + if score_type: + st = score_type.upper() + if st == "BOT": + where_clauses.append("threat_level = 'KNOWN_BOT'") + elif st == "REGLE": + where_clauses.append("threat_level = 'ANUBIS_DENY'") + elif st == "BOT_REGLE": + where_clauses.append("threat_level IN ('KNOWN_BOT', 'ANUBIS_DENY')") + elif st == "SCORE": + where_clauses.append("threat_level NOT IN ('KNOWN_BOT', 'ANUBIS_DENY')") + where_clause = " AND ".join(where_clauses) # Requête de comptage diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4a17b75..c7c9f9f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -302,6 +302,12 @@ function InvestigateRoute() { return ; } +/** Redirige /detections/ip/:ip → /investigation/:ip */ +function IpDetectionPageRedirect() { + const { ip } = useParams<{ ip: string }>(); + return ; +} + /** Redirige /investigation/ip/:ip → /investigation/:ip */ function IpInvestigationRedirect() { const { ip } = useParams<{ ip: string }>(); @@ -377,6 +383,7 @@ function MainContent({ counts: _counts }: { counts: AlertCounts | null }) { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 7753b46..597d741 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -140,6 +140,7 @@ export const detectionsApi = { sort_by?: string; sort_order?: string; group_by_ip?: boolean; + score_type?: string; }) => api.get('/detections', { params }), getDetails: (id: string) => api.get(`/detections/${encodeURIComponent(id)}`), diff --git a/frontend/src/components/DetectionsList.tsx b/frontend/src/components/DetectionsList.tsx index 72cbb33..11adae0 100644 --- a/frontend/src/components/DetectionsList.tsx +++ b/frontend/src/components/DetectionsList.tsx @@ -52,6 +52,7 @@ export function DetectionsList() { const search = searchParams.get('search') || undefined; const sortField = (searchParams.get('sort_by') || searchParams.get('sort') || 'detected_at') as SortField; const sortOrder = (searchParams.get('sort_order') || searchParams.get('order') || 'desc') as SortOrder; + const scoreType = searchParams.get('score_type') || undefined; const [groupByIP, setGroupByIP] = useState(true); @@ -63,6 +64,7 @@ export function DetectionsList() { sort_by: sortField, sort_order: sortOrder, group_by_ip: groupByIP, + score_type: scoreType, }); const [searchInput, setSearchInput] = useState(search || ''); @@ -468,7 +470,7 @@ export function DetectionsList() { {/* Filtres */}
-
+
- {(modelName || search || sortField) && ( + + + {(modelName || scoreType || search || sortField !== 'detected_at') && ( + + {open && ( +
+ {loading &&
Chargement…
} + {data && ( + <> + {/* Métriques */} +
+ + + {first && last && ( + sameDate ? ( + + ) : ( +
+

Période

+

{fmt(first)}

+

→ {fmt(last!)}

+
+ ) + )} +
+ + {/* Insights */} + {data.insights.length > 0 && ( +
+ {data.insights.map((ins, i) => { + const s: Record = { + warning: 'bg-yellow-500/10 border-yellow-500/40 text-yellow-400', + info: 'bg-blue-500/10 border-blue-500/40 text-blue-400', + success: 'bg-green-500/10 border-green-500/40 text-green-400', + }; + return ( +
+ {ins.message} +
+ ); + })} +
+ )} + + {/* Attributs (JA4, hosts, ASN, pays, UA…) */} + + + )} +
+ )} +
+ ); +} + export function InvestigationView() { const { ip } = useParams<{ ip: string }>(); const navigate = useNavigate(); @@ -331,12 +421,21 @@ export function InvestigationView() { return (
+ {/* Breadcrumb */} + + {/* En-tête */}
+ {/* Attributs détectés (ex-DetailsView) */} + + {/* Ligne 0 : Synthèse multi-sources */} diff --git a/frontend/src/hooks/useDetections.ts b/frontend/src/hooks/useDetections.ts index 2616500..cd026e6 100644 --- a/frontend/src/hooks/useDetections.ts +++ b/frontend/src/hooks/useDetections.ts @@ -12,6 +12,7 @@ interface UseDetectionsParams { sort_by?: string; sort_order?: string; group_by_ip?: boolean; + score_type?: string; } export function useDetections(params: UseDetectionsParams = {}) { @@ -44,6 +45,7 @@ export function useDetections(params: UseDetectionsParams = {}) { params.sort_by, params.sort_order, params.group_by_ip, + params.score_type, ]); return { data, loading, error };