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:
@ -388,7 +388,7 @@ export function BruteForceView() {
|
||||
Params combos
|
||||
<InfoTip content={TIPS.params_combos} />
|
||||
</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">
|
||||
Top JA4
|
||||
<InfoTip content={TIPS.ja4} />
|
||||
|
||||
@ -2,6 +2,8 @@ import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DataTable, { Column } from './ui/DataTable';
|
||||
import ThreatBadge from './ui/ThreatBadge';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@ -433,7 +435,7 @@ function ClusterCard({ cluster, expanded, ips, loadingIPs, onToggle, onNavigate
|
||||
</span>
|
||||
{cluster.asn && (
|
||||
<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>
|
||||
)}
|
||||
{cluster.primary_target && (
|
||||
@ -447,7 +449,7 @@ function ClusterCard({ cluster, expanded, ips, loadingIPs, onToggle, onNavigate
|
||||
<div className="space-y-1.5">
|
||||
{cluster.ja4 && (
|
||||
<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">
|
||||
{cluster.ja4}
|
||||
</code>
|
||||
@ -477,7 +479,10 @@ function ClusterCard({ cluster, expanded, ips, loadingIPs, onToggle, onNavigate
|
||||
{/* Score bar */}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
<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">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">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" />
|
||||
</tr>
|
||||
</thead>
|
||||
@ -759,6 +764,7 @@ function BehavioralMatrix({ clusters }: { clusters: ClusterData[] }) {
|
||||
{
|
||||
key: 'score',
|
||||
label: 'Score',
|
||||
tooltip: TIPS.risk_score_inv,
|
||||
align: 'right',
|
||||
render: (v: number) => (
|
||||
<span className={`text-xs font-mono font-semibold ${getConfidenceTextColor(v / 100)}`}>
|
||||
@ -774,6 +780,7 @@ function BehavioralMatrix({ clusters }: { clusters: ClusterData[] }) {
|
||||
{
|
||||
key: 'trend',
|
||||
label: 'Tendance',
|
||||
tooltip: TIPS.tendance,
|
||||
sortable: false,
|
||||
render: (_: string, row: BehavioralRow) =>
|
||||
row.trend === 'up' ? (
|
||||
@ -916,7 +923,7 @@ function BotnetRow({ item, onInvestigate }: { item: BotnetItem; onInvestigate: (
|
||||
</div>
|
||||
</td>
|
||||
<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 className="px-4 py-3">
|
||||
<button
|
||||
@ -1046,7 +1053,7 @@ function BotnetTab() {
|
||||
<th className="px-4 py-3">IPs distinctes</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">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"></th>
|
||||
</tr>
|
||||
|
||||
@ -15,6 +15,8 @@ import ReactFlow, {
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import { useEffect, useState, useCallback, memo } from 'react';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@ -510,7 +512,7 @@ function GraphInner({ rawData, loading, error, filters, toggleFilter, height, ip
|
||||
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"
|
||||
>
|
||||
{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>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
interface EntityStats {
|
||||
entity_type: string;
|
||||
@ -194,7 +196,7 @@ export function EntityInvestigationView() {
|
||||
|
||||
{/* Panel 2: JA4 Fingerprints */}
|
||||
<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">
|
||||
{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">
|
||||
@ -251,7 +253,7 @@ export function EntityInvestigationView() {
|
||||
|
||||
{/* Panel 4: Client Headers */}
|
||||
<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">
|
||||
{data.client_headers.slice(0, 10).map((header, idx) => (
|
||||
<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">
|
||||
{/* ASNs */}
|
||||
<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">
|
||||
{data.related.asns.slice(0, 10).map((asn, idx) => (
|
||||
<div key={idx} className="bg-background-card rounded-lg p-3">
|
||||
|
||||
@ -2,6 +2,8 @@ import { useState, useEffect, useCallback, Fragment, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DataTable, { Column } from './ui/DataTable';
|
||||
import ThreatBadge from './ui/ThreatBadge';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@ -489,7 +491,7 @@ function SpoofingPanel() {
|
||||
<span className="text-sm font-semibold text-text-primary">
|
||||
JA4 suspects de spoofing — {data.total} fingerprints analysés
|
||||
</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 className="divide-y divide-background-card">
|
||||
{data.items.length === 0 && (
|
||||
@ -563,11 +565,11 @@ function SpoofingPanel() {
|
||||
{/* Indicators */}
|
||||
<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: 'Browser score', value: `${item.indicators.avg_browser_score}/100`, warn: item.indicators.avg_browser_score > 50 },
|
||||
{ label: 'SNI mismatch', value: `${item.indicators.sni_mismatch_pct}%`, warn: item.indicators.sni_mismatch_pct > 10 },
|
||||
{ label: 'JA4 rare', value: `${item.indicators.rare_ja4_pct}%`, warn: item.indicators.rare_ja4_pct > 50 },
|
||||
{ label: 'UA rotation', value: `${item.indicators.ua_rotating_pct}%`, warn: item.indicators.ua_rotating_pct > 10 },
|
||||
{ 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, tip: TIPS.browser_score },
|
||||
{ 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, tip: TIPS.ja4_rare_pct },
|
||||
{ label: 'UA rotation', value: `${item.indicators.ua_rotating_pct}%`, warn: item.indicators.ua_rotating_pct > 10, tip: TIPS.ua_rotation },
|
||||
].map((ind) => (
|
||||
<div
|
||||
key={ind.label}
|
||||
@ -578,7 +580,7 @@ function SpoofingPanel() {
|
||||
<div className={`text-sm font-semibold ${ind.warn ? 'text-threat-high' : 'text-text-primary'}`}>
|
||||
{ind.value}
|
||||
</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>
|
||||
@ -603,8 +605,8 @@ function SpoofingPanel() {
|
||||
{activeSubTab === 'matrix' && (
|
||||
<div className="bg-background-secondary rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-background-card">
|
||||
<span className="text-sm font-semibold text-text-primary">
|
||||
Matrice JA4 × User-Agent — {matrixData.length} fingerprints
|
||||
<span className="text-sm font-semibold text-text-primary flex items-center gap-1">
|
||||
Matrice JA4 × User-Agent — {matrixData.length} fingerprints<InfoTip content={TIPS.ja4} />
|
||||
</span>
|
||||
<p className="text-xs text-text-disabled mt-0.5">
|
||||
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')}
|
||||
>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
Score botnet
|
||||
Score botnet<InfoTip content={TIPS.risk_score_inv} />
|
||||
{sortField === 'botnet_score'
|
||||
? <span className="text-accent-primary">{sortDir === 'desc' ? '↓' : '↑'}</span>
|
||||
: <span className="text-text-disabled opacity-50">⇅</span>}
|
||||
|
||||
@ -241,12 +241,14 @@ export function HeaderFingerprintView() {
|
||||
{
|
||||
key: 'sec_fetch_mode',
|
||||
label: 'Sec-Fetch Mode',
|
||||
tooltip: TIPS.sec_fetch_dest,
|
||||
sortable: true,
|
||||
render: (v) => <span className="text-text-secondary">{v || '—'}</span>,
|
||||
},
|
||||
{
|
||||
key: 'sec_fetch_dest',
|
||||
label: 'Sec-Fetch Dest',
|
||||
tooltip: TIPS.sec_fetch_dest,
|
||||
sortable: true,
|
||||
render: (v) => <span className="text-text-secondary">{v || '—'}</span>,
|
||||
},
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
interface IncidentCluster {
|
||||
id: string;
|
||||
@ -147,10 +149,10 @@ export function IncidentsView() {
|
||||
{baseline && (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{([
|
||||
{ key: 'total_detections', label: 'Détections 24h', icon: '📊' },
|
||||
{ key: 'unique_ips', label: 'IPs uniques', icon: '🖥️' },
|
||||
{ key: 'critical_alerts', label: 'Alertes CRITICAL', icon: '🔴' },
|
||||
] as { key: keyof BaselineData; label: string; icon: string }[]).map(({ key, label, icon }) => {
|
||||
{ key: 'total_detections', label: 'Détections 24h', icon: '📊', tip: TIPS.total_detections_stat },
|
||||
{ key: 'unique_ips', label: 'IPs uniques', icon: '🖥️', tip: TIPS.unique_ips_stat },
|
||||
{ key: 'critical_alerts', label: 'Alertes CRITICAL', icon: '🔴', tip: TIPS.risk_critical },
|
||||
] as { key: keyof BaselineData; label: string; icon: string; tip: string }[]).map(({ key, label, icon, tip }) => {
|
||||
const m = baseline[key];
|
||||
const up = 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">
|
||||
<span className="text-xl">{icon}</span>
|
||||
<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-xs text-text-secondary">hier: {m.yesterday.toLocaleString('fr-FR')}</div>
|
||||
</div>
|
||||
@ -292,7 +294,7 @@ export function IncidentsView() {
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start justify-between mb-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}
|
||||
</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="text-right">
|
||||
<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>
|
||||
@ -327,11 +329,11 @@ export function IncidentsView() {
|
||||
</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>
|
||||
<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 ${
|
||||
cluster.trend === 'up' ? 'text-red-500' :
|
||||
cluster.trend === 'down' ? 'text-green-500' :
|
||||
@ -344,7 +346,7 @@ export function IncidentsView() {
|
||||
|
||||
{cluster.ja4 && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { JA4CorrelationSummary } from './analysis/JA4CorrelationSummary';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
interface JA4InvestigationData {
|
||||
ja4: string;
|
||||
@ -164,7 +166,7 @@ export function JA4InvestigationView() {
|
||||
<div className="bg-background-secondary rounded-lg p-6">
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<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">
|
||||
{data.ja4}
|
||||
</div>
|
||||
@ -176,7 +178,7 @@ export function JA4InvestigationView() {
|
||||
</div>
|
||||
|
||||
<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="Dernière détection" value={formatDate(data.last_seen)} />
|
||||
<StatBox label="User-Agents" value={data.user_agents.length.toString()} />
|
||||
@ -247,7 +249,7 @@ export function JA4InvestigationView() {
|
||||
</div>
|
||||
|
||||
<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">
|
||||
{data.top_asns.map((asn, idx) => (
|
||||
<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 (
|
||||
<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>
|
||||
);
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@ -47,11 +49,11 @@ interface VariabilityData {
|
||||
|
||||
type AttrKey = keyof VariabilityData['attributes'];
|
||||
|
||||
const ATTR_ROWS: { key: AttrKey; label: string; icon: string }[] = [
|
||||
{ key: 'ja4', label: 'JA4 Fingerprint', icon: '🔐' },
|
||||
const ATTR_ROWS: { key: AttrKey; label: string; icon: string; tip?: string }[] = [
|
||||
{ key: 'ja4', label: 'JA4 Fingerprint', icon: '🔐', tip: TIPS.ja4 },
|
||||
{ key: 'user_agents', label: 'User-Agents', 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: 'subnets', label: 'Subnets', icon: '🔷' },
|
||||
];
|
||||
@ -283,6 +285,7 @@ export function PivotView() {
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span>{row.icon}</span>
|
||||
<span className="text-xs font-medium text-text-secondary">{row.label}</span>
|
||||
{row.tip && <InfoTip content={row.tip} />}
|
||||
</div>
|
||||
{shared.size > 0 && (
|
||||
<div className="mt-1">
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
interface SubnetIP {
|
||||
ip: string;
|
||||
@ -139,7 +141,7 @@ export function SubnetInvestigation() {
|
||||
<div className="text-2xl font-bold text-text-primary">{stats.total_detections.toLocaleString()}</div>
|
||||
</div>
|
||||
<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>
|
||||
<div className="bg-background-card rounded-lg p-4">
|
||||
@ -161,7 +163,7 @@ export function SubnetInvestigation() {
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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">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">Menace</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">Menace<InfoTip content={TIPS.threat_level} /></span></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>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { InfoTip } from './ui/Tooltip';
|
||||
import { TIPS } from './ui/tooltips';
|
||||
|
||||
interface Classification {
|
||||
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">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">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>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
Reference in New Issue
Block a user