ids.alfacom.it/client/src/pages/Detections.tsx
marco370 9c5293158f Add a navigation sidebar and dashboard to the IDS system
Introduces a new sidebar component in `client/src/App.tsx` for navigation, along with new pages for Dashboard, Detections, and Routers. The backend in `server/routes.ts` is updated to include API endpoints for managing routers, fetching network logs, and retrieving detection data.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 99f17f6a-6021-4354-9517-5610b878cb21
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/c9ITWqD
2025-11-15 11:16:44 +00:00

184 lines
7.9 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { AlertTriangle, Search, Shield, Eye } from "lucide-react";
import { format } from "date-fns";
import { useState } from "react";
import type { Detection } from "@shared/schema";
export default function Detections() {
const [searchQuery, setSearchQuery] = useState("");
const { data: detections, isLoading } = useQuery<Detection[]>({
queryKey: ["/api/detections"],
refetchInterval: 5000,
});
const filteredDetections = detections?.filter((d) =>
d.sourceIp.toLowerCase().includes(searchQuery.toLowerCase()) ||
d.anomalyType.toLowerCase().includes(searchQuery.toLowerCase())
);
const getRiskBadge = (riskScore: string) => {
const score = parseFloat(riskScore);
if (score >= 85) return <Badge variant="destructive">CRITICO</Badge>;
if (score >= 70) return <Badge className="bg-orange-500">ALTO</Badge>;
if (score >= 60) return <Badge className="bg-yellow-500">MEDIO</Badge>;
if (score >= 40) return <Badge variant="secondary">BASSO</Badge>;
return <Badge variant="outline">NORMALE</Badge>;
};
const getAnomalyTypeLabel = (type: string) => {
const labels: Record<string, string> = {
ddos: "DDoS Attack",
port_scan: "Port Scanning",
brute_force: "Brute Force",
botnet: "Botnet Activity",
suspicious: "Suspicious Activity"
};
return labels[type] || type;
};
return (
<div className="flex flex-col gap-6 p-6" data-testid="page-detections">
<div>
<h1 className="text-3xl font-semibold" data-testid="text-page-title">Rilevamenti</h1>
<p className="text-muted-foreground" data-testid="text-page-subtitle">
Anomalie rilevate dal sistema IDS
</p>
</div>
{/* Search and Filters */}
<Card data-testid="card-filters">
<CardContent className="pt-6">
<div className="flex items-center gap-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Cerca per IP o tipo anomalia..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
data-testid="input-search"
/>
</div>
<Button variant="outline" data-testid="button-refresh">
Aggiorna
</Button>
</div>
</CardContent>
</Card>
{/* Detections List */}
<Card data-testid="card-detections-list">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5" />
Rilevamenti ({filteredDetections?.length || 0})
</CardTitle>
</CardHeader>
<CardContent>
{isLoading ? (
<div className="text-center py-8 text-muted-foreground" data-testid="text-loading">
Caricamento...
</div>
) : filteredDetections && filteredDetections.length > 0 ? (
<div className="space-y-3">
{filteredDetections.map((detection) => (
<div
key={detection.id}
className="p-4 rounded-lg border hover-elevate"
data-testid={`detection-${detection.id}`}
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2 flex-wrap">
<code className="font-mono font-semibold text-lg" data-testid={`text-ip-${detection.id}`}>
{detection.sourceIp}
</code>
{getRiskBadge(detection.riskScore)}
<Badge variant="outline" data-testid={`badge-type-${detection.id}`}>
{getAnomalyTypeLabel(detection.anomalyType)}
</Badge>
</div>
<p className="text-sm text-muted-foreground mb-3" data-testid={`text-reason-${detection.id}`}>
{detection.reason}
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<p className="text-muted-foreground text-xs">Risk Score</p>
<p className="font-medium" data-testid={`text-risk-${detection.id}`}>
{parseFloat(detection.riskScore).toFixed(1)}/100
</p>
</div>
<div>
<p className="text-muted-foreground text-xs">Confidence</p>
<p className="font-medium" data-testid={`text-confidence-${detection.id}`}>
{parseFloat(detection.confidence).toFixed(1)}%
</p>
</div>
<div>
<p className="text-muted-foreground text-xs">Log Count</p>
<p className="font-medium" data-testid={`text-logs-${detection.id}`}>
{detection.logCount}
</p>
</div>
<div>
<p className="text-muted-foreground text-xs">Rilevato</p>
<p className="font-medium" data-testid={`text-detected-${detection.id}`}>
{format(new Date(detection.detectedAt), "dd/MM HH:mm")}
</p>
</div>
</div>
<div className="flex items-center gap-4 mt-3 text-xs text-muted-foreground">
<span data-testid={`text-first-seen-${detection.id}`}>
Prima: {format(new Date(detection.firstSeen), "dd/MM HH:mm:ss")}
</span>
<span data-testid={`text-last-seen-${detection.id}`}>
Ultima: {format(new Date(detection.lastSeen), "dd/MM HH:mm:ss")}
</span>
</div>
</div>
<div className="flex flex-col items-end gap-2">
{detection.blocked ? (
<Badge variant="destructive" className="flex items-center gap-1" data-testid={`badge-blocked-${detection.id}`}>
<Shield className="h-3 w-3" />
Bloccato
</Badge>
) : (
<Badge variant="outline" data-testid={`badge-active-${detection.id}`}>
Attivo
</Badge>
)}
<Button variant="outline" size="sm" asChild data-testid={`button-details-${detection.id}`}>
<a href={`/logs?ip=${detection.sourceIp}`}>
<Eye className="h-3 w-3 mr-1" />
Dettagli
</a>
</Button>
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12 text-muted-foreground" data-testid="text-no-results">
<AlertTriangle className="h-12 w-12 mx-auto mb-2 opacity-50" />
<p>Nessun rilevamento trovato</p>
{searchQuery && (
<p className="text-sm">Prova con un altro termine di ricerca</p>
)}
</div>
)}
</CardContent>
</Card>
</div>
);
}