Add restore messages to server notification templates

- Add new fields for restore messages to the Server Template Dialog to match the DB schema.
This commit is contained in:
Tola Leng
2025-08-11 23:09:43 +07:00
parent 86503b7160
commit 36dfd8a52c
3 changed files with 202 additions and 35 deletions
@@ -93,6 +93,87 @@ export const ServerTemplateFields: React.FC<ServerTemplateFieldsProps> = ({ cont
</CardContent> </CardContent>
</Card> </Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium">System Resource Restore Messages</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField
control={control}
name="restore_cpu_message"
render={({ field }) => (
<FormItem>
<FormLabel>CPU Restore Message</FormLabel>
<FormControl>
<Textarea
placeholder="CPU usage on ${server_name} has returned to normal: ${cpu_usage}%"
className="min-h-20"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="restore_ram_message"
render={({ field }) => (
<FormItem>
<FormLabel>RAM Restore Message</FormLabel>
<FormControl>
<Textarea
placeholder="Memory usage on ${server_name} has returned to normal: ${ram_usage}%"
className="min-h-20"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="restore_disk_message"
render={({ field }) => (
<FormItem>
<FormLabel>Disk Restore Message</FormLabel>
<FormControl>
<Textarea
placeholder="Disk usage on ${server_name} has returned to normal: ${disk_usage}%"
className="min-h-20"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="restore_network_message"
render={({ field }) => (
<FormItem>
<FormLabel>Network Restore Message</FormLabel>
<FormControl>
<Textarea
placeholder="Network usage on ${server_name} has returned to normal: ${network_usage}%"
className="min-h-20"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
<Card> <Card>
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
@@ -203,6 +284,42 @@ export const ServerTemplateFields: React.FC<ServerTemplateFieldsProps> = ({ cont
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={control}
name="restore_cpu_temp_message"
render={({ field }) => (
<FormItem>
<FormLabel>CPU Temperature Restore Message</FormLabel>
<FormControl>
<Textarea
placeholder="CPU temperature on ${server_name} has returned to normal: ${cpu_temp}°C"
className="min-h-20"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="restore_disk_io_message"
render={({ field }) => (
<FormItem>
<FormLabel>Disk I/O Restore Message</FormLabel>
<FormControl>
<Textarea
placeholder="Disk I/O on ${server_name} has returned to normal: ${disk_io} MB/s"
className="min-h-20"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -1,4 +1,3 @@
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod"; import * as z from "zod";
@@ -29,6 +28,12 @@ const serverTemplateSchema = z.object({
paused_message: z.string().min(1, "Paused message is required"), paused_message: z.string().min(1, "Paused message is required"),
cpu_temp_message: z.string().min(1, "CPU temperature message is required"), cpu_temp_message: z.string().min(1, "CPU temperature message is required"),
disk_io_message: z.string().min(1, "Disk I/O message is required"), disk_io_message: z.string().min(1, "Disk I/O message is required"),
restore_ram_message: z.string().optional(),
restore_cpu_message: z.string().optional(),
restore_disk_message: z.string().optional(),
restore_network_message: z.string().optional(),
restore_cpu_temp_message: z.string().optional(),
restore_disk_io_message: z.string().optional(),
}); });
// Service template schema // Service template schema
@@ -98,6 +103,12 @@ const getDefaultValues = (templateType: TemplateType): TemplateFormData => {
paused_message: "Monitoring for server ${server_name} is paused", paused_message: "Monitoring for server ${server_name} is paused",
cpu_temp_message: "CPU temperature on ${server_name} is ${cpu_temp}°C", cpu_temp_message: "CPU temperature on ${server_name} is ${cpu_temp}°C",
disk_io_message: "Disk I/O on ${server_name} is ${disk_io} MB/s", disk_io_message: "Disk I/O on ${server_name} is ${disk_io} MB/s",
restore_ram_message: "Memory usage on ${server_name} has returned to normal: ${ram_usage}%",
restore_cpu_message: "CPU usage on ${server_name} has returned to normal: ${cpu_usage}%",
restore_disk_message: "Disk usage on ${server_name} has returned to normal: ${disk_usage}%",
restore_network_message: "Network usage on ${server_name} has returned to normal: ${network_usage}%",
restore_cpu_temp_message: "CPU temperature on ${server_name} has returned to normal: ${cpu_temp}°C",
restore_disk_io_message: "Disk I/O on ${server_name} has returned to normal: ${disk_io} MB/s",
}; };
case 'service': case 'service':
@@ -150,7 +161,7 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange,
const { toast } = useToast(); const { toast } = useToast();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const isEditMode = !!templateId; const isEditMode = !!templateId;
const form = useForm<TemplateFormData>({ const form = useForm<TemplateFormData>({
resolver: zodResolver(templateFormSchema), resolver: zodResolver(templateFormSchema),
defaultValues: getDefaultValues(templateType), defaultValues: getDefaultValues(templateType),
@@ -162,6 +173,7 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange,
queryKey: ['template', templateId, templateType], queryKey: ['template', templateId, templateType],
queryFn: () => { queryFn: () => {
if (!templateId) return null; if (!templateId) return null;
return templateService.getTemplate(templateId, templateType); return templateService.getTemplate(templateId, templateType);
}, },
enabled: !!templateId && open, enabled: !!templateId && open,
@@ -170,33 +182,54 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange,
// Set form values when template data is loaded // Set form values when template data is loaded
useEffect(() => { useEffect(() => {
if (templateData && open) { if (templateData && open) {
const formData: any = { const formData: any = {
name: templateData.name || "", name: templateData.name || "",
templateType: templateType, templateType: templateType,
placeholder: (templateData as any).placeholder || "", placeholder: (templateData as any).placeholder || "",
}; };
// Add template-specific fields with proper type conversion for server_threshold // Define the expected fields for each template type
Object.keys(templateData).forEach(key => { const expectedFields = {
if (!['id', 'collectionId', 'collectionName', 'created', 'updated', 'name'].includes(key)) { server: [
let value = (templateData as any)[key]; 'ram_message', 'cpu_message', 'disk_message', 'network_message',
'up_message', 'down_message', 'notification_id', 'warning_message',
// Convert string values to numbers for server threshold fields 'paused_message', 'cpu_temp_message', 'disk_io_message',
if (templateType === 'server_threshold' && 'restore_ram_message', 'restore_cpu_message', 'restore_disk_message',
['cpu_threshold', 'ram_threshold', 'disk_threshold', 'network_threshold'].includes(key)) { 'restore_network_message', 'restore_cpu_temp_message', 'restore_disk_io_message'
value = typeof value === 'string' ? Number(value) : value; ],
// Ensure valid number, fallback to default if invalid service: [
if (isNaN(value)) { 'up_message', 'down_message', 'maintenance_message', 'incident_message',
const defaults = getDefaultValues(templateType) as any; 'resolved_message', 'warning_message'
value = defaults[key] || 0; ],
} ssl: ['expired', 'exiring_soon', 'warning'],
server_threshold: [
'cpu_threshold', 'ram_threshold', 'disk_threshold', 'network_threshold',
'notification_id', 'server_template_id'
]
};
// Add template-specific fields
const fieldsToProcess = expectedFields[templateType] || [];
fieldsToProcess.forEach(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 || "";
} }
// Set the value, using empty string as fallback for string fields
formData[key] = value !== undefined && value !== null ? value : "";
}); });
console.log("Final form data being set:", formData);
form.reset(formData); form.reset(formData);
} }
}, [templateData, open, form, templateType]); }, [templateData, open, form, templateType]);
@@ -213,6 +246,7 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange,
onSuccess(); onSuccess();
}, },
onError: (error) => { onError: (error) => {
toast({ toast({
title: "Error", title: "Error",
description: "Failed to create template. Please check your inputs and try again.", description: "Failed to create template. Please check your inputs and try again.",
@@ -234,6 +268,7 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange,
onSuccess(); onSuccess();
}, },
onError: (error) => { onError: (error) => {
toast({ toast({
title: "Error", title: "Error",
description: "Failed to update template. Please check your inputs and try again.", description: "Failed to update template. Please check your inputs and try again.",
@@ -246,14 +281,17 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange,
// Handle form submission // Handle form submission
const onSubmit = (formData: TemplateFormData) => { const onSubmit = (formData: TemplateFormData) => {
// Remove templateType from the data before sending to API // Remove templateType from the data before sending to API
const { templateType: _, ...templateDataWithoutType } = formData; const { templateType: _, ...templateDataWithoutType } = formData;
const completeData = templateDataWithoutType as AnyTemplateData; const completeData = templateDataWithoutType as AnyTemplateData;
if (isEditMode && templateId) { if (isEditMode && templateId) {
updateMutation.mutate({ id: templateId, data: completeData }); updateMutation.mutate({ id: templateId, data: completeData });
} else { } else {
createMutation.mutate(completeData); createMutation.mutate(completeData);
} }
}; };
@@ -261,6 +299,7 @@ export const useTemplateForm = ({ templateId, templateType, open, onOpenChange,
// Reset form when dialog closes or template type changes // Reset form when dialog closes or template type changes
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
console.log("Dialog closed, resetting form");
form.reset(getDefaultValues(templateType)); form.reset(getDefaultValues(templateType));
} }
}, [open, form, templateType]); }, [open, form, templateType]);
@@ -1,4 +1,3 @@
import { pb } from "@/lib/pocketbase"; import { pb } from "@/lib/pocketbase";
export interface ServerNotificationTemplate { export interface ServerNotificationTemplate {
@@ -17,6 +16,12 @@ export interface ServerNotificationTemplate {
paused_message: string; paused_message: string;
cpu_temp_message: string; cpu_temp_message: string;
disk_io_message: string; disk_io_message: string;
restore_ram_message: string;
restore_cpu_message: string;
restore_disk_message: string;
restore_network_message: string;
restore_disk_io_message: string;
restore_cpu_temp_message: string;
placeholder: string; placeholder: string;
created: string; created: string;
updated: string; updated: string;
@@ -35,68 +40,74 @@ export interface CreateUpdateServerNotificationTemplateData {
paused_message: string; paused_message: string;
cpu_temp_message: string; cpu_temp_message: string;
disk_io_message: string; disk_io_message: string;
restore_ram_message: string;
restore_cpu_message: string;
restore_disk_message: string;
restore_network_message: string;
restore_disk_io_message: string;
restore_cpu_temp_message: string;
placeholder: string; placeholder: string;
} }
export const serverNotificationTemplateService = { export const serverNotificationTemplateService = {
async getTemplates(): Promise<ServerNotificationTemplate[]> { async getTemplates(): Promise<ServerNotificationTemplate[]> {
try { try {
// console.log("Fetching server notification templates");
const response = await pb.collection('server_notification_templates').getList(1, 50, { const response = await pb.collection('server_notification_templates').getList(1, 50, {
sort: '-created', sort: '-created',
}); });
// console.log("Server notification templates response:", response);
return response.items as unknown as ServerNotificationTemplate[]; return response.items as unknown as ServerNotificationTemplate[];
} catch (error) { } catch (error) {
// console.error("Error fetching server notification templates:", error);
throw error; throw error;
} }
}, },
async getTemplate(id: string): Promise<ServerNotificationTemplate> { async getTemplate(id: string): Promise<ServerNotificationTemplate> {
try { try {
// console.log(`Fetching server notification template with id: ${id}`);
const response = await pb.collection('server_notification_templates').getOne(id); const response = await pb.collection('server_notification_templates').getOne(id);
// console.log("Server notification template response:", response);
return response as unknown as ServerNotificationTemplate; return response as unknown as ServerNotificationTemplate;
} catch (error) { } catch (error) {
/// console.error(`Error fetching server notification template with id ${id}:`, error);
throw error; throw error;
} }
}, },
async createTemplate(data: CreateUpdateServerNotificationTemplateData): Promise<ServerNotificationTemplate> { async createTemplate(data: CreateUpdateServerNotificationTemplateData): Promise<ServerNotificationTemplate> {
try { try {
// console.log("Creating new server notification template with data:", data);
const response = await pb.collection('server_notification_templates').create(data); const response = await pb.collection('server_notification_templates').create(data);
// console.log("Create server notification template response:", response);
return response as unknown as ServerNotificationTemplate; return response as unknown as ServerNotificationTemplate;
} catch (error) { } catch (error) {
// console.error("Error creating server notification template:", error);
throw error; throw error;
} }
}, },
async updateTemplate(id: string, data: Partial<CreateUpdateServerNotificationTemplateData>): Promise<ServerNotificationTemplate> { async updateTemplate(id: string, data: Partial<CreateUpdateServerNotificationTemplateData>): Promise<ServerNotificationTemplate> {
try { try {
// console.log(`Updating server notification template with id: ${id}`, data);
const response = await pb.collection('server_notification_templates').update(id, data); const response = await pb.collection('server_notification_templates').update(id, data);
// console.log("Update server notification template response:", response);
return response as unknown as ServerNotificationTemplate; return response as unknown as ServerNotificationTemplate;
} catch (error) { } catch (error) {
// console.error(`Error updating server notification template with id ${id}:`, error);
throw error; throw error;
} }
}, },
async deleteTemplate(id: string): Promise<boolean> { async deleteTemplate(id: string): Promise<boolean> {
try { try {
// console.log(`Deleting server notification template with id: ${id}`);
await pb.collection('server_notification_templates').delete(id); await pb.collection('server_notification_templates').delete(id);
// console.log("Server notification template deleted successfully");
return true; return true;
} catch (error) { } catch (error) {
// console.error(`Error deleting server notification template with id ${id}:`, error);
throw error; throw error;
} }
} }