Removes the connection testing functionality and updates the default API port to 8729 for Mikrotik routers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 54ecaeb2-ec77-4629-8d8d-e3bc4f663bec Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/31VdIyL
536 lines
19 KiB
TypeScript
536 lines
19 KiB
TypeScript
import { useState } from "react";
|
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
|
import { queryClient, apiRequest } from "@/lib/queryClient";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
DialogFooter,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormDescription,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@/components/ui/form";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Server, Plus, Trash2, Edit } from "lucide-react";
|
|
import { format } from "date-fns";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { insertRouterSchema, type InsertRouter } from "@shared/schema";
|
|
import type { Router } from "@shared/schema";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
|
export default function Routers() {
|
|
const { toast } = useToast();
|
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
|
const [editingRouter, setEditingRouter] = useState<Router | null>(null);
|
|
|
|
const { data: routers, isLoading } = useQuery<Router[]>({
|
|
queryKey: ["/api/routers"],
|
|
});
|
|
|
|
const addForm = useForm<InsertRouter>({
|
|
resolver: zodResolver(insertRouterSchema),
|
|
defaultValues: {
|
|
name: "",
|
|
ipAddress: "",
|
|
apiPort: 8729,
|
|
username: "",
|
|
password: "",
|
|
enabled: true,
|
|
},
|
|
});
|
|
|
|
const editForm = useForm<InsertRouter>({
|
|
resolver: zodResolver(insertRouterSchema),
|
|
});
|
|
|
|
const addMutation = useMutation({
|
|
mutationFn: async (data: InsertRouter) => {
|
|
return await apiRequest("POST", "/api/routers", data);
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["/api/routers"] });
|
|
toast({
|
|
title: "Router aggiunto",
|
|
description: "Il router è stato configurato con successo",
|
|
});
|
|
setAddDialogOpen(false);
|
|
addForm.reset();
|
|
},
|
|
onError: (error: any) => {
|
|
toast({
|
|
title: "Errore",
|
|
description: error.message || "Impossibile aggiungere il router",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const updateMutation = useMutation({
|
|
mutationFn: async ({ id, data }: { id: string; data: InsertRouter }) => {
|
|
return await apiRequest("PUT", `/api/routers/${id}`, data);
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["/api/routers"] });
|
|
toast({
|
|
title: "Router aggiornato",
|
|
description: "Le modifiche sono state salvate con successo",
|
|
});
|
|
setEditDialogOpen(false);
|
|
setEditingRouter(null);
|
|
editForm.reset();
|
|
},
|
|
onError: (error: any) => {
|
|
toast({
|
|
title: "Errore",
|
|
description: error.message || "Impossibile aggiornare il router",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: async (id: string) => {
|
|
await apiRequest("DELETE", `/api/routers/${id}`);
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["/api/routers"] });
|
|
toast({
|
|
title: "Router eliminato",
|
|
description: "Il router è stato rimosso con successo",
|
|
});
|
|
},
|
|
onError: () => {
|
|
toast({
|
|
title: "Errore",
|
|
description: "Impossibile eliminare il router",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const handleAddSubmit = (data: InsertRouter) => {
|
|
addMutation.mutate(data);
|
|
};
|
|
|
|
const handleEditSubmit = (data: InsertRouter) => {
|
|
if (editingRouter) {
|
|
updateMutation.mutate({ id: editingRouter.id, data });
|
|
}
|
|
};
|
|
|
|
const handleEdit = (router: Router) => {
|
|
setEditingRouter(router);
|
|
editForm.reset({
|
|
name: router.name,
|
|
ipAddress: router.ipAddress,
|
|
apiPort: router.apiPort,
|
|
username: router.username,
|
|
password: router.password,
|
|
enabled: router.enabled,
|
|
});
|
|
setEditDialogOpen(true);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 p-6" data-testid="page-routers">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-semibold" data-testid="text-page-title">Router MikroTik</h1>
|
|
<p className="text-muted-foreground" data-testid="text-page-subtitle">
|
|
Gestisci i router connessi al sistema IDS
|
|
</p>
|
|
</div>
|
|
|
|
<Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button data-testid="button-add-router">
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Aggiungi Router
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="sm:max-w-[500px]" data-testid="dialog-add-router">
|
|
<DialogHeader>
|
|
<DialogTitle>Aggiungi Router MikroTik</DialogTitle>
|
|
<DialogDescription>
|
|
Configura un nuovo router MikroTik per il sistema IDS. Assicurati che l'API RouterOS (porta 8729/8728) sia abilitata.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Form {...addForm}>
|
|
<form onSubmit={addForm.handleSubmit(handleAddSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={addForm.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Nome Router</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="es. MikroTik Ufficio" {...field} data-testid="input-name" />
|
|
</FormControl>
|
|
<FormDescription>
|
|
Nome descrittivo per identificare il router
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={addForm.control}
|
|
name="ipAddress"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Indirizzo IP</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="es. 192.168.1.1" {...field} data-testid="input-ip" />
|
|
</FormControl>
|
|
<FormDescription>
|
|
Indirizzo IP o hostname del router
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={addForm.control}
|
|
name="apiPort"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Porta API</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
placeholder="8729"
|
|
{...field}
|
|
onChange={(e) => field.onChange(parseInt(e.target.value))}
|
|
data-testid="input-port"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>
|
|
Porta RouterOS API MikroTik (8729 per API-SSL, 8728 per API)
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={addForm.control}
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Username</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="admin" {...field} data-testid="input-username" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={addForm.control}
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Password</FormLabel>
|
|
<FormControl>
|
|
<Input type="password" placeholder="••••••••" {...field} data-testid="input-password" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={addForm.control}
|
|
name="enabled"
|
|
render={({ field }) => (
|
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
|
<div className="space-y-0.5">
|
|
<FormLabel>Abilitato</FormLabel>
|
|
<FormDescription>
|
|
Attiva il router per il blocco automatico degli IP
|
|
</FormDescription>
|
|
</div>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-enabled"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setAddDialogOpen(false)}
|
|
data-testid="button-cancel"
|
|
>
|
|
Annulla
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={addMutation.isPending}
|
|
data-testid="button-submit"
|
|
>
|
|
{addMutation.isPending ? "Salvataggio..." : "Salva Router"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
|
|
<Card data-testid="card-routers">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Server className="h-5 w-5" />
|
|
Router Configurati ({routers?.length || 0})
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{isLoading ? (
|
|
<div className="text-center py-8 text-muted-foreground" data-testid="text-loading">
|
|
Caricamento...
|
|
</div>
|
|
) : routers && routers.length > 0 ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{routers.map((router) => (
|
|
<div
|
|
key={router.id}
|
|
className="p-4 rounded-lg border hover-elevate"
|
|
data-testid={`router-card-${router.id}`}
|
|
>
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div>
|
|
<h3 className="font-semibold text-lg" data-testid={`text-name-${router.id}`}>
|
|
{router.name}
|
|
</h3>
|
|
<p className="text-sm font-mono text-muted-foreground" data-testid={`text-ip-${router.id}`}>
|
|
{router.ipAddress}:{router.apiPort}
|
|
</p>
|
|
</div>
|
|
<Badge
|
|
variant={router.enabled ? "default" : "secondary"}
|
|
data-testid={`badge-status-${router.id}`}
|
|
>
|
|
{router.enabled ? "Attivo" : "Disabilitato"}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Username:</span>
|
|
<span className="font-mono" data-testid={`text-username-${router.id}`}>
|
|
{router.username}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Creato:</span>
|
|
<span data-testid={`text-created-${router.id}`}>
|
|
{format(new Date(router.createdAt), "dd/MM/yyyy")}
|
|
</span>
|
|
</div>
|
|
{router.lastSync && (
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Ultima sync:</span>
|
|
<span data-testid={`text-sync-${router.id}`}>
|
|
{format(new Date(router.lastSync), "HH:mm:ss")}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex gap-2 mt-4">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="flex-1"
|
|
onClick={() => handleEdit(router)}
|
|
data-testid={`button-edit-${router.id}`}
|
|
>
|
|
<Edit className="h-4 w-4 mr-2" />
|
|
Modifica
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => deleteMutation.mutate(router.id)}
|
|
disabled={deleteMutation.isPending}
|
|
data-testid={`button-delete-${router.id}`}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-12 text-muted-foreground" data-testid="text-no-routers">
|
|
<Server className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
|
<p className="mb-2">Nessun router configurato</p>
|
|
<p className="text-sm">Aggiungi il primo router per iniziare</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
|
|
<DialogContent className="sm:max-w-[500px]" data-testid="dialog-edit-router">
|
|
<DialogHeader>
|
|
<DialogTitle>Modifica Router</DialogTitle>
|
|
<DialogDescription>
|
|
Modifica le impostazioni del router {editingRouter?.name}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Form {...editForm}>
|
|
<form onSubmit={editForm.handleSubmit(handleEditSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Nome Router</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="es. MikroTik Ufficio" {...field} data-testid="input-edit-name" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editForm.control}
|
|
name="ipAddress"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Indirizzo IP</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="es. 192.168.1.1" {...field} data-testid="input-edit-ip" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editForm.control}
|
|
name="apiPort"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Porta API</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
placeholder="8729"
|
|
{...field}
|
|
onChange={(e) => field.onChange(parseInt(e.target.value))}
|
|
data-testid="input-edit-port"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>
|
|
Porta RouterOS API MikroTik (8729 per API-SSL, 8728 per API)
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editForm.control}
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Username</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="admin" {...field} data-testid="input-edit-username" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editForm.control}
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Password</FormLabel>
|
|
<FormControl>
|
|
<Input type="password" placeholder="••••••••" {...field} data-testid="input-edit-password" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editForm.control}
|
|
name="enabled"
|
|
render={({ field }) => (
|
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
|
<div className="space-y-0.5">
|
|
<FormLabel>Abilitato</FormLabel>
|
|
<FormDescription>
|
|
Attiva il router per il blocco automatico degli IP
|
|
</FormDescription>
|
|
</div>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-edit-enabled"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setEditDialogOpen(false)}
|
|
data-testid="button-edit-cancel"
|
|
>
|
|
Annulla
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={updateMutation.isPending}
|
|
data-testid="button-edit-submit"
|
|
>
|
|
{updateMutation.isPending ? "Salvataggio..." : "Salva Modifiche"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|