Add functionality to manage and test router connections
Implement dialogs and forms for adding/editing routers, along with backend endpoints for updating router details and testing connectivity. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 72dce443-ff50-4028-b2d4-a6b504b9b018 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/L6QSDnx
This commit is contained in:
parent
7c204c62b2
commit
8aabed0272
4
.replit
4
.replit
@ -26,6 +26,10 @@ externalPort = 3003
|
||||
localPort = 43803
|
||||
externalPort = 3000
|
||||
|
||||
[[ports]]
|
||||
localPort = 46817
|
||||
externalPort = 3001
|
||||
|
||||
[env]
|
||||
PORT = "5000"
|
||||
|
||||
|
||||
@ -1,19 +1,109 @@
|
||||
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 { Server, Plus, Trash2 } from "lucide-react";
|
||||
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, Wifi, WifiOff } 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 [testingRouterId, setTestingRouterId] = useState<string | null>(null);
|
||||
|
||||
const { data: routers, isLoading } = useQuery<Router[]>({
|
||||
queryKey: ["/api/routers"],
|
||||
});
|
||||
|
||||
const addForm = useForm<InsertRouter>({
|
||||
resolver: zodResolver(insertRouterSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
ipAddress: "",
|
||||
apiPort: 8728,
|
||||
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}`);
|
||||
@ -34,6 +124,56 @@ export default function Routers() {
|
||||
},
|
||||
});
|
||||
|
||||
const testConnectionMutation = useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
const response = await apiRequest("POST", `/api/routers/${id}/test`);
|
||||
return response;
|
||||
},
|
||||
onSuccess: (data: any) => {
|
||||
toast({
|
||||
title: "Connessione riuscita",
|
||||
description: data.message || "Il router è raggiungibile e le credenziali sono corrette",
|
||||
});
|
||||
setTestingRouterId(null);
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast({
|
||||
title: "Connessione fallita",
|
||||
description: error.message || "Impossibile connettersi al router. Verifica IP, porta e credenziali.",
|
||||
variant: "destructive",
|
||||
});
|
||||
setTestingRouterId(null);
|
||||
},
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
const handleTestConnection = (id: string) => {
|
||||
setTestingRouterId(id);
|
||||
testConnectionMutation.mutate(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6" data-testid="page-routers">
|
||||
<div className="flex items-center justify-between">
|
||||
@ -43,10 +183,152 @@ export default function Routers() {
|
||||
Gestisci i router connessi al sistema IDS
|
||||
</p>
|
||||
</div>
|
||||
<Button data-testid="button-add-router">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Aggiungi Router
|
||||
</Button>
|
||||
|
||||
<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 REST 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="8728"
|
||||
{...field}
|
||||
onChange={(e) => field.onChange(parseInt(e.target.value))}
|
||||
data-testid="input-port"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Porta API MikroTik (default: 8728 per API, 8729 per API-SSL)
|
||||
</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">
|
||||
@ -114,9 +396,24 @@ export default function Routers() {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => handleTestConnection(router.id)}
|
||||
disabled={testingRouterId === router.id}
|
||||
data-testid={`button-test-${router.id}`}
|
||||
>
|
||||
Test Connessione
|
||||
{testingRouterId === router.id ? (
|
||||
<WifiOff className="h-4 w-4 mr-2 animate-pulse" />
|
||||
) : (
|
||||
<Wifi className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
{testingRouterId === router.id ? "Test..." : "Test"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(router)}
|
||||
data-testid={`button-edit-${router.id}`}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -140,6 +437,137 @@ export default function Routers() {
|
||||
)}
|
||||
</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="8728"
|
||||
{...field}
|
||||
onChange={(e) => field.onChange(parseInt(e.target.value))}
|
||||
data-testid="input-edit-port"
|
||||
/>
|
||||
</FormControl>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -27,6 +27,20 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
app.put("/api/routers/:id", async (req, res) => {
|
||||
try {
|
||||
const validatedData = insertRouterSchema.parse(req.body);
|
||||
const router = await storage.updateRouter(req.params.id, validatedData);
|
||||
if (!router) {
|
||||
return res.status(404).json({ error: "Router not found" });
|
||||
}
|
||||
res.json(router);
|
||||
} catch (error) {
|
||||
console.error('[Router UPDATE] Error:', error);
|
||||
res.status(400).json({ error: "Invalid router data" });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete("/api/routers/:id", async (req, res) => {
|
||||
try {
|
||||
const success = await storage.deleteRouter(req.params.id);
|
||||
@ -39,6 +53,67 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/routers/:id/test", async (req, res) => {
|
||||
try {
|
||||
const router = await storage.getRouterById(req.params.id);
|
||||
if (!router) {
|
||||
return res.status(404).json({ error: "Router not found" });
|
||||
}
|
||||
|
||||
// Test connessione TCP/HTTP al router
|
||||
const testUrl = `http://${router.ipAddress}:${router.apiPort}`;
|
||||
|
||||
try {
|
||||
// Timeout di 5 secondi per il test
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
const response = await fetch(testUrl, {
|
||||
method: 'GET',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + Buffer.from(`${router.username}:${router.password}`).toString('base64')
|
||||
}
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// Aggiorna lastSync
|
||||
await storage.updateRouter(router.id, { lastSync: new Date() });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Router ${router.name} raggiungibile (HTTP ${response.status})`,
|
||||
status: response.status,
|
||||
statusText: response.statusText
|
||||
});
|
||||
} catch (fetchError: any) {
|
||||
console.error('[Router TEST] Connection failed:', fetchError.message);
|
||||
|
||||
// Differenzia gli errori
|
||||
if (fetchError.name === 'AbortError') {
|
||||
res.status(408).json({
|
||||
error: "Timeout: Il router non risponde entro 5 secondi",
|
||||
message: `Impossibile connettersi a ${router.ipAddress}:${router.apiPort}. Verifica che il router sia acceso e raggiungibile.`
|
||||
});
|
||||
} else if (fetchError.code === 'ECONNREFUSED') {
|
||||
res.status(503).json({
|
||||
error: "Connessione rifiutata",
|
||||
message: `Il router a ${router.ipAddress}:${router.apiPort} rifiuta la connessione. Verifica che l'API REST sia abilitata.`
|
||||
});
|
||||
} else {
|
||||
res.status(503).json({
|
||||
error: "Errore di connessione",
|
||||
message: `${fetchError.message}. Verifica IP, porta e firewall.`
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Router TEST] Error:', error);
|
||||
res.status(500).json({ error: "Failed to test router connection" });
|
||||
}
|
||||
});
|
||||
|
||||
// Network Logs
|
||||
app.get("/api/logs", async (req, res) => {
|
||||
try {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user