- 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>
173 lines
5.8 KiB
TypeScript
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'
|
|
});
|
|
}
|