From b0999ee83a10df8489da2f360e42b656264d2f53 Mon Sep 17 00:00:00 2001 From: SOC Analyst Date: Thu, 19 Mar 2026 12:20:35 +0100 Subject: [PATCH] feat: tooltips InvestigationView/BruteForce/HeaderFingerprint + fix RouteTracker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InvestigationView: InfoTip sur Risk Score, JA4 Rotation, TCP Spoof, Persistance, UA/CH mismatch, Browser score, JA4 distincts, JA4 rares, JA4 Légitimes (baseline) - BruteForceView: tooltip Params combos, Top JA4, Credential Stuffing, Énumération - HeaderFingerprintView: tooltip colonnes Hash cluster, Browser Score, UA/CH Mismatch %, Sec-Fetch modes - App.tsx: RouteTracker ignore /investigation/ip/ (route alias) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/App.tsx | 2 ++ frontend/src/components/BruteForceView.tsx | 18 +++++++--- .../src/components/HeaderFingerprintView.tsx | 5 +++ frontend/src/components/InvestigationView.tsx | 34 ++++++++++++------- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 32c4734..4a17b75 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -330,6 +330,8 @@ function RouteTracker() { const p = location.pathname; if (p.startsWith('/investigation/ja4/')) { saveRecent({ type: 'ja4', value: decodeURIComponent(p.split('/investigation/ja4/')[1] || '') }); + } else if (p.startsWith('/investigation/ip/')) { + // Redirigé — ne pas sauvegarder l'alias (la route finale /investigation/:ip sera sauvegardée) } else if (p.startsWith('/investigation/')) { saveRecent({ type: 'ip', value: decodeURIComponent(p.split('/investigation/')[1] || '') }); } else if (p.startsWith('/entities/subnet/')) { diff --git a/frontend/src/components/BruteForceView.tsx b/frontend/src/components/BruteForceView.tsx index 124cdf7..fede15e 100644 --- a/frontend/src/components/BruteForceView.tsx +++ b/frontend/src/components/BruteForceView.tsx @@ -1,6 +1,8 @@ import { useState, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import DataTable, { Column } from './ui/DataTable'; +import { InfoTip } from './ui/Tooltip'; +import { TIPS } from './ui/tooltips'; // ─── Types ──────────────────────────────────────────────────────────────────── @@ -87,12 +89,14 @@ function AttackersTable({ { key: 'total_params', label: 'Params', + tooltip: TIPS.params_combos, align: 'right', render: (v: number) => formatNumber(v), }, { key: 'ja4', label: 'JA4', + tooltip: TIPS.ja4, render: (v: string) => ( {v ? `${v.slice(0, 16)}…` : '—'} @@ -163,9 +167,9 @@ function TargetRow({ t, navigate }: { t: BruteForceTarget; navigate: (path: stri {formatNumber(t.total_params)} {t.attack_type === 'credential_stuffing' ? ( - 💳 Credential Stuffing + 💳 Credential Stuffing ) : ( - 🔍 Énumération + 🔍 Énumération )} @@ -380,9 +384,15 @@ export function BruteForceView() { Host (cliquer pour détails) IPs distinctes Total hits - Params combos + + Params combos + + Type d'attaque - Top JA4 + + Top JA4 + + diff --git a/frontend/src/components/HeaderFingerprintView.tsx b/frontend/src/components/HeaderFingerprintView.tsx index aaa24a1..0312b17 100644 --- a/frontend/src/components/HeaderFingerprintView.tsx +++ b/frontend/src/components/HeaderFingerprintView.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import DataTable, { Column } from './ui/DataTable'; +import { TIPS } from './ui/tooltips'; // ─── Types ──────────────────────────────────────────────────────────────────── @@ -140,6 +141,7 @@ export function HeaderFingerprintView() { { key: 'hash', label: 'Hash cluster', + tooltip: TIPS.hash_cluster, sortable: true, render: (_, row) => ( @@ -158,6 +160,7 @@ export function HeaderFingerprintView() { { key: 'avg_browser_score', label: 'Browser Score', + tooltip: TIPS.browser_score, sortable: true, render: (v) => (
@@ -171,6 +174,7 @@ export function HeaderFingerprintView() { { key: 'ua_ch_mismatch_pct', label: 'UA/CH Mismatch %', + tooltip: TIPS.ua_mismatch, sortable: true, align: 'right', render: (v) => ( @@ -191,6 +195,7 @@ export function HeaderFingerprintView() { { key: 'top_sec_fetch_modes', label: 'Sec-Fetch modes', + tooltip: TIPS.sec_fetch, sortable: false, render: (v) => (
diff --git a/frontend/src/components/InvestigationView.tsx b/frontend/src/components/InvestigationView.tsx index c4183eb..20b4c79 100644 --- a/frontend/src/components/InvestigationView.tsx +++ b/frontend/src/components/InvestigationView.tsx @@ -7,6 +7,8 @@ import { UserAgentAnalysis } from './analysis/UserAgentAnalysis'; import { CorrelationSummary } from './analysis/CorrelationSummary'; import { CorrelationGraph } from './CorrelationGraph'; import { ReputationPanel } from './ReputationPanel'; +import { InfoTip } from './ui/Tooltip'; +import { TIPS } from './ui/tooltips'; // ─── Multi-source Activity Summary Widget ───────────────────────────────────── @@ -33,7 +35,7 @@ function RiskGauge({ score }: { score: number }) { transform="rotate(-90 40 40)" /> {score} - Risk Score + Risk Score
); } @@ -113,9 +115,9 @@ function IPActivitySummary({ ip }: { ip: string }) {
0} label={`ML: ${data.ml.total_detections} détections`} color="threat-critical" /> - - - + + +
{/* Detail grid */}
@@ -137,7 +139,7 @@ function IPActivitySummary({ ip }: { ip: string }) { )} {data.tcp_spoofing.detected && (
-
TCP Spoofing
+
TCP Spoofing
TTL {data.tcp_spoofing.tcp_ttl} → {data.tcp_spoofing.suspected_os}
UA déclare: {data.tcp_spoofing.declared_os}
@@ -220,8 +222,8 @@ function FingerprintCoherenceWidget({ ip }: { ip: string }) { {vs.icon}
{vs.label}
-
- Score de spoofing: {data.spoofing_score}/100 +
+ Score de spoofing: {data.spoofing_score}/100
@@ -250,14 +252,17 @@ function FingerprintCoherenceWidget({ ip }: { ip: string }) { {/* Key indicators */}
{[ - { label: 'UA/CH mismatch', value: `${data.indicators.ua_ch_mismatch_rate}%`, warn: data.indicators.ua_ch_mismatch_rate > 20 }, - { label: 'Browser score', value: `${data.indicators.avg_browser_score}/100`, warn: data.indicators.avg_browser_score > 60 }, - { label: 'JA4 distincts', value: data.indicators.distinct_ja4_count, warn: data.indicators.distinct_ja4_count > 2 }, - { label: 'JA4 rares', value: `${data.indicators.rare_ja4_rate}%`, warn: data.indicators.rare_ja4_rate > 50 }, + { label: 'UA/CH mismatch', tip: TIPS.ua_mismatch, value: `${data.indicators.ua_ch_mismatch_rate}%`, warn: data.indicators.ua_ch_mismatch_rate > 20 }, + { label: 'Browser score', tip: TIPS.browser_score, value: `${data.indicators.avg_browser_score}/100`, warn: data.indicators.avg_browser_score > 60 }, + { label: 'JA4 distincts', tip: TIPS.ja4_distinct, value: data.indicators.distinct_ja4_count, warn: data.indicators.distinct_ja4_count > 2 }, + { label: 'JA4 rares %', tip: TIPS.ja4_rare_pct, value: `${data.indicators.rare_ja4_rate}%`, warn: data.indicators.rare_ja4_rate > 50 }, ].map((ind) => (
{ind.value}
-
{ind.label}
+
+ {ind.label} + +
))}
@@ -376,7 +381,10 @@ export function InvestigationView() {
-

🔏 JA4 Légitimes (baseline)

+

+ 🔏 JA4 Légitimes (baseline) + +

Comparez les fingerprints de cette IP avec la baseline des JA4 légitimes pour évaluer le risque de spoofing.