ids.alfacom.it/client/src/pages/Detections.tsx
marco370 61df9c4f4d Remove unused details button and related icon
Remove the "Dettagli" button and the Eye icon import from the Detections page as they are no longer needed.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 62bbad53-aa3a-4887-b48d-7203ea4974de
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/L6QSDnx
2025-11-25 10:29:18 +00:00

311 lines
14 KiB
TypeScript

import { useQuery, useMutation } 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Slider } from "@/components/ui/slider";
import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus } from "lucide-react";
import { format } from "date-fns";
import { useState } from "react";
import type { Detection } from "@shared/schema";
import { getFlag } from "@/lib/country-flags";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
export default function Detections() {
const [searchQuery, setSearchQuery] = useState("");
const [anomalyTypeFilter, setAnomalyTypeFilter] = useState<string>("all");
const [minScore, setMinScore] = useState(0);
const [maxScore, setMaxScore] = useState(100);
const { toast } = useToast();
// Build query params
const queryParams = new URLSearchParams();
queryParams.set("limit", "5000");
if (anomalyTypeFilter !== "all") {
queryParams.set("anomalyType", anomalyTypeFilter);
}
if (minScore > 0) {
queryParams.set("minScore", minScore.toString());
}
if (maxScore < 100) {
queryParams.set("maxScore", maxScore.toString());
}
const { data: detections, isLoading } = useQuery<Detection[]>({
queryKey: ["/api/detections", anomalyTypeFilter, minScore, maxScore],
queryFn: () => fetch(`/api/detections?${queryParams.toString()}`).then(r => r.json()),
refetchInterval: 5000,
});
const filteredDetections = detections?.filter((d) =>
d.sourceIp.toLowerCase().includes(searchQuery.toLowerCase()) ||
d.anomalyType.toLowerCase().includes(searchQuery.toLowerCase())
);
// Mutation per aggiungere a whitelist
const addToWhitelistMutation = useMutation({
mutationFn: async (detection: Detection) => {
return await apiRequest("POST", "/api/whitelist", {
ipAddress: detection.sourceIp,
reason: `Auto-added from detection: ${detection.anomalyType} (Risk: ${parseFloat(detection.riskScore).toFixed(1)})`
});
},
onSuccess: (_, detection) => {
toast({
title: "IP aggiunto alla whitelist",
description: `${detection.sourceIp} è stato aggiunto alla whitelist con successo.`,
});
queryClient.invalidateQueries({ queryKey: ["/api/whitelist"] });
queryClient.invalidateQueries({ queryKey: ["/api/detections"] });
},
onError: (error: any, detection) => {
toast({
title: "Errore",
description: error.message || `Impossibile aggiungere ${detection.sourceIp} alla whitelist.`,
variant: "destructive",
});
}
});
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 flex-col gap-4">
<div className="flex items-center gap-4 flex-wrap">
<div className="relative flex-1 min-w-[200px]">
<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>
<Select value={anomalyTypeFilter} onValueChange={setAnomalyTypeFilter}>
<SelectTrigger className="w-[200px]" data-testid="select-anomaly-type">
<SelectValue placeholder="Tipo attacco" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Tutti i tipi</SelectItem>
<SelectItem value="ddos">DDoS Attack</SelectItem>
<SelectItem value="port_scan">Port Scanning</SelectItem>
<SelectItem value="brute_force">Brute Force</SelectItem>
<SelectItem value="botnet">Botnet Activity</SelectItem>
<SelectItem value="suspicious">Suspicious Activity</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Risk Score:</span>
<span className="font-medium" data-testid="text-score-range">
{minScore} - {maxScore}
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-xs text-muted-foreground w-8">0</span>
<Slider
min={0}
max={100}
step={5}
value={[minScore, maxScore]}
onValueChange={([min, max]) => {
setMinScore(min);
setMaxScore(max);
}}
className="flex-1"
data-testid="slider-risk-score"
/>
<span className="text-xs text-muted-foreground w-8">100</span>
</div>
</div>
</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-3 mb-2 flex-wrap">
{/* Flag Emoji */}
{detection.countryCode && (
<span className="text-2xl" title={detection.country || detection.countryCode} data-testid={`flag-${detection.id}`}>
{getFlag(detection.country, detection.countryCode)}
</span>
)}
<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>
{/* Geolocation Info */}
{(detection.country || detection.organization || detection.asNumber) && (
<div className="flex flex-wrap gap-3 mb-3 text-sm" data-testid={`geo-info-${detection.id}`}>
{detection.country && (
<div className="flex items-center gap-1.5 text-muted-foreground">
<Globe className="h-3.5 w-3.5" />
<span data-testid={`text-country-${detection.id}`}>
{detection.city ? `${detection.city}, ${detection.country}` : detection.country}
</span>
</div>
)}
{detection.organization && (
<div className="flex items-center gap-1.5 text-muted-foreground">
<Building2 className="h-3.5 w-3.5" />
<span data-testid={`text-org-${detection.id}`}>{detection.organization}</span>
</div>
)}
{detection.asNumber && (
<div className="flex items-center gap-1.5 text-muted-foreground">
<MapPin className="h-3.5 w-3.5" />
<span data-testid={`text-as-${detection.id}`}>
{detection.asNumber} {detection.asName && `- ${detection.asName}`}
</span>
</div>
)}
</div>
)}
<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"
onClick={() => addToWhitelistMutation.mutate(detection)}
disabled={addToWhitelistMutation.isPending}
className="w-full"
data-testid={`button-whitelist-${detection.id}`}
>
<ShieldPlus className="h-3 w-3 mr-1" />
Whitelist
</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>
);
}