import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { RefreshCw, Search, Eye, Activity, MoreHorizontal, Pause, Play, Edit, Trash2 } from "lucide-react"; import { Server } from "@/types/server.types"; import { ServerStatusBadge } from "./ServerStatusBadge"; import { OSTypeIcon } from "./OSTypeIcon"; import { EditServerDialog } from "./EditServerDialog"; import { serverService } from "@/services/serverService"; import { useToast } from "@/hooks/use-toast"; import { pb } from "@/lib/pocketbase"; import { useTheme } from "@/contexts/ThemeContext"; import { useLanguage } from "@/contexts/LanguageContext"; interface ServerTableProps { servers: Server[]; isLoading: boolean; onRefresh: () => void; } export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) => { const { t } = useLanguage(); const { theme } = useTheme(); const [searchTerm, setSearchTerm] = useState(""); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false); const [selectedServer, setSelectedServer] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [pausingServers, setPausingServers] = useState>(new Set()); const [selectedServerIds, setSelectedServerIds] = useState>(new Set()); const [bulkDeleteDialogOpen, setBulkDeleteDialogOpen] = useState(false); const [isBulkDeleting, setIsBulkDeleting] = useState(false); const navigate = useNavigate(); const { toast } = useToast(); const filteredServers = servers.filter(server => server.name.toLowerCase().includes(searchTerm.toLowerCase()) || server.hostname.toLowerCase().includes(searchTerm.toLowerCase()) || server.ip_address.toLowerCase().includes(searchTerm.toLowerCase()) ); const allVisibleSelected = filteredServers.length > 0 && filteredServers.every(s => selectedServerIds.has(s.id)); const someVisibleSelected = filteredServers.some(s => selectedServerIds.has(s.id)) && !allVisibleSelected; const toggleSelectAllVisible = (checked: boolean) => { const newSet = new Set(selectedServerIds); if (checked) { filteredServers.forEach(s => newSet.add(s.id)); } else { filteredServers.forEach(s => newSet.delete(s.id)); } setSelectedServerIds(newSet); }; const toggleSelectOne = (serverId: string, checked: boolean) => { const newSet = new Set(selectedServerIds); if (checked) newSet.add(serverId); else newSet.delete(serverId); setSelectedServerIds(newSet); }; const handleViewDetails = (serverId: string) => { navigate(`/server-detail/${serverId}`); }; const handleViewContainers = (serverId: string) => { navigate(`/container-monitoring/${serverId}`); }; const handlePauseResume = async (server: Server) => { const serverId = server.id; const isPaused = server.status === "paused"; if (pausingServers.has(serverId)) { return; // Already processing this server } try { setPausingServers(prev => new Set(prev).add(serverId)); // Only update the status field, preserving all other server configuration const updateData = { status: isPaused ? "up" : "paused", last_checked: new Date().toISOString() }; await pb.collection('servers').update(serverId, updateData); toast({ title: isPaused ? t('serverResumed') : t('serverPaused'), description: isPaused ? t('monitoringResumed', { name: server.name }) : t('monitoringPaused', { name: server.name }), }); // console.log(`${isPaused ? 'Resume' : 'Pause'} server monitoring: ${serverId}`); // Refresh the server list to show updated status onRefresh(); } catch (error) { // console.error('Error updating server status:', error); toast({ variant: "destructive", title: t('error'), description: isPaused ? t('resumeServerError') : t('pauseServerError'), }); } finally { setPausingServers(prev => { const newSet = new Set(prev); newSet.delete(serverId); return newSet; }); } }; const handleEdit = (server: Server) => { setSelectedServer(server); setEditDialogOpen(true); }; const handleDelete = (server: Server) => { setSelectedServer(server); setDeleteDialogOpen(true); }; const confirmDelete = async () => { if (!selectedServer || isDeleting) return; try { setIsDeleting(true); // Delete the server from the database await pb.collection('servers').delete(selectedServer.id); toast({ title: "Server deleted", description: `${selectedServer.name} has been deleted successfully.`, }); // Refresh the server list onRefresh(); // Close the dialog setDeleteDialogOpen(false); setSelectedServer(null); } catch (error) { // console.error('Error deleting server:', error); toast({ variant: "destructive", title: "Error", description: "Failed to delete server. Please try again.", }); } finally { setIsDeleting(false); } }; const confirmBulkDelete = async () => { if (selectedServerIds.size === 0 || isBulkDeleting) return; try { setIsBulkDeleting(true); const ids = Array.from(selectedServerIds); const deletions = ids.map(id => pb.collection('servers').delete(id)); const results = await Promise.allSettled(deletions); const failed = results.filter(r => r.status === 'rejected').length; if (failed === 0) { toast({ title: "Servers deleted", description: `${ids.length} server(s) have been deleted.` }); } else if (failed === ids.length) { toast({ variant: "destructive", title: "Error", description: "Failed to delete selected servers. Please try again." }); } else { toast({ variant: "destructive", title: "Partial success", description: `Deleted ${ids.length - failed}, failed ${failed}.` }); } onRefresh(); setSelectedServerIds(new Set()); setBulkDeleteDialogOpen(false); } catch (_e) { toast({ variant: "destructive", title: "Error", description: "Failed to delete selected servers. Please try again." }); } finally { setIsBulkDeleting(false); } }; const CustomProgressBar = ({ value, label, subtitle, type }: { value: number; label: string; subtitle: string; type: 'cpu' | 'memory' | 'disk' }) => { const getGradientColors = (type: string, value: number) => { if (type === 'cpu') { if (value > 90) return 'from-red-500 to-red-600'; if (value > 75) return 'from-orange-500 to-orange-600'; if (value > 60) return 'from-yellow-500 to-yellow-600'; return 'from-green-500 to-green-600'; } if (type === 'memory') { if (value > 90) return 'from-red-500 to-red-600'; if (value > 75) return 'from-yellow-500 to-yellow-600'; return 'from-blue-500 to-blue-600'; } if (type === 'disk') { if (value > 95) return 'from-red-500 to-red-600'; if (value > 85) return 'from-yellow-500 to-yellow-600'; return 'from-orange-500 to-orange-600'; } return 'from-gray-500 to-gray-600'; }; const getTextColor = (value: number) => { if (value > 90) return 'text-red-600 dark:text-red-400'; if (value > 75) return 'text-orange-600 dark:text-orange-400'; if (value > 60) return 'text-yellow-600 dark:text-yellow-400'; return 'text-green-600 dark:text-green-400'; }; return (
{label} {subtitle}
); }; if (isLoading) { return ( {t('servers')}
{t('loadingServers')}
); } return ( <>
{t('servers')}
setSearchTerm(e.target.value)} className="pl-8" />
{selectedServerIds.size > 0 && (
{selectedServerIds.size} selected
)}
{filteredServers.length === 0 ? (

{t('noServersFound')}

) : (
e.stopPropagation()}> toggleSelectAllVisible(Boolean(v))} aria-label="Select all" indeterminate={someVisibleSelected} />
{t('name')} {t('status')} {t('OS')} {t('IPAddress')} {t('CPU')} {t('memory')} {t('disk')} {t('uptime')} {t('lastChecked')} {t('actions')}
{filteredServers.map((server) => { const cpuUsage = server.cpu_usage || 0; const memoryUsage = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0; const diskUsage = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0; const isPaused = server.status === "paused"; const isProcessing = pausingServers.has(server.id); const isSelected = selectedServerIds.has(server.id); return ( handleViewDetails(server.id)} > e.stopPropagation()}> toggleSelectOne(server.id, Boolean(v))} aria-label={`Select ${server.name}`} />
{server.name}
{server.os_type}
{server.ip_address}
{server.uptime}
{new Date(server.last_checked).toLocaleString()}
e.stopPropagation()}> { e.stopPropagation(); handleViewDetails(server.id); }}> {t('viewServerDetail')} {server.docker === 'true' && ( { e.stopPropagation(); handleViewContainers(server.id); }}> {t('containerMonitoring')} )} { e.stopPropagation(); handlePauseResume(server); }} disabled={isProcessing} > {isPaused ? ( <> {t('resumeMonitoring')} ) : ( <> {t('pauseMonitoring')} )} { e.stopPropagation(); handleEdit(server); }}> {t('editServer')} { e.stopPropagation(); handleDelete(server); }} className="text-red-600 focus:text-red-600" > {t('deleteServer')}
); })}
)}
{/* Edit Server Dialog */} {/* Delete Confirmation Dialog */} {t('deleteServerConfirmTitle')} {t('deleteServerConfirmDesc').replace('{name}', selectedServer?.name ?? '')} {t('cancel')} {isDeleting ? t('deleting') : t('delete')} {/* Bulk Delete Confirmation Dialog */} Delete selected servers? This action cannot be undone. This will permanently delete {selectedServerIds.size} server(s) and all of their monitoring data. {t('cancel')} {isBulkDeleting ? t('deleting') : 'Delete Selected'} ); };