feat: système de tooltips universel sur tous les termes techniques

- Nouveau composant ui/Tooltip.tsx (createPortal → pas de clipping overflow)
  - Tooltip : bulle au survol avec ajustement viewport automatique
  - InfoTip : icône ⓘ avec bulle intégrée
- Nouveau ui/tooltips.ts : 50+ définitions en français
  (clustering, ML features, TCP spoofing, general)
- ui/DataTable.tsx : prop tooltip sur Column → InfoTip dans les en-têtes
- ClusteringView : ⓘ sur Sensibilité, k, arêtes, toutes les stats,
  légende CRITICAL/HIGH/MEDIUM/LOW, sidebar (score risque, radar,
  TTL/MSS/Score ML/Vélocité/Headless/UA-CH)
- MLFeaturesView : <title> SVG sur axes radar et scatter, tooltip
  sur colonnes Fuzzing/Type/Signaux
- TcpSpoofingView : tooltip sur colonnes TTL/MSS/Scale/OS/Confiance/Verdict
- App.tsx : tooltip sur Alertes 24h et niveaux CRITICAL/HIGH/MEDIUM

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
SOC Analyst
2026-03-19 12:02:15 +01:00
parent 5805231c38
commit 485b95b62e
7 changed files with 529 additions and 39 deletions

View File

@ -1,6 +1,7 @@
import { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import DataTable, { Column } from './ui/DataTable';
import { TIPS } from './ui/tooltips';
// ─── Types ────────────────────────────────────────────────────────────────────
@ -100,14 +101,14 @@ function ErrorMessage({ message }: { message: string }) {
// ─── Radar Chart (SVG octagonal) ─────────────────────────────────────────────
const RADAR_AXES = [
{ key: 'fuzzing_score', label: 'Fuzzing' },
{ key: 'velocity_score', label: 'Vélocité' },
{ key: 'fake_nav_score', label: 'Fausse nav' },
{ key: 'ua_mismatch_score', label: 'UA/CH mismatch' },
{ key: 'sni_mismatch_score', label: 'SNI mismatch' },
{ key: 'orphan_score', label: 'Orphan ratio' },
{ key: 'path_repetition_score', label: 'Répétition URL' },
{ key: 'payload_anomaly_score', label: 'Payload anormal' },
{ key: 'fuzzing_score', label: 'Fuzzing', tip: TIPS.fuzzing },
{ key: 'velocity_score', label: 'Vélocité', tip: TIPS.velocity },
{ key: 'fake_nav_score', label: 'Fausse nav', tip: TIPS.fake_nav },
{ key: 'ua_mismatch_score', label: 'UA/CH mismatch', tip: TIPS.ua_mismatch },
{ key: 'sni_mismatch_score', label: 'SNI mismatch', tip: TIPS.sni_mismatch },
{ key: 'orphan_score', label: 'Orphan ratio', tip: TIPS.orphan_ratio },
{ key: 'path_repetition_score', label: 'Répétition URL', tip: TIPS.path_repetition },
{ key: 'payload_anomaly_score', label: 'Payload anormal', tip: TIPS.payload_anomaly },
] as const;
type RadarKey = typeof RADAR_AXES[number]['key'];
@ -182,7 +183,7 @@ function RadarChart({ data }: { data: RadarData }) {
<circle key={i} cx={x} cy={y} r="3" fill="rgba(239,68,68,0.9)" />
))}
{/* Axis labels */}
{/* Axis labels — survolez pour la définition */}
{RADAR_AXES.map((axis, i) => {
const [x, y] = pointFor(i, maxR + 18);
const anchor = x < cx - 5 ? 'end' : x > cx + 5 ? 'start' : 'middle';
@ -195,7 +196,9 @@ function RadarChart({ data }: { data: RadarData }) {
fontSize="10"
fill="rgba(148,163,184,0.9)"
dominantBaseline="middle"
style={{ cursor: 'help' }}
>
<title>{axis.tip}</title>
{axis.label}
</text>
);
@ -257,7 +260,10 @@ function ScatterPlot({ points }: { points: ScatterPoint[] }) {
{xTicks.map((v) => (
<text key={v} x={toSvgX(v)} y={H - padB + 12} textAnchor="middle" fontSize="9" fill="rgba(148,163,184,0.7)">{v}</text>
))}
<text x={(W - padL - padR) / 2 + padL} y={H - 2} textAnchor="middle" fontSize="10" fill="rgba(148,163,184,0.8)">Fuzzing Index </text>
<text x={(W - padL - padR) / 2 + padL} y={H - 2} textAnchor="middle" fontSize="10" fill="rgba(148,163,184,0.8)" style={{ cursor: 'help' }}>
<title>{TIPS.fuzzing_index}</title>
Fuzzing Index
</text>
{/* Y axis */}
<line x1={padL} y1={padT} x2={padL} y2={H - padB} stroke="rgba(100,116,139,0.4)" strokeWidth="1" />
@ -361,6 +367,7 @@ function AnomaliesTable({
{
key: 'fuzzing_index',
label: 'Fuzzing',
tooltip: TIPS.fuzzing_index,
align: 'right',
render: (v: number) => (
<span className={`px-1.5 py-0.5 rounded text-xs font-semibold ${fuzzingBadgeClass(v)}`}>
@ -371,6 +378,7 @@ function AnomaliesTable({
{
key: 'attack_type',
label: 'Type',
tooltip: 'Type d\'attaque détecté : Brute Force 🔑, Flood 🌊, Scraper 🕷️, Spoofing 🎭, Scanner 🔍',
render: (v: string) => (
<span title={v} className="text-sm">{attackTypeEmoji(v)}</span>
),
@ -378,6 +386,7 @@ function AnomaliesTable({
{
key: '_signals',
label: 'Signaux',
tooltip: '⚠️ UA/CH mismatch · 🎭 Fausse navigation · 🔄 UA rotatif · 🌐 SNI mismatch',
sortable: false,
render: (_: unknown, row: MLAnomaly) => (
<span className="flex gap-0.5">