Files
dashboard/frontend/src/components/DetailsView.tsx
SOC Analyst 1455e04303 fix: correct CampaignsView, analysis.py IPv4 split, entities date filter
- CampaignsView: update ClusterData interface to match real API response
  (severity/unique_ips/score instead of threat_level/total_ips/confidence_range)
  Fix fetch to use data.items, rewrite ClusterCard and BehavioralTab
  Remove unused getClassificationColor and THREAT_ORDER constants
- analysis.py: fix IPv4Address object has no attribute 'split' on line 322
  Add str() conversion before calling .split('.')
- entities.py: fix Date vs DateTime comparison — log_date is a Date column,
  comparing against now()-INTERVAL HOUR caused yesterday's entries to be excluded
  Use toDate(now() - INTERVAL X HOUR) for correct Date-level comparison

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-15 23:10:35 +01:00

173 lines
5.8 KiB
TypeScript

import { useParams, useNavigate, Link } from 'react-router-dom';
import { useVariability } from '../hooks/useVariability';
import { VariabilityPanel } from './VariabilityPanel';
export function DetailsView() {
const { type, value } = useParams<{ type: string; value: string }>();
const navigate = useNavigate();
const { data, loading, error } = useVariability(type || '', value || '');
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-text-secondary">Chargement...</div>
</div>
);
}
if (error) {
return (
<div className="bg-threat-critical_bg border border-threat-critical rounded-lg p-4">
<p className="text-threat-critical">Erreur: {error.message}</p>
<button
onClick={() => navigate('/detections')}
className="mt-4 bg-accent-primary hover:bg-accent-primary/80 text-white px-4 py-2 rounded-lg transition-colors"
>
Retour aux détections
</button>
</div>
);
}
if (!data) return null;
const typeLabels: Record<string, { label: string }> = {
ip: { label: 'IP' },
ja4: { label: 'JA4' },
country: { label: 'Pays' },
asn: { label: 'ASN' },
host: { label: 'Host' },
user_agent: { label: 'User-Agent' },
};
const typeInfo = typeLabels[type || ''] || { label: type };
return (
<div className="space-y-6 animate-fade-in">
{/* Breadcrumb */}
<nav className="flex items-center gap-2 text-sm text-text-secondary">
<Link to="/" className="hover:text-text-primary transition-colors">Dashboard</Link>
<span>/</span>
<Link to="/detections" className="hover:text-text-primary transition-colors">Détections</Link>
<span>/</span>
<span className="text-text-primary">{typeInfo.label}: {value}</span>
</nav>
{/* En-tête */}
<div className="bg-background-secondary rounded-lg p-6">
<div className="flex items-start justify-between">
<div>
<h1 className="text-2xl font-bold text-text-primary mb-2">
{typeInfo.label}
</h1>
<p className="font-mono text-text-secondary break-all">{value}</p>
</div>
<div className="text-right">
<div className="text-3xl font-bold text-text-primary">{data.total_detections}</div>
<div className="text-text-secondary text-sm">détections (24h)</div>
{type === 'ip' && value && (
<button
onClick={() => navigate(`/investigation/${encodeURIComponent(value)}`)}
className="mt-2 bg-accent-primary hover:bg-accent-primary/80 text-white px-4 py-2 rounded-lg text-sm transition-colors"
>
🔍 Investigation complète
</button>
)}
{type === 'ja4' && value && (
<button
onClick={() => navigate(`/investigation/ja4/${encodeURIComponent(value)}`)}
className="mt-2 bg-accent-primary hover:bg-accent-primary/80 text-white px-4 py-2 rounded-lg text-sm transition-colors"
>
🔍 Investigation JA4
</button>
)}
</div>
</div>
{/* Stats rapides */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-6">
<StatBox
label="IPs Uniques"
value={data.unique_ips.toLocaleString()}
/>
<StatBox
label="Première détection"
value={formatDate(data.date_range.first_seen)}
/>
<StatBox
label="Dernière détection"
value={formatDate(data.date_range.last_seen)}
/>
<StatBox
label="User-Agents"
value={data.attributes.user_agents.length.toString()}
/>
</div>
</div>
{/* Insights + Variabilité côte à côte */}
<div className="grid grid-cols-3 gap-6 items-start">
{data.insights.length > 0 && (
<div className="space-y-2">
<h2 className="text-lg font-semibold text-text-primary">Insights</h2>
{data.insights.map((insight, i) => (
<InsightCard key={i} insight={insight} />
))}
</div>
)}
<div className={data.insights.length > 0 ? 'col-span-2' : 'col-span-3'}>
<VariabilityPanel attributes={data.attributes} />
</div>
</div>
{/* Bouton retour */}
<div className="flex justify-center">
<button
onClick={() => navigate('/detections')}
className="bg-background-card hover:bg-background-card/80 text-text-primary px-6 py-3 rounded-lg transition-colors"
>
Retour aux détections
</button>
</div>
</div>
);
}
// Composant StatBox
function StatBox({ label, value }: { label: string; value: string }) {
return (
<div className="bg-background-card rounded-lg p-4">
<div className="text-xl font-bold text-text-primary">{value}</div>
<div className="text-text-secondary text-xs">{label}</div>
</div>
);
}
// Composant InsightCard
function InsightCard({ insight }: { insight: { type: string; message: string } }) {
const styles: Record<string, string> = {
warning: 'bg-yellow-500/10 border-yellow-500/50 text-yellow-500',
info: 'bg-blue-500/10 border-blue-500/50 text-blue-400',
success: 'bg-green-500/10 border-green-500/50 text-green-400',
};
return (
<div className={`${styles[insight.type] || styles.info} border rounded-lg p-4`}>
<span>{insight.message}</span>
</div>
);
}
// Helper pour formater la date
function formatDate(dateStr: string): string {
const date = new Date(dateStr);
return date.toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}