suite des maj

This commit is contained in:
SOC Analyst
2026-03-18 09:00:47 +01:00
parent 446d3623ec
commit 32a96966dd
17 changed files with 2398 additions and 755 deletions

View File

@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import DataTable, { Column } from './ui/DataTable';
// ─── Types ────────────────────────────────────────────────────────────────────
@ -321,6 +322,89 @@ function ScatterPlot({ points }: { points: ScatterPoint[] }) {
);
}
// ─── Anomalies DataTable ─────────────────────────────────────────────────────
function AnomaliesTable({
anomalies,
selectedIP,
onRowClick,
}: {
anomalies: MLAnomaly[];
selectedIP: string | null;
onRowClick: (row: MLAnomaly) => void;
}) {
const columns = useMemo((): Column<MLAnomaly>[] => [
{
key: 'ip',
label: 'IP',
render: (v: string, row: MLAnomaly) => (
<span className={`font-mono text-xs ${selectedIP === row.ip ? 'text-accent-primary' : 'text-text-primary'}`}>
{v}
</span>
),
},
{
key: 'host',
label: 'Host',
render: (v: string) => (
<span className="text-text-secondary max-w-[120px] truncate block" title={v}>
{v || '—'}
</span>
),
},
{
key: 'hits',
label: 'Hits',
align: 'right',
render: (v: number) => formatNumber(v),
},
{
key: 'fuzzing_index',
label: 'Fuzzing',
align: 'right',
render: (v: number) => (
<span className={`px-1.5 py-0.5 rounded text-xs font-semibold ${fuzzingBadgeClass(v)}`}>
{Math.round(v)}
</span>
),
},
{
key: 'attack_type',
label: 'Type',
render: (v: string) => (
<span title={v} className="text-sm">{attackTypeEmoji(v)}</span>
),
},
{
key: '_signals',
label: 'Signaux',
sortable: false,
render: (_: unknown, row: MLAnomaly) => (
<span className="flex gap-0.5">
{row.ua_ch_mismatch && <span title="UA/CH mismatch"></span>}
{row.is_fake_navigation && <span title="Fausse navigation">🎭</span>}
{row.is_ua_rotating && <span title="UA rotatif">🔄</span>}
{row.sni_host_mismatch && <span title="SNI mismatch">🌐</span>}
</span>
),
},
], [selectedIP]);
return (
<div className="overflow-auto max-h-[500px]">
<DataTable
data={anomalies}
columns={columns}
rowKey="ip"
defaultSortKey="fuzzing_index"
onRowClick={onRowClick}
emptyMessage="Aucune anomalie détectée"
compact
/>
</div>
);
}
// ─── Main Component ───────────────────────────────────────────────────────────
export function MLFeaturesView() {
@ -412,57 +496,11 @@ export function MLFeaturesView() {
) : anomaliesError ? (
<div className="p-4"><ErrorMessage message={anomaliesError} /></div>
) : (
<div className="overflow-auto max-h-[500px]">
<table className="w-full text-xs">
<thead className="sticky top-0 bg-background-secondary z-10">
<tr className="border-b border-border text-text-secondary text-left">
<th className="px-3 py-2">IP</th>
<th className="px-3 py-2">Host</th>
<th className="px-3 py-2">Hits</th>
<th className="px-3 py-2">Fuzzing</th>
<th className="px-3 py-2">Type</th>
<th className="px-3 py-2">Signaux</th>
</tr>
</thead>
<tbody>
{anomalies.map((item) => (
<tr
key={item.ip}
onClick={() => loadRadar(item.ip)}
className={`border-b border-border cursor-pointer transition-colors ${
selectedIP === item.ip
? 'bg-accent-primary/10'
: 'hover:bg-background-card'
}`}
>
<td className="px-3 py-2 font-mono text-text-primary">{item.ip}</td>
<td className="px-3 py-2 text-text-secondary max-w-[120px] truncate" title={item.host}>
{item.host || ''}
</td>
<td className="px-3 py-2 text-text-primary">{formatNumber(item.hits)}</td>
<td className="px-3 py-2">
<span className={`px-1.5 py-0.5 rounded text-xs font-semibold ${fuzzingBadgeClass(item.fuzzing_index)}`}>
{Math.round(item.fuzzing_index)}
</span>
</td>
<td className="px-3 py-2">
<span title={item.attack_type} className="text-sm">
{attackTypeEmoji(item.attack_type)}
</span>
</td>
<td className="px-3 py-2">
<span className="flex gap-0.5">
{item.ua_ch_mismatch && <span title="UA/CH mismatch">⚠️</span>}
{item.is_fake_navigation && <span title="Fausse navigation">🎭</span>}
{item.is_ua_rotating && <span title="UA rotatif">🔄</span>}
{item.sni_host_mismatch && <span title="SNI mismatch">🌐</span>}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
<AnomaliesTable
anomalies={anomalies}
selectedIP={selectedIP}
onRowClick={(row) => loadRadar(row.ip)}
/>
)}
</div>
</div>