Files
dashboard/frontend/src/components/DetailsView.tsx
SOC Analyst a61828d1e7 Initial commit: Bot Detector Dashboard for SOC Incident Response
🛡️ Dashboard complet pour l'analyse et la classification des menaces

Fonctionnalités principales:
- Visualisation des détections en temps réel (24h)
- Investigation multi-entités (IP, JA4, ASN, Host, User-Agent)
- Analyse de corrélation pour classification SOC
- Clustering automatique par subnet/JA4/UA
- Export des classifications pour ML

Composants:
- Backend: FastAPI (Python) + ClickHouse
- Frontend: React + TypeScript + TailwindCSS
- 6 routes API: metrics, detections, variability, attributes, analysis, entities
- 7 types d'entités investigables

Documentation ajoutée:
- NAVIGATION_GRAPH.md: Graph complet de navigation
- SOC_OPTIMIZATION_PROPOSAL.md: Proposition d'optimisation pour SOC
  • Réduction de 7 à 2 clics pour classification
  • Nouvelle vue /incidents clusterisée
  • Panel latéral d'investigation
  • Quick Search (Cmd+K)
  • Timeline interactive
  • Graph de corrélations

Sécurité:
- .gitignore configuré (exclut .env, secrets, node_modules)
- Credentials dans .env (à ne pas committer)

⚠️ Audit sécurité réalisé - Voir recommandations dans SOC_OPTIMIZATION_PROPOSAL.md

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-14 21:33:55 +01:00

170 lines
5.6 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 */}
{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>
)}
{/* Variabilité */}
<VariabilityPanel attributes={data.attributes} />
{/* 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'
});
}