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
184 lines
7.9 KiB
TypeScript
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>
|
|
);
|
|
}
|