suite des maj
This commit is contained in:
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user