feat: tooltips universels sur l'ensemble du site

- CampaignsView: Score menace, JA4, ASN, Tendance, Score, Détections, Confiance
- FingerprintsView: colonnes avec tooltips via DataTable
- JA4InvestigationView: IPs Uniques, JA4, ASN, Score
- SubnetInvestigation: colonnes clés avec tooltips
- CorrelationGraph: légende et badges
- EntityInvestigationView: sections headers, JA4, ASN
- IncidentsView: stat cards, sévérité, ASN, Score de risque
- Fix: suppression des imports dupliqués dans CampaignsView

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
SOC Analyst
2026-03-19 12:24:53 +01:00
parent b0999ee83a
commit da6fef87fd
11 changed files with 70 additions and 44 deletions

View File

@ -388,7 +388,7 @@ export function BruteForceView() {
Params combos Params combos
<InfoTip content={TIPS.params_combos} /> <InfoTip content={TIPS.params_combos} />
</th> </th>
<th className="px-4 py-3">Type d'attaque</th> <th className="px-4 py-3"><span className="flex items-center gap-1">Type d'attaque<InfoTip content={TIPS.attack_brute_force} /></span></th>
<th className="px-4 py-3 whitespace-nowrap"> <th className="px-4 py-3 whitespace-nowrap">
Top JA4 Top JA4
<InfoTip content={TIPS.ja4} /> <InfoTip content={TIPS.ja4} />

View File

@ -2,6 +2,8 @@ import { useState, useEffect, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import DataTable, { Column } from './ui/DataTable'; import DataTable, { Column } from './ui/DataTable';
import ThreatBadge from './ui/ThreatBadge'; import ThreatBadge from './ui/ThreatBadge';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
@ -433,7 +435,7 @@ function ClusterCard({ cluster, expanded, ips, loadingIPs, onToggle, onNavigate
</span> </span>
{cluster.asn && ( {cluster.asn && (
<span className="text-text-secondary"> <span className="text-text-secondary">
ASN <span className="text-text-primary font-mono font-semibold">{cluster.asn}</span> <span className="flex items-center gap-1">ASN<InfoTip content={TIPS.asn} /></span> <span className="text-text-primary font-mono font-semibold">{cluster.asn}</span>
</span> </span>
)} )}
{cluster.primary_target && ( {cluster.primary_target && (
@ -447,7 +449,7 @@ function ClusterCard({ cluster, expanded, ips, loadingIPs, onToggle, onNavigate
<div className="space-y-1.5"> <div className="space-y-1.5">
{cluster.ja4 && ( {cluster.ja4 && (
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
<span className="text-text-disabled w-8">JA4</span> <span className="text-text-disabled w-8 flex items-center gap-0.5">JA4<InfoTip content={TIPS.ja4} /></span>
<code className="font-mono text-text-secondary bg-background-card px-2 py-0.5 rounded truncate max-w-xs"> <code className="font-mono text-text-secondary bg-background-card px-2 py-0.5 rounded truncate max-w-xs">
{cluster.ja4} {cluster.ja4}
</code> </code>
@ -477,7 +479,10 @@ function ClusterCard({ cluster, expanded, ips, loadingIPs, onToggle, onNavigate
{/* Score bar */} {/* Score bar */}
<div> <div>
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="text-text-secondary text-xs">Score menace</span> <span className="text-text-secondary text-xs flex items-center gap-1">
Score menace
<InfoTip content={TIPS.risk_score_inv} />
</span>
<span className={`text-xs font-mono font-semibold ${scoreColor}`}>{cluster.score}/100</span> <span className={`text-xs font-mono font-semibold ${scoreColor}`}>{cluster.score}/100</span>
</div> </div>
<div className="relative h-2 bg-background-card rounded-full overflow-hidden"> <div className="relative h-2 bg-background-card rounded-full overflow-hidden">
@ -532,7 +537,7 @@ function ClusterCard({ cluster, expanded, ips, loadingIPs, onToggle, onNavigate
<th className="px-4 py-2 text-text-secondary font-medium text-xs">IP</th> <th className="px-4 py-2 text-text-secondary font-medium text-xs">IP</th>
<th className="px-4 py-2 text-text-secondary font-medium text-xs">JA4</th> <th className="px-4 py-2 text-text-secondary font-medium text-xs">JA4</th>
<th className="px-4 py-2 text-text-secondary font-medium text-xs">Détections</th> <th className="px-4 py-2 text-text-secondary font-medium text-xs">Détections</th>
<th className="px-4 py-2 text-text-secondary font-medium text-xs">Confiance</th> <th className="px-4 py-2 text-text-secondary font-medium text-xs"><span className="flex items-center gap-1">Confiance<InfoTip content={TIPS.confiance} /></span></th>
<th className="px-4 py-2 text-text-secondary font-medium text-xs" /> <th className="px-4 py-2 text-text-secondary font-medium text-xs" />
</tr> </tr>
</thead> </thead>
@ -759,6 +764,7 @@ function BehavioralMatrix({ clusters }: { clusters: ClusterData[] }) {
{ {
key: 'score', key: 'score',
label: 'Score', label: 'Score',
tooltip: TIPS.risk_score_inv,
align: 'right', align: 'right',
render: (v: number) => ( render: (v: number) => (
<span className={`text-xs font-mono font-semibold ${getConfidenceTextColor(v / 100)}`}> <span className={`text-xs font-mono font-semibold ${getConfidenceTextColor(v / 100)}`}>
@ -774,6 +780,7 @@ function BehavioralMatrix({ clusters }: { clusters: ClusterData[] }) {
{ {
key: 'trend', key: 'trend',
label: 'Tendance', label: 'Tendance',
tooltip: TIPS.tendance,
sortable: false, sortable: false,
render: (_: string, row: BehavioralRow) => render: (_: string, row: BehavioralRow) =>
row.trend === 'up' ? ( row.trend === 'up' ? (
@ -916,7 +923,7 @@ function BotnetRow({ item, onInvestigate }: { item: BotnetItem; onInvestigate: (
</div> </div>
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className={`text-xs px-2 py-1 rounded-full ${badge.bg} ${badge.text}`}>{badge.label}</span> <span title={item.botnet_class === 'global_botnet' ? TIPS.botnet_global : item.botnet_class === 'regional_botnet' ? TIPS.botnet_regional : TIPS.botnet_concentrated} className={`text-xs px-2 py-1 rounded-full ${badge.bg} ${badge.text}`}>{badge.label}</span>
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<button <button
@ -1046,7 +1053,7 @@ function BotnetTab() {
<th className="px-4 py-3">IPs distinctes</th> <th className="px-4 py-3">IPs distinctes</th>
<th className="px-4 py-3">Pays</th> <th className="px-4 py-3">Pays</th>
<th className="px-4 py-3">Hosts ciblés</th> <th className="px-4 py-3">Hosts ciblés</th>
<th className="px-4 py-3">Score distribution</th> <th className="px-4 py-3"><span className="flex items-center gap-1">Score distribution<InfoTip content={TIPS.risk_score_inv} /></span></th>
<th className="px-4 py-3">Classe</th> <th className="px-4 py-3">Classe</th>
<th className="px-4 py-3"></th> <th className="px-4 py-3"></th>
</tr> </tr>

View File

@ -15,6 +15,8 @@ import ReactFlow, {
} from 'reactflow'; } from 'reactflow';
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
import { useEffect, useState, useCallback, memo } from 'react'; import { useEffect, useState, useCallback, memo } from 'react';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
// ─── Types ─────────────────────────────────────────────────────────────────── // ─── Types ───────────────────────────────────────────────────────────────────
@ -510,7 +512,7 @@ function GraphInner({ rawData, loading, error, filters, toggleFilter, height, ip
position="bottom-left" position="bottom-left"
className="bg-background-secondary/95 border border-background-card rounded-lg px-3 py-2 shadow-lg text-xs text-text-secondary" className="bg-background-secondary/95 border border-background-card rounded-lg px-3 py-2 shadow-lg text-xs text-text-secondary"
> >
{nodes.length} nœuds · {edges.length} arêtes <span className="flex items-center gap-1">{nodes.length} nœuds · {edges.length} arêtes<InfoTip content={TIPS.correlation_node} /></span>
</Panel> </Panel>
</ReactFlow> </ReactFlow>
</div> </div>

View File

@ -1,5 +1,7 @@
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
interface EntityStats { interface EntityStats {
entity_type: string; entity_type: string;
@ -194,7 +196,7 @@ export function EntityInvestigationView() {
{/* Panel 2: JA4 Fingerprints */} {/* Panel 2: JA4 Fingerprints */}
<div className="bg-background-secondary rounded-lg p-6 mb-6"> <div className="bg-background-secondary rounded-lg p-6 mb-6">
<h3 className="text-lg font-medium text-text-primary mb-4">2. JA4 Fingerprints</h3> <h3 className="text-lg font-medium text-text-primary mb-4"><span className="flex items-center gap-1">2. JA4 Fingerprints<InfoTip content={TIPS.ja4} /></span></h3>
<div className="space-y-2"> <div className="space-y-2">
{data.related.ja4s.slice(0, 10).map((ja4, idx) => ( {data.related.ja4s.slice(0, 10).map((ja4, idx) => (
<div key={idx} className="flex items-center justify-between bg-background-card rounded-lg p-3"> <div key={idx} className="flex items-center justify-between bg-background-card rounded-lg p-3">
@ -251,7 +253,7 @@ export function EntityInvestigationView() {
{/* Panel 4: Client Headers */} {/* Panel 4: Client Headers */}
<div className="bg-background-secondary rounded-lg p-6 mb-6"> <div className="bg-background-secondary rounded-lg p-6 mb-6">
<h3 className="text-lg font-medium text-text-primary mb-4">4. Client Headers</h3> <h3 className="text-lg font-medium text-text-primary mb-4"><span className="flex items-center gap-1">4. Client Headers<InfoTip content={TIPS.accept_encoding} /></span></h3>
<div className="space-y-3"> <div className="space-y-3">
{data.client_headers.slice(0, 10).map((header, idx) => ( {data.client_headers.slice(0, 10).map((header, idx) => (
<div key={idx} className="bg-background-card rounded-lg p-3 space-y-2"> <div key={idx} className="bg-background-card rounded-lg p-3 space-y-2">
@ -347,7 +349,7 @@ export function EntityInvestigationView() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* ASNs */} {/* ASNs */}
<div className="bg-background-secondary rounded-lg p-6"> <div className="bg-background-secondary rounded-lg p-6">
<h3 className="text-lg font-medium text-text-primary mb-4">ASNs</h3> <h3 className="text-lg font-medium text-text-primary mb-4"><span className="flex items-center gap-1">ASNs<InfoTip content={TIPS.asn} /></span></h3>
<div className="space-y-2"> <div className="space-y-2">
{data.related.asns.slice(0, 10).map((asn, idx) => ( {data.related.asns.slice(0, 10).map((asn, idx) => (
<div key={idx} className="bg-background-card rounded-lg p-3"> <div key={idx} className="bg-background-card rounded-lg p-3">

View File

@ -2,6 +2,8 @@ import { useState, useEffect, useCallback, Fragment, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import DataTable, { Column } from './ui/DataTable'; import DataTable, { Column } from './ui/DataTable';
import ThreatBadge from './ui/ThreatBadge'; import ThreatBadge from './ui/ThreatBadge';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
@ -489,7 +491,7 @@ function SpoofingPanel() {
<span className="text-sm font-semibold text-text-primary"> <span className="text-sm font-semibold text-text-primary">
JA4 suspects de spoofing {data.total} fingerprints analysés JA4 suspects de spoofing {data.total} fingerprints analysés
</span> </span>
<span className="text-xs text-text-disabled">Score de spoofing [0-100]</span> <span className="text-xs text-text-disabled flex items-center gap-1">Score de spoofing [0-100]<InfoTip content={TIPS.spoofing_score} /></span>
</div> </div>
<div className="divide-y divide-background-card"> <div className="divide-y divide-background-card">
{data.items.length === 0 && ( {data.items.length === 0 && (
@ -563,11 +565,11 @@ function SpoofingPanel() {
{/* Indicators */} {/* Indicators */}
<div className="mt-3 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2"> <div className="mt-3 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2">
{[ {[
{ label: 'UA/CH mismatch', value: `${item.indicators.ua_ch_mismatch_pct}%`, warn: item.indicators.ua_ch_mismatch_pct > 20 }, { label: 'UA/CH mismatch', value: `${item.indicators.ua_ch_mismatch_pct}%`, warn: item.indicators.ua_ch_mismatch_pct > 20, tip: TIPS.ua_mismatch },
{ label: 'Browser score', value: `${item.indicators.avg_browser_score}/100`, warn: item.indicators.avg_browser_score > 50 }, { label: 'Browser score', value: `${item.indicators.avg_browser_score}/100`, warn: item.indicators.avg_browser_score > 50, tip: TIPS.browser_score },
{ label: 'SNI mismatch', value: `${item.indicators.sni_mismatch_pct}%`, warn: item.indicators.sni_mismatch_pct > 10 }, { label: 'SNI mismatch', value: `${item.indicators.sni_mismatch_pct}%`, warn: item.indicators.sni_mismatch_pct > 10, tip: TIPS.sni_mismatch },
{ label: 'JA4 rare', value: `${item.indicators.rare_ja4_pct}%`, warn: item.indicators.rare_ja4_pct > 50 }, { label: 'JA4 rare', value: `${item.indicators.rare_ja4_pct}%`, warn: item.indicators.rare_ja4_pct > 50, tip: TIPS.ja4_rare_pct },
{ label: 'UA rotation', value: `${item.indicators.ua_rotating_pct}%`, warn: item.indicators.ua_rotating_pct > 10 }, { label: 'UA rotation', value: `${item.indicators.ua_rotating_pct}%`, warn: item.indicators.ua_rotating_pct > 10, tip: TIPS.ua_rotation },
].map((ind) => ( ].map((ind) => (
<div <div
key={ind.label} key={ind.label}
@ -578,7 +580,7 @@ function SpoofingPanel() {
<div className={`text-sm font-semibold ${ind.warn ? 'text-threat-high' : 'text-text-primary'}`}> <div className={`text-sm font-semibold ${ind.warn ? 'text-threat-high' : 'text-text-primary'}`}>
{ind.value} {ind.value}
</div> </div>
<div className="text-xs text-text-disabled mt-0.5">{ind.label}</div> <div className="text-xs text-text-disabled mt-0.5 flex items-center justify-center gap-0.5">{ind.label}<InfoTip content={ind.tip} /></div>
</div> </div>
))} ))}
</div> </div>
@ -603,8 +605,8 @@ function SpoofingPanel() {
{activeSubTab === 'matrix' && ( {activeSubTab === 'matrix' && (
<div className="bg-background-secondary rounded-lg overflow-hidden"> <div className="bg-background-secondary rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-background-card"> <div className="px-4 py-3 border-b border-background-card">
<span className="text-sm font-semibold text-text-primary"> <span className="text-sm font-semibold text-text-primary flex items-center gap-1">
Matrice JA4 × User-Agent {matrixData.length} fingerprints Matrice JA4 × User-Agent {matrixData.length} fingerprints<InfoTip content={TIPS.ja4} />
</span> </span>
<p className="text-xs text-text-disabled mt-0.5"> <p className="text-xs text-text-disabled mt-0.5">
Pour chaque JA4, répartition des User-Agents observés. 🎭 = spoofing suspect détecté. Pour chaque JA4, répartition des User-Agents observés. 🎭 = spoofing suspect détecté.
@ -1204,7 +1206,7 @@ export function FingerprintsView() {
onClick={() => handleColSort('botnet_score')} onClick={() => handleColSort('botnet_score')}
> >
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
Score botnet Score botnet<InfoTip content={TIPS.risk_score_inv} />
{sortField === 'botnet_score' {sortField === 'botnet_score'
? <span className="text-accent-primary">{sortDir === 'desc' ? '' : ''}</span> ? <span className="text-accent-primary">{sortDir === 'desc' ? '' : ''}</span>
: <span className="text-text-disabled opacity-50">⇅</span>} : <span className="text-text-disabled opacity-50">⇅</span>}

View File

@ -241,12 +241,14 @@ export function HeaderFingerprintView() {
{ {
key: 'sec_fetch_mode', key: 'sec_fetch_mode',
label: 'Sec-Fetch Mode', label: 'Sec-Fetch Mode',
tooltip: TIPS.sec_fetch_dest,
sortable: true, sortable: true,
render: (v) => <span className="text-text-secondary">{v || '—'}</span>, render: (v) => <span className="text-text-secondary">{v || '—'}</span>,
}, },
{ {
key: 'sec_fetch_dest', key: 'sec_fetch_dest',
label: 'Sec-Fetch Dest', label: 'Sec-Fetch Dest',
tooltip: TIPS.sec_fetch_dest,
sortable: true, sortable: true,
render: (v) => <span className="text-text-secondary">{v || '—'}</span>, render: (v) => <span className="text-text-secondary">{v || '—'}</span>,
}, },

View File

@ -1,5 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
interface IncidentCluster { interface IncidentCluster {
id: string; id: string;
@ -147,10 +149,10 @@ export function IncidentsView() {
{baseline && ( {baseline && (
<div className="grid grid-cols-3 gap-3"> <div className="grid grid-cols-3 gap-3">
{([ {([
{ key: 'total_detections', label: 'Détections 24h', icon: '📊' }, { key: 'total_detections', label: 'Détections 24h', icon: '📊', tip: TIPS.total_detections_stat },
{ key: 'unique_ips', label: 'IPs uniques', icon: '🖥️' }, { key: 'unique_ips', label: 'IPs uniques', icon: '🖥️', tip: TIPS.unique_ips_stat },
{ key: 'critical_alerts', label: 'Alertes CRITICAL', icon: '🔴' }, { key: 'critical_alerts', label: 'Alertes CRITICAL', icon: '🔴', tip: TIPS.risk_critical },
] as { key: keyof BaselineData; label: string; icon: string }[]).map(({ key, label, icon }) => { ] as { key: keyof BaselineData; label: string; icon: string; tip: string }[]).map(({ key, label, icon, tip }) => {
const m = baseline[key]; const m = baseline[key];
const up = m.pct_change > 0; const up = m.pct_change > 0;
const neutral = m.pct_change === 0; const neutral = m.pct_change === 0;
@ -158,7 +160,7 @@ export function IncidentsView() {
<div key={key} className="bg-background-card border border-border rounded-lg px-4 py-3 flex items-center gap-3"> <div key={key} className="bg-background-card border border-border rounded-lg px-4 py-3 flex items-center gap-3">
<span className="text-xl">{icon}</span> <span className="text-xl">{icon}</span>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-xs text-text-disabled uppercase tracking-wide">{label}</div> <div className="text-xs text-text-disabled uppercase tracking-wide flex items-center gap-1">{label}<InfoTip content={tip} /></div>
<div className="text-xl font-bold text-text-primary">{m.today.toLocaleString('fr-FR')}</div> <div className="text-xl font-bold text-text-primary">{m.today.toLocaleString('fr-FR')}</div>
<div className="text-xs text-text-secondary">hier: {m.yesterday.toLocaleString('fr-FR')}</div> <div className="text-xs text-text-secondary">hier: {m.yesterday.toLocaleString('fr-FR')}</div>
</div> </div>
@ -292,7 +294,7 @@ export function IncidentsView() {
<div className="flex-1"> <div className="flex-1">
<div className="flex items-start justify-between mb-3"> <div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className={`px-2 py-1 rounded text-xs font-bold ${getSeverityBadgeColor(cluster.severity)}`}> <span title={cluster.severity === 'CRITICAL' ? TIPS.risk_critical : cluster.severity === 'HIGH' ? TIPS.risk_high : cluster.severity === 'MEDIUM' ? TIPS.risk_medium : TIPS.risk_low} className={`px-2 py-1 rounded text-xs font-bold ${getSeverityBadgeColor(cluster.severity)}`}>
{cluster.severity} {cluster.severity}
</span> </span>
<span className="text-lg font-bold text-text-primary">{cluster.id}</span> <span className="text-lg font-bold text-text-primary">{cluster.id}</span>
@ -302,7 +304,7 @@ export function IncidentsView() {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="text-right"> <div className="text-right">
<div className="text-2xl font-bold text-text-primary">{cluster.score}/100</div> <div className="text-2xl font-bold text-text-primary">{cluster.score}/100</div>
<div className="text-xs text-text-secondary">Score de risque</div> <div className="text-xs text-text-secondary flex items-center gap-1">Score de risque<InfoTip content={TIPS.risk_score_inv} /></div>
</div> </div>
</div> </div>
</div> </div>
@ -327,11 +329,11 @@ export function IncidentsView() {
</div> </div>
</div> </div>
<div> <div>
<div className="text-xs text-text-secondary mb-1">ASN</div> <div className="text-xs text-text-secondary mb-1 flex items-center gap-1">ASN<InfoTip content={TIPS.asn} /></div>
<div className="text-text-primary">AS{cluster.asn || '?'}</div> <div className="text-text-primary">AS{cluster.asn || '?'}</div>
</div> </div>
<div> <div>
<div className="text-xs text-text-secondary mb-1">Tendance</div> <div className="text-xs text-text-secondary mb-1 flex items-center gap-1">Tendance<InfoTip content={TIPS.tendance} /></div>
<div className={`font-bold ${ <div className={`font-bold ${
cluster.trend === 'up' ? 'text-red-500' : cluster.trend === 'up' ? 'text-red-500' :
cluster.trend === 'down' ? 'text-green-500' : cluster.trend === 'down' ? 'text-green-500' :
@ -344,7 +346,7 @@ export function IncidentsView() {
{cluster.ja4 && ( {cluster.ja4 && (
<div className="mb-3 p-2 bg-background-card rounded"> <div className="mb-3 p-2 bg-background-card rounded">
<div className="text-xs text-text-secondary mb-1">JA4 Principal</div> <div className="text-xs text-text-secondary mb-1 flex items-center gap-1">JA4 Principal<InfoTip content={TIPS.ja4} /></div>
<div className="font-mono text-xs text-text-primary break-all">{cluster.ja4}</div> <div className="font-mono text-xs text-text-primary break-all">{cluster.ja4}</div>
</div> </div>
)} )}

View File

@ -1,6 +1,8 @@
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { JA4CorrelationSummary } from './analysis/JA4CorrelationSummary'; import { JA4CorrelationSummary } from './analysis/JA4CorrelationSummary';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
interface JA4InvestigationData { interface JA4InvestigationData {
ja4: string; ja4: string;
@ -164,7 +166,7 @@ export function JA4InvestigationView() {
<div className="bg-background-secondary rounded-lg p-6"> <div className="bg-background-secondary rounded-lg p-6">
<div className="flex items-start justify-between mb-6"> <div className="flex items-start justify-between mb-6">
<div className="flex-1"> <div className="flex-1">
<div className="text-sm text-text-secondary mb-2">JA4 Fingerprint</div> <div className="text-sm text-text-secondary mb-2 flex items-center gap-1">JA4 Fingerprint<InfoTip content={TIPS.ja4} /></div>
<div className="bg-background-card rounded-lg p-3 font-mono text-sm text-text-primary break-all"> <div className="bg-background-card rounded-lg p-3 font-mono text-sm text-text-primary break-all">
{data.ja4} {data.ja4}
</div> </div>
@ -176,7 +178,7 @@ export function JA4InvestigationView() {
</div> </div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<StatBox label="IPs Uniques" value={data.unique_ips.toLocaleString()} /> <StatBox label="IPs Uniques" value={data.unique_ips.toLocaleString()} tip={TIPS.unique_ips_stat} />
<StatBox label="Première détection" value={formatDate(data.first_seen)} /> <StatBox label="Première détection" value={formatDate(data.first_seen)} />
<StatBox label="Dernière détection" value={formatDate(data.last_seen)} /> <StatBox label="Dernière détection" value={formatDate(data.last_seen)} />
<StatBox label="User-Agents" value={data.user_agents.length.toString()} /> <StatBox label="User-Agents" value={data.user_agents.length.toString()} />
@ -247,7 +249,7 @@ export function JA4InvestigationView() {
</div> </div>
<div className="bg-background-secondary rounded-lg p-6"> <div className="bg-background-secondary rounded-lg p-6">
<h3 className="text-lg font-medium text-text-primary mb-4">🏢 TOP ASNs</h3> <h3 className="text-lg font-medium text-text-primary mb-4"><span className="flex items-center gap-1">🏢 TOP ASNs<InfoTip content={TIPS.asn} /></span></h3>
<div className="space-y-3"> <div className="space-y-3">
{data.top_asns.map((asn, idx) => ( {data.top_asns.map((asn, idx) => (
<div key={idx} className="space-y-1"> <div key={idx} className="space-y-1">
@ -318,10 +320,10 @@ export function JA4InvestigationView() {
); );
} }
function StatBox({ label, value }: { label: string; value: string }) { function StatBox({ label, value, tip }: { label: string; value: string; tip?: string }) {
return ( return (
<div className="bg-background-card rounded-lg p-4"> <div className="bg-background-card rounded-lg p-4">
<div className="text-sm text-text-secondary mb-1">{label}</div> <div className="text-sm text-text-secondary mb-1 flex items-center gap-1">{label}{tip && <InfoTip content={tip} />}</div>
<div className="text-2xl font-bold text-text-primary">{value}</div> <div className="text-2xl font-bold text-text-primary">{value}</div>
</div> </div>
); );

View File

@ -12,6 +12,8 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
@ -47,11 +49,11 @@ interface VariabilityData {
type AttrKey = keyof VariabilityData['attributes']; type AttrKey = keyof VariabilityData['attributes'];
const ATTR_ROWS: { key: AttrKey; label: string; icon: string }[] = [ const ATTR_ROWS: { key: AttrKey; label: string; icon: string; tip?: string }[] = [
{ key: 'ja4', label: 'JA4 Fingerprint', icon: '🔐' }, { key: 'ja4', label: 'JA4 Fingerprint', icon: '🔐', tip: TIPS.ja4 },
{ key: 'user_agents', label: 'User-Agents', icon: '🤖' }, { key: 'user_agents', label: 'User-Agents', icon: '🤖' },
{ key: 'countries', label: 'Pays', icon: '🌍' }, { key: 'countries', label: 'Pays', icon: '🌍' },
{ key: 'asns', label: 'ASN', icon: '🏢' }, { key: 'asns', label: 'ASN', icon: '🏢', tip: TIPS.asn },
{ key: 'hosts', label: 'Hosts cibles', icon: '🖥️' }, { key: 'hosts', label: 'Hosts cibles', icon: '🖥️' },
{ key: 'subnets', label: 'Subnets', icon: '🔷' }, { key: 'subnets', label: 'Subnets', icon: '🔷' },
]; ];
@ -283,6 +285,7 @@ export function PivotView() {
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span>{row.icon}</span> <span>{row.icon}</span>
<span className="text-xs font-medium text-text-secondary">{row.label}</span> <span className="text-xs font-medium text-text-secondary">{row.label}</span>
{row.tip && <InfoTip content={row.tip} />}
</div> </div>
{shared.size > 0 && ( {shared.size > 0 && (
<div className="mt-1"> <div className="mt-1">

View File

@ -1,5 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
interface SubnetIP { interface SubnetIP {
ip: string; ip: string;
@ -139,7 +141,7 @@ export function SubnetInvestigation() {
<div className="text-2xl font-bold text-text-primary">{stats.total_detections.toLocaleString()}</div> <div className="text-2xl font-bold text-text-primary">{stats.total_detections.toLocaleString()}</div>
</div> </div>
<div className="bg-background-card rounded-lg p-4"> <div className="bg-background-card rounded-lg p-4">
<div className="text-sm text-text-secondary mb-1">JA4 Uniques</div> <div className="text-sm text-text-secondary mb-1 flex items-center gap-1">JA4 Uniques<InfoTip content={TIPS.ja4} /></div>
<div className="text-2xl font-bold text-text-primary">{stats.unique_ja4}</div> <div className="text-2xl font-bold text-text-primary">{stats.unique_ja4}</div>
</div> </div>
<div className="bg-background-card rounded-lg p-4"> <div className="bg-background-card rounded-lg p-4">
@ -161,7 +163,7 @@ export function SubnetInvestigation() {
</div> </div>
</div> </div>
<div className="bg-background-card rounded-lg p-4"> <div className="bg-background-card rounded-lg p-4">
<div className="text-sm text-text-secondary mb-1">ASN Principal</div> <div className="text-sm text-text-secondary mb-1 flex items-center gap-1">ASN Principal<InfoTip content={TIPS.asn} /></div>
<div className="text-2xl font-bold text-text-primary">AS{stats.primary_asn}</div> <div className="text-2xl font-bold text-text-primary">AS{stats.primary_asn}</div>
</div> </div>
<div className="bg-background-card rounded-lg p-4"> <div className="bg-background-card rounded-lg p-4">
@ -187,8 +189,8 @@ export function SubnetInvestigation() {
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">UA</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">UA</th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Pays</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Pays</th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">ASN</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">ASN</th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Menace</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase"><span className="flex items-center gap-1">Menace<InfoTip content={TIPS.threat_level} /></span></th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Score</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase"><span className="flex items-center gap-1">Score<InfoTip content={TIPS.risk_score_inv} /></span></th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Actions</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Actions</th>
</tr> </tr>
</thead> </thead>

View File

@ -1,4 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { InfoTip } from './ui/Tooltip';
import { TIPS } from './ui/tooltips';
interface Classification { interface Classification {
ip?: string; ip?: string;
@ -229,7 +231,7 @@ export function ThreatIntelView() {
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Entité</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Entité</th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Label</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Label</th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Tags</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Tags</th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Confiance</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase"><span className="flex items-center gap-1">Confiance<InfoTip content={TIPS.confiance} /></span></th>
<th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Analyste</th> <th className="px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase">Analyste</th>
</tr> </tr>
</thead> </thead>