diff --git a/frontend/src/pages/Resource/Server/List/components/AlertRuleFormDialog.tsx b/frontend/src/pages/Resource/Server/List/components/AlertRuleFormDialog.tsx new file mode 100644 index 00000000..56430486 --- /dev/null +++ b/frontend/src/pages/Resource/Server/List/components/AlertRuleFormDialog.tsx @@ -0,0 +1,394 @@ +import React, { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Loader2 } from 'lucide-react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogBody, + DialogFooter, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Switch } from '@/components/ui/switch'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from '@/components/ui/form'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { useToast } from '@/components/ui/use-toast'; +import type { AlertRuleResponse, ServerResponse, AlertRuleRequest } from '../types'; +import { AlertType, AlertTypeLabels } from '../types'; +import { alertRuleFormSchema, type AlertRuleFormValues } from '../schema'; +import { createAlertRule, updateAlertRule, getServers } from '../service'; + +interface AlertRuleFormDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + rule: AlertRuleResponse | null; + onSuccess?: () => void; +} + +export const AlertRuleFormDialog: React.FC = ({ + open, + onOpenChange, + rule, + onSuccess, +}) => { + const { toast } = useToast(); + const [loading, setLoading] = useState(false); + const [servers, setServers] = useState([]); + + const form = useForm({ + resolver: zodResolver(alertRuleFormSchema), + defaultValues: { + serverId: null, + ruleName: '', + alertType: AlertType.CPU, + warningThreshold: 75, + criticalThreshold: 90, + durationMinutes: 5, + enabled: true, + description: '', + }, + }); + + // 加载服务器列表 + useEffect(() => { + if (open) { + loadServers(); + } + }, [open]); + + const loadServers = async () => { + try { + const result = await getServers({ pageNum: 0, size: 1000 }); + setServers(result?.content || []); + } catch (error) { + console.error('加载服务器列表失败:', error); + } + }; + + // 编辑时初始化表单 + useEffect(() => { + if (open && rule) { + form.reset({ + serverId: rule.serverId, + ruleName: rule.ruleName, + alertType: rule.alertType, + warningThreshold: rule.warningThreshold, + criticalThreshold: rule.criticalThreshold, + durationMinutes: rule.durationMinutes, + enabled: rule.enabled, + description: rule.description || '', + }); + } else if (open && !rule) { + form.reset({ + serverId: null, + ruleName: '', + alertType: AlertType.CPU, + warningThreshold: 75, + criticalThreshold: 90, + durationMinutes: 5, + enabled: true, + description: '', + }); + } + }, [open, rule, form]); + + const onSubmit = async (values: AlertRuleFormValues) => { + setLoading(true); + try { + const requestData: AlertRuleRequest = { + serverId: values.serverId ?? null, + ruleName: values.ruleName!, + alertType: values.alertType!, + warningThreshold: values.warningThreshold!, + criticalThreshold: values.criticalThreshold!, + durationMinutes: values.durationMinutes!, + enabled: values.enabled!, + description: values.description, + }; + + if (rule) { + await updateAlertRule(rule.id, requestData); + toast({ + title: '更新成功', + description: `告警规则"${values.ruleName}"已更新`, + }); + } else { + await createAlertRule(requestData); + toast({ + title: '创建成功', + description: `告警规则"${values.ruleName}"已创建`, + }); + } + onSuccess?.(); + onOpenChange(false); + } catch (error: any) { + toast({ + variant: 'destructive', + title: rule ? '更新失败' : '创建失败', + description: error.response?.data?.message || '操作失败', + }); + } finally { + setLoading(false); + } + }; + + return ( + + + + {rule ? '编辑告警规则' : '新增告警规则'} + + + +
+ +
+ {/* 规则范围 */} + ( + + 规则范围 + + + 全局规则适用于所有服务器,专属规则只对指定服务器生效(会覆盖同类型的全局规则) + + + + )} + /> + + {/* 规则名称 */} + ( + + + 规则名称 * + + + + + + + )} + /> + + {/* 告警类型 */} + ( + + + 告警类型 * + + + + + )} + /> + + {/* 持续时长 */} + ( + + + 持续时长(分钟) * + + + field.onChange(Number(e.target.value))} + /> + + + 触发告警前需要持续超过阈值的时间 + + + + )} + /> + + {/* 警告阈值 */} + ( + + + 警告阈值 * + + +
+ field.onChange(Number(e.target.value))} + className="flex-1" + /> + + {AlertTypeLabels[form.watch('alertType')]?.unit || '%'} + +
+
+ +
+ )} + /> + + {/* 严重阈值 */} + ( + + + 严重阈值 * + + +
+ field.onChange(Number(e.target.value))} + className="flex-1" + /> + + {AlertTypeLabels[form.watch('alertType')]?.unit || '%'} + +
+
+ +
+ )} + /> + + {/* 是否启用 */} + ( + +
+ 启用规则 + + 启用后规则将立即生效,关闭后规则将暂停监控 + +
+ + + +
+ )} + /> + + {/* 描述 */} + ( + + 描述 + +