import { useParams, useNavigate } from 'react-router-dom'; import { useEffect, useState } from 'react'; import { InfoTip } from './ui/Tooltip'; import { TIPS } from './ui/tooltips'; import { formatDateOnly } from '../utils/dateUtils'; import { getCountryFlag } from '../utils/countryUtils'; interface EntityStats { entity_type: string; entity_value: string; total_requests: number; unique_ips: number; first_seen: string; last_seen: string; } interface EntityRelatedAttributes { ips: string[]; ja4s: string[]; hosts: string[]; asns: string[]; countries: string[]; } interface AttributeValue { value: string; count: number; percentage: number; } interface EntityInvestigationData { stats: EntityStats; related: EntityRelatedAttributes; user_agents: AttributeValue[]; client_headers: AttributeValue[]; paths: AttributeValue[]; query_params: AttributeValue[]; } export function EntityInvestigationView() { const { type, value } = useParams<{ type: string; value: string }>(); const navigate = useNavigate(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showAllUA, setShowAllUA] = useState(false); useEffect(() => { if (!type || !value) { setError("Type ou valeur d'entité manquant"); setLoading(false); return; } const fetchInvestigation = async () => { setLoading(true); try { const response = await fetch(`/api/entities/${type}/${encodeURIComponent(value)}`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Erreur chargement données'); } const result = await response.json(); setData(result); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur inconnue'); } finally { setLoading(false); } }; fetchInvestigation(); }, [type, value]); const getEntityLabel = (entityType: string) => { const labels: Record = { ip: 'Adresse IP', ja4: 'Fingerprint JA4', user_agent: 'User-Agent', client_header: 'Client Header', host: 'Host', path: 'Path', query_param: 'Query Params' }; return labels[entityType] || entityType; }; ; if (loading) { return (
Chargement...
); } if (error || !data) { return (
Erreur
{error || 'Données non disponibles'}
); } return (
{/* Header */}

Investigation: {getEntityLabel(data.stats.entity_type)}

{data.stats.entity_value}
Requêtes: {data.stats.total_requests.toLocaleString()}
IPs Uniques: {data.stats.unique_ips.toLocaleString()}
{/* Stats Summary */}
{/* Panel 1: IPs Associées */}

1. IPs Associées

{data.related.ips.slice(0, 20).map((ip, idx) => ( ))}
{data.related.ips.length === 0 && (
Aucune IP associée
)} {data.related.ips.length > 20 && (
+{data.related.ips.length - 20} autres IPs
)}
{/* Panel 2: JA4 Fingerprints */}

2. JA4 Fingerprints

{data.related.ja4s.slice(0, 10).map((ja4, idx) => (
{ja4}
))}
{data.related.ja4s.length === 0 && (
Aucun JA4 associé
)} {data.related.ja4s.length > 10 && (
+{data.related.ja4s.length - 10} autres JA4
)}
{/* Panel 3: User-Agents */}

3. User-Agents

{(showAllUA ? data.user_agents : data.user_agents.slice(0, 10)).map((ua, idx) => (
{ua.value}
{ua.count} requêtes
{ua.percentage.toFixed(1)}%
))}
{data.user_agents.length === 0 && (
Aucun User-Agent
)} {data.user_agents.length > 10 && ( )}
{/* Panel 4: Client Headers */}

4. Client Headers

{data.client_headers.slice(0, 10).map((header, idx) => (
{header.value}
{header.count} requêtes
{header.percentage.toFixed(1)}%
))}
{data.client_headers.length === 0 && (
Aucun Client Header
)} {data.client_headers.length > 10 && (
+{data.client_headers.length - 10} autres Client Headers
)}
{/* Panel 5: Hosts */}

5. Hosts Ciblés

{data.related.hosts.slice(0, 15).map((host, idx) => (
{host}
))}
{data.related.hosts.length === 0 && (
Aucun Host associé
)} {data.related.hosts.length > 15 && (
+{data.related.hosts.length - 15} autres Hosts
)}
{/* Panel 6: Paths */}

6. Paths

{data.paths.slice(0, 15).map((path, idx) => (
{path.value}
{path.count} requêtes
{path.percentage.toFixed(1)}%
))}
{data.paths.length === 0 && (
Aucun Path
)} {data.paths.length > 15 && (
+{data.paths.length - 15} autres Paths
)}
{/* Panel 7: Query Params */}

7. Query Params

{data.query_params.slice(0, 15).map((qp, idx) => (
{qp.value}
{qp.count} requêtes
{qp.percentage.toFixed(1)}%
))}
{data.query_params.length === 0 && (
Aucun Query Param
)} {data.query_params.length > 15 && (
+{data.query_params.length - 15} autres Query Params
)}
{/* Panel 8: ASNs & Pays */}
{/* ASNs */}

ASNs

{data.related.asns.slice(0, 10).map((asn, idx) => (
{asn}
))}
{data.related.asns.length === 0 && (
Aucun ASN
)} {data.related.asns.length > 10 && (
+{data.related.asns.length - 10} autres ASNs
)}
{/* Pays */}

Pays

{data.related.countries.slice(0, 10).map((country, idx) => (
{getCountryFlag(country)} {country}
))}
{data.related.countries.length === 0 && (
Aucun pays
)} {data.related.countries.length > 10 && (
+{data.related.countries.length - 10} autres pays
)}
); } function StatCard({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); }