From 7171c3d6077c6c49091cd807bb99128717660457 Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Fri, 21 Nov 2025 09:00:50 +0000 Subject: [PATCH] Add new pages for ML training and IP whitelisting Adds new frontend pages for ML training and IP whitelisting, along with backend API endpoints to trigger ML actions and manage the whitelist. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: d6b17610-31b4-4ed0-a52a-66659e36d756 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/Aqah4U9 --- .replit | 4 + client/src/App.tsx | 7 +- client/src/pages/Training.tsx | 421 +++++++++++++++++++++++++++++++++ client/src/pages/Whitelist.tsx | 234 ++++++++++++++++++ server/routes.ts | 58 +++++ 5 files changed, 723 insertions(+), 1 deletion(-) create mode 100644 client/src/pages/Training.tsx create mode 100644 client/src/pages/Whitelist.tsx 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" + /> +
+
+ +