diff --git a/application/src/components/settings/alerts-templates/AlertsTemplates.tsx b/application/src/components/settings/alerts-templates/AlertsTemplates.tsx index de52b33..ec14f0d 100644 --- a/application/src/components/settings/alerts-templates/AlertsTemplates.tsx +++ b/application/src/components/settings/alerts-templates/AlertsTemplates.tsx @@ -64,10 +64,11 @@ export const AlertsTemplates = () => { setActiveTab(value as TemplateType)}> - + Service Uptime Server Monitoring SSL Certificate + Server Threshold @@ -126,6 +127,25 @@ export const AlertsTemplates = () => { /> )} + + + {error ? ( + + Error loading server threshold templates + refetch()}> + Try Again + + + ) : ( + handleEditTemplate(id, 'server_threshold')} + refetchTemplates={refetch} + templateType="server_threshold" + /> + )} + diff --git a/application/src/components/settings/alerts-templates/TemplateDialog.tsx b/application/src/components/settings/alerts-templates/TemplateDialog.tsx index a1afeb7..fb547d6 100644 --- a/application/src/components/settings/alerts-templates/TemplateDialog.tsx +++ b/application/src/components/settings/alerts-templates/TemplateDialog.tsx @@ -10,6 +10,7 @@ import { useTemplateForm } from "./hooks/useTemplateForm"; import { ServerTemplateFields } from "./form/ServerTemplateFields"; import { ServiceTemplateFields } from "./form/ServiceTemplateFields"; import { SslTemplateFields } from "./form/SslTemplateFields"; +import { ServerThresholdFields } from "./form/ServerThresholdFields"; import { Loader2, ChevronDown } from "lucide-react"; import { ScrollArea } from "@/components/ui/scroll-area"; import { TemplateType, templateTypeConfigs } from "@/services/templateService"; @@ -72,6 +73,8 @@ export const TemplateDialog: React.FC = ({ return ; case 'ssl': return ; + case 'server_threshold': + return ; default: return null; } @@ -158,6 +161,7 @@ export const TemplateDialog: React.FC = ({ Server Monitoring Service Uptime SSL Certificate + Server Threshold @@ -166,27 +170,29 @@ export const TemplateDialog: React.FC = ({ )} /> - ( - - Custom Placeholder - - - - - - )} - /> + {selectedTemplateType !== 'server_threshold' && ( + ( + + Custom Placeholder + + + + + + )} + /> + )} - Messages + {selectedTemplateType === 'server_threshold' ? 'Thresholds' : 'Messages'} Placeholders diff --git a/application/src/components/settings/alerts-templates/form/ServerThresholdFields.tsx b/application/src/components/settings/alerts-templates/form/ServerThresholdFields.tsx new file mode 100644 index 0000000..f50be47 --- /dev/null +++ b/application/src/components/settings/alerts-templates/form/ServerThresholdFields.tsx @@ -0,0 +1,121 @@ + +import React from "react"; +import { FormField, FormItem, FormLabel, FormControl, FormMessage, FormDescription } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Control } from "react-hook-form"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +interface ServerThresholdFieldsProps { + control: Control; +} + +export const ServerThresholdFields: React.FC = ({ control }) => { + return ( + + + + Server Resource Thresholds + + + + ( + + CPU Threshold (%) + + field.onChange(Number(e.target.value))} + /> + + + CPU usage percentage that triggers an alert + + + + )} + /> + + ( + + RAM Threshold (%) + + field.onChange(Number(e.target.value))} + /> + + + Memory usage percentage that triggers an alert + + + + )} + /> + + ( + + Disk Threshold (%) + + field.onChange(Number(e.target.value))} + /> + + + Disk usage percentage that triggers an alert + + + + )} + /> + + ( + + Network Threshold (%) + + field.onChange(Number(e.target.value))} + /> + + + Network usage percentage that triggers an alert + + + + )} + /> + + + + + ); +}; \ No newline at end of file diff --git a/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts b/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts index b54a29a..e334000 100644 --- a/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts +++ b/application/src/components/settings/alerts-templates/hooks/useTemplateForm.ts @@ -10,7 +10,7 @@ import { useEffect } from "react"; // Base schema const baseSchema = { name: z.string().min(2, "Name is required and must be at least 2 characters"), - templateType: z.enum(['server', 'service', 'ssl'] as const), + templateType: z.enum(['server', 'service', 'ssl', 'server_threshold'] as const), placeholder: z.string().optional(), }; @@ -52,11 +52,24 @@ const sslTemplateSchema = z.object({ warning: z.string().min(1, "Warning message is required"), }); +// Server threshold schema +const serverThresholdSchema = z.object({ + ...baseSchema, + templateType: z.literal('server_threshold'), + cpu_threshold: z.number().min(0).max(100, "CPU threshold must be between 0-100"), + ram_threshold: z.number().min(0).max(100, "RAM threshold must be between 0-100"), + disk_threshold: z.number().min(0).max(100, "Disk threshold must be between 0-100"), + network_threshold: z.number().min(0).max(100, "Network threshold must be between 0-100"), + notification_id: z.string().optional(), + server_template_id: z.string().optional(), +}); + // Combined schema export const templateFormSchema = z.discriminatedUnion("templateType", [ serverTemplateSchema, serviceTemplateSchema, sslTemplateSchema, + serverThresholdSchema, ]); export type TemplateFormData = z.infer; @@ -107,6 +120,18 @@ const getDefaultValues = (templateType: TemplateType): TemplateFormData => { exiring_soon: "SSL certificate for ${domain} will expire in ${days_left} days on ${expiry_date}", warning: "Warning: SSL certificate for ${domain} requires attention", }; + + case 'server_threshold': + return { + ...base, + templateType: 'server_threshold' as const, + cpu_threshold: 85, + ram_threshold: 80, + disk_threshold: 90, + network_threshold: 75, + notification_id: "", + server_template_id: "", + }; default: throw new Error(`Unknown template type: ${templateType}`); @@ -125,9 +150,7 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, const { toast } = useToast(); const queryClient = useQueryClient(); const isEditMode = !!templateId; - - // console.log("Template form initialized with templateId:", templateId, "templateType:", templateType, "isEditMode:", isEditMode); - + const form = useForm({ resolver: zodResolver(templateFormSchema), defaultValues: getDefaultValues(templateType), @@ -139,7 +162,6 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, queryKey: ['template', templateId, templateType], queryFn: () => { if (!templateId) return null; - // console.log("Fetching template data for ID:", templateId, "type:", templateType); return templateService.getTemplate(templateId, templateType); }, enabled: !!templateId && open, @@ -148,7 +170,6 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, // Set form values when template data is loaded useEffect(() => { if (templateData && open) { - // console.log("Setting form values with template data:", templateData); const formData: any = { name: templateData.name || "", @@ -156,10 +177,23 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, placeholder: (templateData as any).placeholder || "", }; - // Add template-specific fields + // Add template-specific fields with proper type conversion for server_threshold Object.keys(templateData).forEach(key => { if (!['id', 'collectionId', 'collectionName', 'created', 'updated', 'name'].includes(key)) { - formData[key] = (templateData as any)[key] || ""; + let value = (templateData as any)[key]; + + // Convert string values to numbers for server threshold fields + if (templateType === 'server_threshold' && + ['cpu_threshold', 'ram_threshold', 'disk_threshold', 'network_threshold'].includes(key)) { + value = typeof value === 'string' ? Number(value) : value; + // Ensure valid number, fallback to default if invalid + if (isNaN(value)) { + const defaults = getDefaultValues(templateType) as any; + value = defaults[key] || 0; + } + } + + formData[key] = value || ""; } }); @@ -179,7 +213,6 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, onSuccess(); }, onError: (error) => { - // console.error("Error creating template:", error); toast({ title: "Error", description: "Failed to create template. Please check your inputs and try again.", @@ -201,7 +234,6 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, onSuccess(); }, onError: (error) => { - // console.error("Error updating template:", error); toast({ title: "Error", description: "Failed to update template. Please check your inputs and try again.", @@ -214,17 +246,14 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, // Handle form submission const onSubmit = (formData: TemplateFormData) => { - // console.log("Submitting form data:", formData); // Remove templateType from the data before sending to API const { templateType: _, ...templateDataWithoutType } = formData; const completeData = templateDataWithoutType as AnyTemplateData; if (isEditMode && templateId) { - // console.log("Updating template with ID:", templateId); updateMutation.mutate({ id: templateId, data: completeData }); } else { - // console.log("Creating new template"); createMutation.mutate(completeData); } }; @@ -232,7 +261,6 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange, // Reset form when dialog closes or template type changes useEffect(() => { if (!open) { - // console.log("Dialog closed, resetting form"); form.reset(getDefaultValues(templateType)); } }, [open, form, templateType]); diff --git a/application/src/services/templateService.ts b/application/src/services/templateService.ts index 3dc83d9..0206482 100644 --- a/application/src/services/templateService.ts +++ b/application/src/services/templateService.ts @@ -1,3 +1,4 @@ + import { pb } from "@/lib/pocketbase"; import { serverNotificationTemplateService, @@ -14,15 +15,20 @@ import { SslNotificationTemplate, CreateUpdateSslNotificationTemplateData } from "./sslNotificationTemplateService"; +import { + serverThresholdService, + ServerThreshold, + CreateUpdateServerThresholdData +} from "./serverThresholdService"; -export type TemplateType = 'server' | 'service' | 'ssl'; +export type TemplateType = 'server' | 'service' | 'ssl' | 'server_threshold'; -export type AnyTemplate = ServerNotificationTemplate | ServiceNotificationTemplate | SslNotificationTemplate; -export type AnyTemplateData = CreateUpdateServerNotificationTemplateData | CreateUpdateServiceNotificationTemplateData | CreateUpdateSslNotificationTemplateData; +export type AnyTemplate = ServerNotificationTemplate | ServiceNotificationTemplate | SslNotificationTemplate | ServerThreshold; +export type AnyTemplateData = CreateUpdateServerNotificationTemplateData | CreateUpdateServiceNotificationTemplateData | CreateUpdateSslNotificationTemplateData | CreateUpdateServerThresholdData; // Export individual template types -export type { ServerNotificationTemplate, ServiceNotificationTemplate, SslNotificationTemplate }; -export type { CreateUpdateServerNotificationTemplateData, CreateUpdateServiceNotificationTemplateData, CreateUpdateSslNotificationTemplateData }; +export type { ServerNotificationTemplate, ServiceNotificationTemplate, SslNotificationTemplate, ServerThreshold }; +export type { CreateUpdateServerNotificationTemplateData, CreateUpdateServiceNotificationTemplateData, CreateUpdateSslNotificationTemplateData, CreateUpdateServerThresholdData }; export const templateService = { async getTemplates(type: TemplateType): Promise { @@ -33,6 +39,8 @@ export const templateService = { return serviceNotificationTemplateService.getTemplates(); case 'ssl': return sslNotificationTemplateService.getTemplates(); + case 'server_threshold': + return serverThresholdService.getServerThresholds(); default: throw new Error(`Unknown template type: ${type}`); } @@ -46,6 +54,8 @@ export const templateService = { return serviceNotificationTemplateService.getTemplate(id); case 'ssl': return sslNotificationTemplateService.getTemplate(id); + case 'server_threshold': + return serverThresholdService.getServerThreshold(id); default: throw new Error(`Unknown template type: ${type}`); } @@ -59,6 +69,8 @@ export const templateService = { return serviceNotificationTemplateService.createTemplate(data as CreateUpdateServiceNotificationTemplateData); case 'ssl': return sslNotificationTemplateService.createTemplate(data as CreateUpdateSslNotificationTemplateData); + case 'server_threshold': + return serverThresholdService.createServerThreshold(data as CreateUpdateServerThresholdData); default: throw new Error(`Unknown template type: ${type}`); } @@ -72,6 +84,8 @@ export const templateService = { return serviceNotificationTemplateService.updateTemplate(id, data as Partial); case 'ssl': return sslNotificationTemplateService.updateTemplate(id, data as Partial); + case 'server_threshold': + return serverThresholdService.updateServerThreshold(id, data as Partial); default: throw new Error(`Unknown template type: ${type}`); } @@ -85,6 +99,8 @@ export const templateService = { return serviceNotificationTemplateService.deleteTemplate(id); case 'ssl': return sslNotificationTemplateService.deleteTemplate(id); + case 'server_threshold': + return serverThresholdService.deleteServerThreshold(id); default: throw new Error(`Unknown template type: ${type}`); } @@ -116,5 +132,12 @@ export const templateTypeConfigs = { '${domain}', '${certificate_name}', '${expiry_date}', '${days_left}', '${issuer}', '${serial_number}', '${time}' ] + }, + server_threshold: { + label: 'Server Threshold', + description: 'Templates for server resource threshold configurations', + placeholders: [ + '${name}', '${cpu_threshold}', '${ram_threshold}', '${disk_threshold}', '${network_threshold}' + ] } }; \ No newline at end of file
Error loading server threshold templates