diff --git a/.replit b/.replit index 0e0ad5e..26a22e3 100644 --- a/.replit +++ b/.replit @@ -14,6 +14,10 @@ run = ["npm", "run", "start"] localPort = 5000 externalPort = 80 +[[ports]] +localPort = 43079 +externalPort = 3001 + [[ports]] localPort = 43803 externalPort = 3000 diff --git a/client/src/App.tsx b/client/src/App.tsx index 1ffb3ca..3c3827b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,15 +4,18 @@ import { QueryClientProvider } from "@tanstack/react-query"; import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import { SidebarProvider, Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarTrigger } from "@/components/ui/sidebar"; -import { LayoutDashboard, AlertTriangle, Server, Shield, Menu } from "lucide-react"; +import { LayoutDashboard, AlertTriangle, Server, Shield, Brain, Menu } from "lucide-react"; import Dashboard from "@/pages/Dashboard"; import Detections from "@/pages/Detections"; import Routers from "@/pages/Routers"; +import Whitelist from "@/pages/Whitelist"; +import Training from "@/pages/Training"; import NotFound from "@/pages/not-found"; const menuItems = [ { title: "Dashboard", url: "/", icon: LayoutDashboard }, { title: "Rilevamenti", url: "/detections", icon: AlertTriangle }, + { title: "Training ML", url: "/training", icon: Brain }, { title: "Router", url: "/routers", icon: Server }, { title: "Whitelist", url: "/whitelist", icon: Shield }, ]; @@ -48,7 +51,9 @@ function Router() { + + ); diff --git a/client/src/pages/Training.tsx b/client/src/pages/Training.tsx new file mode 100644 index 0000000..e48bf57 --- /dev/null +++ b/client/src/pages/Training.tsx @@ -0,0 +1,421 @@ +import { useQuery, useMutation } from "@tanstack/react-query"; +import { queryClient, apiRequest } from "@/lib/queryClient"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Brain, Play, Search, CheckCircle2, XCircle, Clock, TrendingUp } from "lucide-react"; +import { format } from "date-fns"; +import type { TrainingHistory } from "@shared/schema"; +import { useToast } from "@/hooks/use-toast"; +import { useState } from "react"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, + DialogFooter, +} from "@/components/ui/dialog"; + +interface MLStatsResponse { + logs?: { total: number; last_hour: number }; + detections?: { total: number; blocked: number }; + routers?: { active: number }; + latest_training?: any; +} + +export default function TrainingPage() { + const { toast } = useToast(); + const [isTrainDialogOpen, setIsTrainDialogOpen] = useState(false); + const [isDetectDialogOpen, setIsDetectDialogOpen] = useState(false); + const [trainRecords, setTrainRecords] = useState("100000"); + const [trainHours, setTrainHours] = useState("24"); + const [detectRecords, setDetectRecords] = useState("50000"); + const [detectHours, setDetectHours] = useState("1"); + const [detectThreshold, setDetectThreshold] = useState("75"); + const [detectAutoBlock, setDetectAutoBlock] = useState(true); + + const { data: history, isLoading } = useQuery({ + queryKey: ["/api/training-history"], + refetchInterval: 10000, + }); + + const { data: mlStats } = useQuery({ + queryKey: ["/api/ml/stats"], + refetchInterval: 10000, + }); + + const trainMutation = useMutation({ + mutationFn: async (params: { max_records: number; hours_back: number }) => { + return await apiRequest("POST", "/api/ml/train", params); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/training-history"] }); + queryClient.invalidateQueries({ queryKey: ["/api/ml/stats"] }); + toast({ + title: "Training avviato", + description: "Il modello ML è in addestramento. Controlla lo storico tra qualche minuto.", + }); + setIsTrainDialogOpen(false); + }, + onError: () => { + toast({ + title: "Errore", + description: "Impossibile avviare il training", + variant: "destructive", + }); + }, + }); + + const detectMutation = useMutation({ + mutationFn: async (params: { max_records: number; hours_back: number; risk_threshold: number; auto_block: boolean }) => { + return await apiRequest("POST", "/api/ml/detect", params); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/detections"] }); + queryClient.invalidateQueries({ queryKey: ["/api/stats"] }); + toast({ + title: "Detection avviata", + description: "Analisi anomalie in corso. Controlla i rilevamenti tra qualche secondo.", + }); + setIsDetectDialogOpen(false); + }, + onError: () => { + toast({ + title: "Errore", + description: "Impossibile avviare la detection", + variant: "destructive", + }); + }, + }); + + const handleTrain = () => { + const records = parseInt(trainRecords); + const hours = parseInt(trainHours); + + if (isNaN(records) || records <= 0) { + toast({ + title: "Errore", + description: "Inserisci un numero valido di record", + variant: "destructive", + }); + return; + } + + trainMutation.mutate({ max_records: records, hours_back: hours }); + }; + + const handleDetect = () => { + const records = parseInt(detectRecords); + const hours = parseInt(detectHours); + const threshold = parseInt(detectThreshold); + + if (isNaN(records) || records <= 0 || isNaN(threshold) || threshold < 0 || threshold > 100) { + toast({ + title: "Errore", + description: "Inserisci valori validi", + variant: "destructive", + }); + return; + } + + detectMutation.mutate({ + max_records: records, + hours_back: hours, + risk_threshold: threshold, + auto_block: detectAutoBlock, + }); + }; + + return ( +
+
+

Machine Learning

+

+ Training e detection del modello Isolation Forest +

+
+ + {/* ML Stats */} + {mlStats && ( +
+ + + Log Totali + + + +
+ {mlStats.logs?.total?.toLocaleString() || 0} +
+

+ Ultima ora: {mlStats.logs?.last_hour?.toLocaleString() || 0} +

+
+
+ + + + Detection Totali + + + +
+ {mlStats.detections?.total || 0} +
+

+ Bloccati: {mlStats.detections?.blocked || 0} +

+
+
+ + + + Router Attivi + + + +
+ {mlStats.routers?.active || 0} +
+
+
+
+ )} + + {/* Actions */} +
+ + + + + Addestramento Modello + + + +

+ Addestra il modello Isolation Forest analizzando i log recenti per rilevare pattern di traffico normale. +

+ + + + + + + Avvia Training ML + + Configura i parametri per l'addestramento del modello + + +
+
+ + setTrainRecords(e.target.value)} + data-testid="input-train-records" + /> +

Consigliato: 100000

+
+
+ + setTrainHours(e.target.value)} + data-testid="input-train-hours" + /> +

Consigliato: 24

+
+
+ + + + +
+
+
+
+ + + + + + Rilevamento Anomalie + + + +

+ Analizza i log recenti per rilevare anomalie e IP sospetti. Opzionalmente blocca automaticamente gli IP critici. +

+ + + + + + + Avvia Detection Anomalie + + Configura i parametri per il rilevamento anomalie + + +
+
+ + setDetectRecords(e.target.value)} + data-testid="input-detect-records" + /> +

Consigliato: 50000

+
+
+ + setDetectHours(e.target.value)} + data-testid="input-detect-hours" + /> +

Consigliato: 1

+
+
+ + setDetectThreshold(e.target.value)} + data-testid="input-detect-threshold" + /> +

Consigliato: 75

+
+
+ setDetectAutoBlock(e.target.checked)} + className="rounded border-gray-300" + data-testid="checkbox-auto-block" + /> + +
+
+ + + + +
+
+
+
+
+ + {/* Training History */} + + + + + Storico Training ({history?.length || 0}) + + + + {isLoading ? ( +
+ Caricamento... +
+ ) : history && history.length > 0 ? ( +
+ {history.map((item) => ( +
+
+
+
+

+ Versione {item.modelVersion} +

+ {item.status === "success" ? ( + + + Successo + + ) : ( + + + Fallito + + )} +
+
+
+ Record: {item.recordsProcessed.toLocaleString()} +
+
+ Feature: {item.featuresCount} +
+ {item.accuracy && ( +
+ Accuracy: {item.accuracy}% +
+ )} + {item.trainingDuration && ( +
+ Durata: {item.trainingDuration}s +
+ )} +
+

+ {format(new Date(item.trainedAt), "dd/MM/yyyy HH:mm:ss")} +

+ {item.notes && ( +

+ {item.notes} +

+ )} +
+
+
+ ))} +
+ ) : ( +
+ +

Nessun training eseguito

+

Avvia il primo training per addestrare il modello ML

+
+ )} +
+
+
+ ); +} diff --git a/client/src/pages/Whitelist.tsx b/client/src/pages/Whitelist.tsx new file mode 100644 index 0000000..ab5a2aa --- /dev/null +++ b/client/src/pages/Whitelist.tsx @@ -0,0 +1,234 @@ +import { useQuery, useMutation } from "@tanstack/react-query"; +import { queryClient, apiRequest } from "@/lib/queryClient"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Shield, Plus, Trash2, CheckCircle2, XCircle } from "lucide-react"; +import { format } from "date-fns"; +import { useState } from "react"; +import type { Whitelist } from "@shared/schema"; +import { useToast } from "@/hooks/use-toast"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, + DialogFooter, +} from "@/components/ui/dialog"; + +export default function WhitelistPage() { + const { toast } = useToast(); + const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); + const [newIp, setNewIp] = useState(""); + const [newComment, setNewComment] = useState(""); + const [newReason, setNewReason] = useState(""); + + const { data: whitelist, isLoading } = useQuery({ + queryKey: ["/api/whitelist"], + }); + + const addMutation = useMutation({ + mutationFn: async (data: { ipAddress: string; comment?: string; reason?: string }) => { + return await apiRequest("POST", "/api/whitelist", data); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/whitelist"] }); + toast({ + title: "IP aggiunto", + description: "L'indirizzo IP è stato aggiunto alla whitelist", + }); + setIsAddDialogOpen(false); + setNewIp(""); + setNewComment(""); + setNewReason(""); + }, + onError: () => { + toast({ + title: "Errore", + description: "Impossibile aggiungere l'IP alla whitelist", + variant: "destructive", + }); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: async (id: string) => { + await apiRequest("DELETE", `/api/whitelist/${id}`); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/whitelist"] }); + toast({ + title: "IP rimosso", + description: "L'indirizzo IP è stato rimosso dalla whitelist", + }); + }, + onError: () => { + toast({ + title: "Errore", + description: "Impossibile rimuovere l'IP dalla whitelist", + variant: "destructive", + }); + }, + }); + + const handleAdd = () => { + if (!newIp.trim()) { + toast({ + title: "Errore", + description: "Inserisci un indirizzo IP valido", + variant: "destructive", + }); + return; + } + + addMutation.mutate({ + ipAddress: newIp.trim(), + comment: newComment.trim() || undefined, + reason: newReason.trim() || undefined, + }); + }; + + return ( +
+
+
+

Whitelist IP

+

+ Gestisci gli indirizzi IP fidati che non verranno bloccati +

+
+ + + + + + + + Aggiungi IP alla Whitelist + + Inserisci l'indirizzo IP che vuoi proteggere dal blocco automatico + + +
+
+ + setNewIp(e.target.value)} + data-testid="input-ip" + /> +
+
+ + setNewReason(e.target.value)} + data-testid="input-reason" + /> +
+
+ +