import React, { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { useToast } from "@/hooks/use-toast"; import { pb } from "@/lib/pocketbase"; import { Server } from "@/types/server.types"; import { RefreshCw, X } from "lucide-react"; import { alertConfigService, AlertConfiguration } from "@/services/alertConfigService"; import { templateService, NotificationTemplate } from "@/services/templateService"; import { serverThresholdService, ServerThreshold } from "@/services/serverThresholdService"; interface EditServerDialogProps { server: Server | null; open: boolean; onOpenChange: (open: boolean) => void; onServerUpdated: () => void; } interface ServerFormData { name: string; check_interval: number; retry_attempts: number; docker_monitoring: boolean; notification_enabled: boolean; notification_channels: string[]; // Changed to array for multiple selections threshold_id: string; template_id: string; } interface ThresholdFormData { cpu_threshold: number; ram_threshold: number; disk_threshold: number; network_threshold: number; } export const EditServerDialog: React.FC = ({ server, open, onOpenChange, onServerUpdated, }) => { const [formData, setFormData] = useState({ name: "", check_interval: 60, retry_attempts: 3, docker_monitoring: false, notification_enabled: false, notification_channels: [], // Changed to array threshold_id: "none", template_id: "none", }); const [thresholdFormData, setThresholdFormData] = useState({ cpu_threshold: 80, ram_threshold: 80, disk_threshold: 80, network_threshold: 80, }); const [isSubmitting, setIsSubmitting] = useState(false); const [alertConfigs, setAlertConfigs] = useState([]); const [templates, setTemplates] = useState([]); const [thresholds, setThresholds] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(null); const [selectedThreshold, setSelectedThreshold] = useState(null); const [loadingAlertConfigs, setLoadingAlertConfigs] = useState(false); const [loadingTemplates, setLoadingTemplates] = useState(false); const [loadingThresholds, setLoadingThresholds] = useState(false); const { toast } = useToast(); // Initialize form data when server changes useEffect(() => { if (server) { // console.log("Setting form data for server:", server); // Parse comma-separated notification_id into array const notificationChannels = server.notification_id ? server.notification_id.split(',').map(id => id.trim()).filter(id => id) : []; setFormData({ name: server.name || "", check_interval: server.check_interval || 60, retry_attempts: 3, docker_monitoring: server.docker === "true", notification_enabled: notificationChannels.length > 0, notification_channels: notificationChannels, threshold_id: server.threshold_id || "none", template_id: server.template_id || "none", }); } }, [server]); // Load data when dialog opens useEffect(() => { if (open) { loadAlertConfigurations(); loadTemplates(); loadThresholds(); } }, [open]); // Load existing threshold data when thresholds are loaded and we have a server with threshold_id useEffect(() => { if (server && server.threshold_id && thresholds.length > 0) { // console.log("Loading existing threshold data for server:", server.threshold_id); const existingThreshold = thresholds.find(t => t.id === server.threshold_id); if (existingThreshold) { // console.log("Found existing threshold:", existingThreshold); setSelectedThreshold(existingThreshold); // Handle the API response format with proper field names and type conversion setThresholdFormData({ cpu_threshold: parseInt(String(existingThreshold.cpu_threshold)) || 80, ram_threshold: parseInt(String((existingThreshold as any).ram_threshold_message || existingThreshold.ram_threshold)) || 80, disk_threshold: parseInt(String(existingThreshold.disk_threshold)) || 80, network_threshold: parseInt(String(existingThreshold.network_threshold)) || 80, }); } } }, [server, thresholds]); // Update selected template when form data or templates change useEffect(() => { if (formData.template_id && formData.template_id !== "none" && templates.length > 0) { const template = templates.find(t => t.id === formData.template_id); setSelectedTemplate(template || null); } else { setSelectedTemplate(null); } }, [formData.template_id, templates]); // Update selected threshold when threshold_id changes in form useEffect(() => { if (formData.threshold_id && formData.threshold_id !== "none" && thresholds.length > 0) { const threshold = thresholds.find(t => t.id === formData.threshold_id); setSelectedThreshold(threshold || null); if (threshold) { // Handle the API response format with proper field names and type conversion setThresholdFormData({ cpu_threshold: parseInt(String(threshold.cpu_threshold)) || 80, ram_threshold: parseInt(String((threshold as any).ram_threshold_message || threshold.ram_threshold)) || 80, disk_threshold: parseInt(String(threshold.disk_threshold)) || 80, network_threshold: parseInt(String(threshold.network_threshold)) || 80, }); } } else if (formData.threshold_id === "none") { setSelectedThreshold(null); setThresholdFormData({ cpu_threshold: 80, ram_threshold: 80, disk_threshold: 80, network_threshold: 80, }); } }, [formData.threshold_id, thresholds]); const loadAlertConfigurations = async () => { try { setLoadingAlertConfigs(true); const configs = await alertConfigService.getAlertConfigurations(); setAlertConfigs(configs); } catch (error) { // console.error('Error loading alert configurations:', error); toast({ variant: "destructive", title: "Error", description: "Failed to load notification channels", }); } finally { setLoadingAlertConfigs(false); } }; const loadTemplates = async () => { try { setLoadingTemplates(true); const templateList = await templateService.getTemplates(); setTemplates(templateList); } catch (error) { // console.error('Error loading templates:', error); toast({ variant: "destructive", title: "Error", description: "Failed to load templates", }); } finally { setLoadingTemplates(false); } }; const loadThresholds = async () => { try { setLoadingThresholds(true); const thresholdList = await serverThresholdService.getServerThresholds(); setThresholds(thresholdList); } catch (error) { // console.error('Error loading server thresholds:', error); toast({ variant: "destructive", title: "Error", description: "Failed to load server thresholds", }); } finally { setLoadingThresholds(false); } }; const handleThresholdUpdate = async () => { if (!selectedThreshold) return; try { // Use the correct field name for RAM threshold const updateData = { cpu_threshold: thresholdFormData.cpu_threshold, ram_threshold_message: thresholdFormData.ram_threshold, // Use the correct field name disk_threshold: thresholdFormData.disk_threshold, network_threshold: thresholdFormData.network_threshold, }; await serverThresholdService.updateServerThreshold(selectedThreshold.id, updateData); // Update local state setSelectedThreshold({ ...selectedThreshold, ...thresholdFormData, }); // Update thresholds list setThresholds(prev => prev.map(t => t.id === selectedThreshold.id ? { ...t, ...thresholdFormData } : t )); toast({ title: "Threshold updated", description: "Server threshold values have been updated successfully.", }); } catch (error) { // console.error('Error updating threshold:', error); toast({ variant: "destructive", title: "Error", description: "Failed to update threshold values.", }); } }; const handleNotificationChannelToggle = (channelId: string, checked: boolean) => { setFormData(prev => ({ ...prev, notification_channels: checked ? [...prev.notification_channels, channelId] : prev.notification_channels.filter(id => id !== channelId) })); }; const removeNotificationChannel = (channelId: string) => { setFormData(prev => ({ ...prev, notification_channels: prev.notification_channels.filter(id => id !== channelId) })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!server || isSubmitting) return; try { setIsSubmitting(true); // Convert notification channels array to comma-separated string const notificationChannelsString = formData.notification_enabled ? formData.notification_channels.join(',') : ""; const updateData = { name: formData.name, check_interval: formData.check_interval, docker: formData.docker_monitoring ? "true" : "false", notification_id: notificationChannelsString, threshold_id: formData.notification_enabled && formData.threshold_id !== "none" ? formData.threshold_id : "", template_id: formData.notification_enabled && formData.template_id !== "none" ? formData.template_id : "", updated: new Date().toISOString(), }; await pb.collection('servers').update(server.id, updateData); toast({ title: "Server updated", description: `${formData.name} has been updated successfully.`, }); onServerUpdated(); onOpenChange(false); } catch (error) { // console.error('Error updating server:', error); toast({ variant: "destructive", title: "Error", description: "Failed to update server. Please try again.", }); } finally { setIsSubmitting(false); } }; const handleCancel = () => { if (server) { const notificationChannels = server.notification_id ? server.notification_id.split(',').map(id => id.trim()).filter(id => id) : []; setFormData({ name: server.name || "", check_interval: server.check_interval || 60, retry_attempts: 3, docker_monitoring: server.docker === "true", notification_enabled: notificationChannels.length > 0, notification_channels: notificationChannels, threshold_id: server.threshold_id || "none", template_id: server.template_id || "none", }); } onOpenChange(false); }; return ( Edit Server Configuration
setFormData(prev => ({ ...prev, name: e.target.value }))} placeholder="Enter server name" required />
setFormData(prev => ({ ...prev, docker_monitoring: checked }))} />
{/* Notification Status Toggle */}
setFormData(prev => ({ ...prev, notification_enabled: checked, notification_channels: checked ? prev.notification_channels : [], threshold_id: checked ? prev.threshold_id : "none", template_id: checked ? prev.template_id : "none" }))} />
{/* Expanded Notification Settings */} {formData.notification_enabled && ( Notification Settings {/* Multiple Notification Channels Selection */}
{loadingAlertConfigs ? (
Loading channels...
) : alertConfigs.length > 0 ? ( alertConfigs.map((config) => (
handleNotificationChannelToggle(config.id || "", checked as boolean) } />
)) ) : (
No notification channels available
)}
{/* Selected Channels Display */} {formData.notification_channels.length > 0 && (
{formData.notification_channels.map((channelId) => { const channel = alertConfigs.find(c => c.id === channelId); return (
{channel?.notify_name || channelId}
); })}
)}
{/* Server Set Threshold Selection */}
{/* Editable Threshold Details */} {selectedThreshold && ( Threshold Details: {selectedThreshold.name}
setThresholdFormData(prev => ({ ...prev, cpu_threshold: parseInt(e.target.value) || 0 }))} className="mt-1" />
setThresholdFormData(prev => ({ ...prev, ram_threshold: parseInt(e.target.value) || 0 }))} className="mt-1" />
setThresholdFormData(prev => ({ ...prev, disk_threshold: parseInt(e.target.value) || 0 }))} className="mt-1" />
setThresholdFormData(prev => ({ ...prev, network_threshold: parseInt(e.target.value) || 0 }))} className="mt-1" />
)} {/* Server Template Selection */}
{/* Template Details */} {selectedTemplate && ( Template Details: {selectedTemplate.name}

{selectedTemplate.up_message || "No RAM threshold message defined"}

{selectedTemplate.down_message || "No CPU threshold message defined"}

{selectedTemplate.incident_message || "No disk threshold message defined"}

{selectedTemplate.maintenance_message || "No network threshold message defined"}

)}
)}
); };