refactor: UI improvements and code cleanup
Frontend: - DetectionsList: Simplify columns, improve truncation and display for IPs, hosts, bot info - IncidentsView: Replace metric cards with compact stat cards (unique IPs, known bots, ML anomalies, threat levels) - InvestigationView: Add section navigation anchors, reorganize layout with proper IDs - ThreatIntelView: Add navigation links to investigation pages, add comment column, improve table layout Backend: - Various route and model adjustments - Configuration updates Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@ -30,6 +30,8 @@ interface MetricsSummary {
|
||||
medium_count: number;
|
||||
low_count: number;
|
||||
unique_ips: number;
|
||||
known_bots_count: number;
|
||||
anomalies_count: number;
|
||||
}
|
||||
|
||||
interface BaselineMetric {
|
||||
@ -135,81 +137,114 @@ export function IncidentsView() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
{/* Header with Quick Search */}
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-text-primary">SOC Dashboard</h1>
|
||||
<p className="text-text-secondary text-sm mt-1">
|
||||
Surveillance en temps réel - 24 dernières heures
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-text-secondary text-sm mt-1">Surveillance en temps réel · 24 dernières heures</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Baseline comparison */}
|
||||
{baseline && (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{([
|
||||
{ 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];
|
||||
{/* Stats unifiées — 6 cartes compact */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
{/* Total détections avec comparaison hier */}
|
||||
<div
|
||||
className="bg-background-card border border-border rounded-lg px-3 py-2.5 flex flex-col gap-0.5 cursor-pointer hover:border-accent-primary/50 transition-colors"
|
||||
onClick={() => navigate('/detections')}
|
||||
>
|
||||
<div className="text-[10px] text-text-disabled uppercase tracking-wide flex items-center gap-1">
|
||||
📊 Total 24h<InfoTip content={TIPS.total_detections_stat} />
|
||||
</div>
|
||||
<div className="text-xl font-bold text-text-primary">
|
||||
{(metrics?.total_detections ?? 0).toLocaleString()}
|
||||
</div>
|
||||
{baseline && (() => {
|
||||
const m = baseline.total_detections;
|
||||
const up = m.pct_change > 0;
|
||||
const neutral = m.pct_change === 0;
|
||||
return (
|
||||
<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 flex items-center gap-1">{label}<InfoTip content={tip} /></div>
|
||||
<div className="text-xl font-bold text-text-primary">{m.today.toLocaleString(navigator.language || undefined)}</div>
|
||||
<div className="text-xs text-text-secondary">hier: {m.yesterday.toLocaleString(navigator.language || undefined)}</div>
|
||||
</div>
|
||||
<div className={`text-sm font-bold px-2 py-1 rounded ${
|
||||
neutral ? 'text-text-disabled' :
|
||||
up ? 'text-threat-critical bg-threat-critical/10' :
|
||||
'text-threat-low bg-threat-low/10'
|
||||
}`}>
|
||||
{neutral ? '=' : up ? `▲ +${m.pct_change}%` : `▼ ${m.pct_change}%`}
|
||||
</div>
|
||||
<div className={`text-[10px] font-medium ${neutral ? 'text-text-disabled' : up ? 'text-threat-critical' : 'text-threat-low'}`}>
|
||||
{neutral ? '= même' : up ? `▲ +${m.pct_change}%` : `▼ ${m.pct_change}%`} vs hier
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Critical Metrics */}
|
||||
{metrics && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<MetricCard
|
||||
title="CRITICAL"
|
||||
value={metrics.critical_count.toLocaleString()}
|
||||
subtitle={metrics.critical_count > 0 ? 'Requiert action immédiate' : 'Aucune'}
|
||||
color="bg-red-500/20"
|
||||
trend={metrics.critical_count > 10 ? 'up' : 'stable'}
|
||||
/>
|
||||
<MetricCard
|
||||
title="HIGH"
|
||||
value={metrics.high_count.toLocaleString()}
|
||||
subtitle="Menaces élevées"
|
||||
color="bg-orange-500/20"
|
||||
trend="stable"
|
||||
/>
|
||||
<MetricCard
|
||||
title="MEDIUM"
|
||||
value={metrics.medium_count.toLocaleString()}
|
||||
subtitle="Menaces moyennes"
|
||||
color="bg-yellow-500/20"
|
||||
trend="stable"
|
||||
/>
|
||||
<MetricCard
|
||||
title="TOTAL"
|
||||
value={metrics.total_detections.toLocaleString()}
|
||||
subtitle={`${metrics.unique_ips.toLocaleString()} IPs uniques`}
|
||||
color="bg-blue-500/20"
|
||||
trend="stable"
|
||||
/>
|
||||
{/* IPs uniques */}
|
||||
<div
|
||||
className="bg-background-card border border-border rounded-lg px-3 py-2.5 flex flex-col gap-0.5 cursor-pointer hover:border-accent-primary/50 transition-colors"
|
||||
onClick={() => navigate('/detections')}
|
||||
>
|
||||
<div className="text-[10px] text-text-disabled uppercase tracking-wide flex items-center gap-1">
|
||||
🖥️ IPs uniques<InfoTip content={TIPS.unique_ips_stat} />
|
||||
</div>
|
||||
<div className="text-xl font-bold text-text-primary">
|
||||
{(metrics?.unique_ips ?? 0).toLocaleString()}
|
||||
</div>
|
||||
{baseline && (() => {
|
||||
const m = baseline.unique_ips;
|
||||
const up = m.pct_change > 0;
|
||||
const neutral = m.pct_change === 0;
|
||||
return (
|
||||
<div className={`text-[10px] font-medium ${neutral ? 'text-text-disabled' : up ? 'text-threat-critical' : 'text-threat-low'}`}>
|
||||
{neutral ? '= même' : up ? `▲ +${m.pct_change}%` : `▼ ${m.pct_change}%`} vs hier
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* BOT connus */}
|
||||
<div
|
||||
className="bg-green-500/10 border border-green-500/30 rounded-lg px-3 py-2.5 flex flex-col gap-0.5 cursor-pointer hover:border-green-500/60 transition-colors"
|
||||
onClick={() => navigate('/detections?score_type=BOT')}
|
||||
>
|
||||
<div className="text-[10px] text-green-400/80 uppercase tracking-wide">🤖 BOT nommés</div>
|
||||
<div className="text-xl font-bold text-green-400">
|
||||
{(metrics?.known_bots_count ?? 0).toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] text-green-400/60">
|
||||
{metrics ? Math.round((metrics.known_bots_count / metrics.total_detections) * 100) : 0}% du total
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Anomalies ML */}
|
||||
<div
|
||||
className="bg-purple-500/10 border border-purple-500/30 rounded-lg px-3 py-2.5 flex flex-col gap-0.5 cursor-pointer hover:border-purple-500/60 transition-colors"
|
||||
onClick={() => navigate('/detections?score_type=SCORE')}
|
||||
>
|
||||
<div className="text-[10px] text-purple-400/80 uppercase tracking-wide">🔬 Anomalies ML</div>
|
||||
<div className="text-xl font-bold text-purple-400">
|
||||
{(metrics?.anomalies_count ?? 0).toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] text-purple-400/60">
|
||||
{metrics ? Math.round((metrics.anomalies_count / metrics.total_detections) * 100) : 0}% du total
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HIGH */}
|
||||
<div
|
||||
className="bg-orange-500/10 border border-orange-500/30 rounded-lg px-3 py-2.5 flex flex-col gap-0.5 cursor-pointer hover:border-orange-500/60 transition-colors"
|
||||
onClick={() => navigate('/detections?threat_level=HIGH')}
|
||||
>
|
||||
<div className="text-[10px] text-orange-400/80 uppercase tracking-wide">⚠️ HIGH</div>
|
||||
<div className="text-xl font-bold text-orange-400">
|
||||
{(metrics?.high_count ?? 0).toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] text-orange-400/60">Menaces élevées</div>
|
||||
</div>
|
||||
|
||||
{/* MEDIUM */}
|
||||
<div
|
||||
className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg px-3 py-2.5 flex flex-col gap-0.5 cursor-pointer hover:border-yellow-500/60 transition-colors"
|
||||
onClick={() => navigate('/detections?threat_level=MEDIUM')}
|
||||
>
|
||||
<div className="text-[10px] text-yellow-400/80 uppercase tracking-wide">📊 MEDIUM</div>
|
||||
<div className="text-xl font-bold text-yellow-400">
|
||||
{(metrics?.medium_count ?? 0).toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] text-yellow-400/60">Menaces moyennes</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bulk Actions */}
|
||||
{selectedClusters.size > 0 && (
|
||||
@ -478,34 +513,6 @@ export function IncidentsView() {
|
||||
);
|
||||
}
|
||||
|
||||
// Metric Card Component
|
||||
function MetricCard({
|
||||
title,
|
||||
value,
|
||||
subtitle,
|
||||
color,
|
||||
trend
|
||||
}: {
|
||||
title: string;
|
||||
value: string | number;
|
||||
subtitle: string;
|
||||
color: string;
|
||||
trend: 'up' | 'down' | 'stable';
|
||||
}) {
|
||||
return (
|
||||
<div className={`${color} rounded-lg p-6`}>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-text-secondary text-sm font-medium">{title}</h3>
|
||||
<span className="text-lg">
|
||||
{trend === 'up' ? '↑' : trend === 'down' ? '↓' : '→'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-3xl font-bold text-text-primary">{value}</p>
|
||||
<p className="text-text-disabled text-xs mt-2">{subtitle}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Mini Heatmap ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface HeatmapHour {
|
||||
|
||||
Reference in New Issue
Block a user