feat(clustering): palette diversifiée, suppression scores anomalie/robot, visualisation éclatée

- Suppression de 'Score Anomalie' (avg_score) des 31→30 features de clustering
- Suppression de 'Score de détection robot' (mean_score) de la sidebar et de l'API
- Suppression de bot_ips / high_risk_ips des stats (métriques dérivées des scores supprimés)
- Redistribution des poids dans risk_score_from_centroid: UA-CH mismatch +17%,
  fuzzing +14%, headless +10%, vélocité +9%, ip_id_zero +7%
- Mise à jour des indices feature dans name_cluster et risk_score_from_centroid
- Palette 24 couleurs spectrales (cluster_color) → bleu/violet/rose/teal/amber/cyan/lime...
  Les couleurs identifient les clusters, non leur niveau de risque
- Remplacement de la légende CRITICAL/HIGH/MEDIUM/LOW par la liste des clusters actifs
- Ajout de spread_clusters(): répulsion itérative des centroïdes trop proches (50 iter)
  min_dist=0.16 → les clusters se repoussent mutuellement → visualisation plus lisible
- Interface TypeScript mise à jour (suppression mean_score, bot_ips, high_risk_ips)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
SOC Analyst
2026-03-19 14:01:14 +01:00
parent da6fef87fd
commit 08d003a050
3 changed files with 160 additions and 86 deletions

View File

@ -35,7 +35,6 @@ interface ClusterNode {
hit_count: number;
mean_ttl: number;
mean_mss: number;
mean_score: number;
mean_velocity: number;
mean_fuzzing: number;
mean_headless: number;
@ -60,8 +59,6 @@ interface ClusterStats {
total_clusters: number;
total_ips: number;
total_hits: number;
bot_ips: number;
high_risk_ips: number;
n_samples: number;
k: number;
elapsed_s: number;
@ -427,8 +424,6 @@ export default function ClusteringView() {
<div className="font-semibold text-sm mb-2">Résultats</div>
<Stat label="Clusters" value={data.stats.total_clusters} tooltip={TIPS.k_actual} />
<Stat label="IPs totales" value={data.stats.total_ips.toLocaleString()} tooltip={TIPS.pca_2d} />
<Stat label="IPs bots 🤖" value={data.stats.bot_ips.toLocaleString()} color="text-red-400" tooltip={TIPS.ips_bots} />
<Stat label="Risque élevé" value={data.stats.high_risk_ips.toLocaleString()} color="text-orange-400" tooltip={TIPS.high_risk} />
<Stat label="Hits totaux" value={data.stats.total_hits.toLocaleString()} tooltip={TIPS.total_hits} />
<Stat label="Calcul" value={`${data.stats.elapsed_s}s`} tooltip={TIPS.calc_time} />
</div>
@ -504,7 +499,7 @@ export default function ClusteringView() {
</div>
<p className="text-white font-semibold text-lg tracking-wide">Clustering en cours</p>
<p className="text-text-secondary text-sm mt-1">
K-means++ · 31 features · {Math.round(k * sensitivity)} clusters · toutes les IPs
K-means++ · 30 features · {Math.round(k * sensitivity)} clusters · toutes les IPs
</p>
<p className="text-text-disabled text-xs mt-2 animate-pulse">Mise à jour automatique toutes les 3 secondes</p>
</div>
@ -527,19 +522,18 @@ export default function ClusteringView() {
{/* Légende overlay */}
<div style={{ position: 'absolute', bottom: 16, left: 16, pointerEvents: 'all' }}>
<div className="bg-black/70 rounded-lg p-2 text-xs flex flex-col gap-1">
{([
['#dc2626', 'CRITICAL', TIPS.risk_critical],
['#f97316', 'HIGH', TIPS.risk_high],
['#eab308', 'MEDIUM', TIPS.risk_medium],
['#22c55e', 'LOW', TIPS.risk_low],
] as const).map(([c, l, tip]) => (
<div key={l} className="flex items-center gap-2" title={tip}>
<span className="w-3 h-3 rounded-full flex-shrink-0" style={{ background: c }} />
<span className="text-white/80 cursor-help">{l}</span>
<div className="text-white/50 text-[10px] uppercase tracking-wide mb-1">Clusters</div>
{data?.nodes?.slice(0, 6).map((n) => (
<div key={n.id} className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full flex-shrink-0" style={{ background: n.color }} />
<span className="text-white/70 truncate max-w-[120px]">{n.label}</span>
</div>
))}
{(data?.nodes?.length ?? 0) > 6 && (
<div className="text-white/30 text-[10px]">+{(data?.nodes?.length ?? 0) - 6} autres</div>
)}
<div className="mt-1 pt-1 border-t border-white/10 text-white/40 text-[10px] cursor-help" title={TIPS.features_31}>
31 features · PCA 2D
30 features · PCA 2D
</div>
</div>
</div>
@ -666,7 +660,6 @@ function ClusterSidebar({ node, ipDetails, ipTotal, ipPage, clusterPoints, onClo
<div className="font-semibold mb-2">Stack TCP</div>
<Stat label="TTL moyen" value={node.mean_ttl} tooltip={TIPS.mean_ttl} />
<Stat label="MSS moyen" value={node.mean_mss} tooltip={TIPS.mean_mss} />
<Stat label="Score ML" value={`${(node.mean_score * 100).toFixed(1)}%`} tooltip={TIPS.mean_score} />
<Stat label="Vélocité" value={node.mean_velocity?.toFixed ? `${node.mean_velocity.toFixed(2)} rps` : '-'} tooltip={TIPS.mean_velocity} />
<Stat label="Headless" value={node.mean_headless ? `${(node.mean_headless * 100).toFixed(0)}%` : '-'} tooltip={TIPS.mean_headless} />
<Stat label="UA-CH Mismatch" value={node.mean_ua_ch ? `${(node.mean_ua_ch * 100).toFixed(0)}%` : '-'} tooltip={TIPS.mean_ua_ch} />