增加服务器阈值规则
This commit is contained in:
parent
e298dcf83c
commit
c36ee0808c
@ -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<AlertRuleFormDialogProps> = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
rule,
|
||||||
|
onSuccess,
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [servers, setServers] = useState<ServerResponse[]>([]);
|
||||||
|
|
||||||
|
const form = useForm<AlertRuleFormValues>({
|
||||||
|
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 (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{rule ? '编辑告警规则' : '新增告警规则'}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
{/* 规则范围 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="serverId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<FormLabel>规则范围</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value === null ? 'global' : field.value.toString()}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
field.onChange(value === 'global' ? null : Number(value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择规则范围" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="global">全局规则(适用于所有服务器)</SelectItem>
|
||||||
|
{servers.map((server) => (
|
||||||
|
<SelectItem key={server.id} value={server.id.toString()}>
|
||||||
|
专属规则 - {server.serverName} ({server.hostIp})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
全局规则适用于所有服务器,专属规则只对指定服务器生效(会覆盖同类型的全局规则)
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 规则名称 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="ruleName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<FormLabel>
|
||||||
|
规则名称 <span className="text-destructive">*</span>
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="例如:全局CPU告警规则" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 告警类型 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="alertType"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
告警类型 <span className="text-destructive">*</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择告警类型" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.entries(AlertTypeLabels).map(([key, value]) => (
|
||||||
|
<SelectItem key={key} value={key}>
|
||||||
|
{value.label} ({value.unit})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 持续时长 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="durationMinutes"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
持续时长(分钟) <span className="text-destructive">*</span>
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
触发告警前需要持续超过阈值的时间
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 警告阈值 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="warningThreshold"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
警告阈值 <span className="text-destructive">*</span>
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.1"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{AlertTypeLabels[form.watch('alertType')]?.unit || '%'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 严重阈值 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="criticalThreshold"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
严重阈值 <span className="text-destructive">*</span>
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.1"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{AlertTypeLabels[form.watch('alertType')]?.unit || '%'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 是否启用 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="enabled"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="col-span-2 flex items-center justify-between rounded-lg border p-4">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel className="text-base">启用规则</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
启用后规则将立即生效,关闭后规则将暂停监控
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 描述 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<FormLabel>描述</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="输入规则描述..."
|
||||||
|
rows={3}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onOpenChange(false)}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={loading}>
|
||||||
|
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
{rule ? '更新' : '创建'}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogBody>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,262 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Plus, Edit, Trash2, AlertTriangle, Bell } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogBody,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
|
import type { AlertRuleResponse, AlertRuleQuery } from '../types';
|
||||||
|
import { AlertType, AlertTypeLabels } from '../types';
|
||||||
|
import {
|
||||||
|
getAlertRulePage,
|
||||||
|
deleteAlertRule,
|
||||||
|
} from '../service';
|
||||||
|
import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
||||||
|
import { AlertRuleFormDialog } from './AlertRuleFormDialog';
|
||||||
|
|
||||||
|
interface AlertRuleManageDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertRuleManageDialog: React.FC<AlertRuleManageDialogProps> = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
onSuccess,
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const tableRef = useRef<PaginatedTableRef<AlertRuleResponse>>(null);
|
||||||
|
const [formDialogOpen, setFormDialogOpen] = useState(false);
|
||||||
|
const [editingRule, setEditingRule] = useState<AlertRuleResponse | null>(null);
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
const [ruleToDelete, setRuleToDelete] = useState<AlertRuleResponse | null>(null);
|
||||||
|
|
||||||
|
// 刷新列表
|
||||||
|
const handleSuccess = () => {
|
||||||
|
tableRef.current?.refresh();
|
||||||
|
onSuccess?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新建规则
|
||||||
|
const handleCreate = () => {
|
||||||
|
setEditingRule(null);
|
||||||
|
setFormDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑规则
|
||||||
|
const handleEdit = (rule: AlertRuleResponse) => {
|
||||||
|
setEditingRule(rule);
|
||||||
|
setFormDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除规则
|
||||||
|
const handleDelete = (rule: AlertRuleResponse) => {
|
||||||
|
setRuleToDelete(rule);
|
||||||
|
setDeleteDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!ruleToDelete) return;
|
||||||
|
await deleteAlertRule(ruleToDelete.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索字段定义
|
||||||
|
const searchFields: SearchFieldDef[] = [
|
||||||
|
{ key: 'ruleName', type: 'input', placeholder: '规则名称', width: 'w-[180px]' },
|
||||||
|
{
|
||||||
|
key: 'alertType',
|
||||||
|
type: 'select',
|
||||||
|
placeholder: '告警类型',
|
||||||
|
width: 'w-[140px]',
|
||||||
|
options: Object.entries(AlertTypeLabels).map(([key, value]) => ({
|
||||||
|
label: value.label,
|
||||||
|
value: key,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
type: 'select',
|
||||||
|
placeholder: '状态',
|
||||||
|
width: 'w-[120px]',
|
||||||
|
options: [
|
||||||
|
{ label: '启用', value: 'true' },
|
||||||
|
{ label: '禁用', value: 'false' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 列定义
|
||||||
|
const columns: ColumnDef<AlertRuleResponse>[] = [
|
||||||
|
{ key: 'id', title: 'ID', dataIndex: 'id', width: '80px' },
|
||||||
|
{ key: 'ruleName', title: '规则名称', dataIndex: 'ruleName', width: '200px' },
|
||||||
|
{
|
||||||
|
key: 'ruleScope',
|
||||||
|
title: '规则范围',
|
||||||
|
width: '150px',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Badge variant={record.serverId ? 'default' : 'secondary'}>
|
||||||
|
{record.serverId ? `专属规则 (${record.serverName || `ID:${record.serverId}`})` : '全局规则'}
|
||||||
|
</Badge>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'alertType',
|
||||||
|
title: '告警类型',
|
||||||
|
width: '120px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const typeInfo = AlertTypeLabels[record.alertType];
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<AlertTriangle className="h-4 w-4 text-orange-500" />
|
||||||
|
<span>{typeInfo.label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'thresholds',
|
||||||
|
title: '阈值',
|
||||||
|
width: '200px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const unit = AlertTypeLabels[record.alertType].unit;
|
||||||
|
return (
|
||||||
|
<div className="space-y-1 text-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant="outline" className="bg-yellow-50 text-yellow-700 border-yellow-300">
|
||||||
|
警告
|
||||||
|
</Badge>
|
||||||
|
<span>{record.warningThreshold}{unit}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant="outline" className="bg-red-50 text-red-700 border-red-300">
|
||||||
|
严重
|
||||||
|
</Badge>
|
||||||
|
<span>{record.criticalThreshold}{unit}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'durationMinutes',
|
||||||
|
title: '持续时长',
|
||||||
|
width: '100px',
|
||||||
|
render: (_, record) => `${record.durationMinutes} 分钟`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
title: '状态',
|
||||||
|
width: '100px',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Badge variant={record.enabled ? 'default' : 'secondary'}>
|
||||||
|
{record.enabled ? '启用' : '禁用'}
|
||||||
|
</Badge>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'description',
|
||||||
|
title: '描述',
|
||||||
|
dataIndex: 'description',
|
||||||
|
width: '200px',
|
||||||
|
render: (value) => value || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
width: '150px',
|
||||||
|
sticky: true,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleEdit(record)}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4 mr-1" />
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleDelete(record)}
|
||||||
|
className="text-destructive hover:text-destructive"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4 mr-1" />
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 工具栏
|
||||||
|
const toolbar = (
|
||||||
|
<Button onClick={handleCreate}>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
新增规则
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-[90vw] max-h-[85vh]">
|
||||||
|
<DialogHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Bell className="h-5 w-5 text-primary" />
|
||||||
|
<DialogTitle>告警规则管理</DialogTitle>
|
||||||
|
</div>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody className="overflow-hidden">
|
||||||
|
<PaginatedTable<AlertRuleResponse, AlertRuleQuery>
|
||||||
|
ref={tableRef}
|
||||||
|
fetchFn={getAlertRulePage}
|
||||||
|
columns={columns}
|
||||||
|
searchFields={searchFields}
|
||||||
|
toolbar={toolbar}
|
||||||
|
rowKey="id"
|
||||||
|
minWidth="1400px"
|
||||||
|
emptyText="暂无告警规则"
|
||||||
|
/>
|
||||||
|
</DialogBody>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* 规则编辑对话框 */}
|
||||||
|
<AlertRuleFormDialog
|
||||||
|
open={formDialogOpen}
|
||||||
|
onOpenChange={setFormDialogOpen}
|
||||||
|
rule={editingRule}
|
||||||
|
onSuccess={handleSuccess}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 删除确认对话框 */}
|
||||||
|
<ConfirmDialog
|
||||||
|
open={deleteDialogOpen}
|
||||||
|
onOpenChange={setDeleteDialogOpen}
|
||||||
|
title="确认删除"
|
||||||
|
description={`确定要删除告警规则"${ruleToDelete?.ruleName}"吗?此操作无法撤销。`}
|
||||||
|
item={ruleToDelete}
|
||||||
|
onConfirm={confirmDelete}
|
||||||
|
onSuccess={() => {
|
||||||
|
toast({
|
||||||
|
title: '删除成功',
|
||||||
|
description: `告警规则"${ruleToDelete?.ruleName}"已删除`,
|
||||||
|
});
|
||||||
|
setRuleToDelete(null);
|
||||||
|
handleSuccess();
|
||||||
|
}}
|
||||||
|
variant="destructive"
|
||||||
|
confirmText="确定"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
Activity,
|
Activity,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
|
Terminal,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -34,6 +35,7 @@ interface ServerTableProps {
|
|||||||
onTest: (server: ServerResponse) => void;
|
onTest: (server: ServerResponse) => void;
|
||||||
onEdit: (server: ServerResponse) => void;
|
onEdit: (server: ServerResponse) => void;
|
||||||
onDelete: (server: ServerResponse) => void;
|
onDelete: (server: ServerResponse) => void;
|
||||||
|
onSSHConnect: (server: ServerResponse) => void;
|
||||||
isTesting?: (serverId: number) => boolean;
|
isTesting?: (serverId: number) => boolean;
|
||||||
getOsIcon: (osType?: string) => React.ReactNode;
|
getOsIcon: (osType?: string) => React.ReactNode;
|
||||||
}
|
}
|
||||||
@ -43,6 +45,7 @@ export const ServerTable: React.FC<ServerTableProps> = ({
|
|||||||
onTest,
|
onTest,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onSSHConnect,
|
||||||
isTesting,
|
isTesting,
|
||||||
getOsIcon,
|
getOsIcon,
|
||||||
}) => {
|
}) => {
|
||||||
@ -74,7 +77,6 @@ export const ServerTable: React.FC<ServerTableProps> = ({
|
|||||||
<TableHead width="100px">内存(GB)</TableHead>
|
<TableHead width="100px">内存(GB)</TableHead>
|
||||||
<TableHead width="100px">磁盘(GB)</TableHead>
|
<TableHead width="100px">磁盘(GB)</TableHead>
|
||||||
<TableHead width="120px">SSH认证</TableHead>
|
<TableHead width="120px">SSH认证</TableHead>
|
||||||
<TableHead width="140px">分类</TableHead>
|
|
||||||
<TableHead width="150px">最后连接</TableHead>
|
<TableHead width="150px">最后连接</TableHead>
|
||||||
<TableHead sticky width="140px" className="text-right">操作</TableHead>
|
<TableHead sticky width="140px" className="text-right">操作</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -82,7 +84,7 @@ export const ServerTable: React.FC<ServerTableProps> = ({
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{servers.length === 0 ? (
|
{servers.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={11} className="text-center py-8">
|
<TableCell colSpan={10} className="text-center py-8">
|
||||||
<span className="text-muted-foreground">暂无服务器数据</span>
|
<span className="text-muted-foreground">暂无服务器数据</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -129,16 +131,24 @@ export const ServerTable: React.FC<ServerTableProps> = ({
|
|||||||
{server.authType === 'PASSWORD' ? '密码' : '密钥'}
|
{server.authType === 'PASSWORD' ? '密码' : '密钥'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
<Badge variant="secondary" className="text-xs">
|
|
||||||
{server.categoryName || '未分类'}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span className="text-sm text-muted-foreground">{formatTime(server.lastConnectTime)}</span>
|
<span className="text-sm text-muted-foreground">{formatTime(server.lastConnectTime)}</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sticky className="text-right">
|
<TableCell sticky className="text-right">
|
||||||
<div className="flex items-center justify-end gap-1">
|
<div className="flex items-center justify-end gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onSSHConnect(server)}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Terminal className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>SSH连接</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
RotateCcw,
|
RotateCcw,
|
||||||
Grid3x3,
|
Grid3x3,
|
||||||
List,
|
List,
|
||||||
|
Bell,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Card, CardHeader, CardContent } from '@/components/ui/card';
|
import { Card, CardHeader, CardContent } from '@/components/ui/card';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
@ -37,6 +38,7 @@ import type { ServerResponse, ServerCategoryResponse, ServerStatus, OsType } fro
|
|||||||
import { ServerStatusLabels, OsTypeLabels } from './types';
|
import { ServerStatusLabels, OsTypeLabels } from './types';
|
||||||
import { getServers, getServerCategories, deleteServer, testServerConnection } from './service';
|
import { getServers, getServerCategories, deleteServer, testServerConnection } from './service';
|
||||||
import { CategoryManageDialog } from './components/CategoryManageDialog';
|
import { CategoryManageDialog } from './components/CategoryManageDialog';
|
||||||
|
import { AlertRuleManageDialog } from './components/AlertRuleManageDialog';
|
||||||
import { ServerEditDialog } from './components/ServerEditDialog';
|
import { ServerEditDialog } from './components/ServerEditDialog';
|
||||||
import { ServerCard } from './components/ServerCard';
|
import { ServerCard } from './components/ServerCard';
|
||||||
import { ServerTable } from './components/ServerTable';
|
import { ServerTable } from './components/ServerTable';
|
||||||
@ -71,6 +73,7 @@ const ServerList: React.FC = () => {
|
|||||||
const [tempOsType, setTempOsType] = useState<OsType | undefined>();
|
const [tempOsType, setTempOsType] = useState<OsType | undefined>();
|
||||||
|
|
||||||
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
||||||
|
const [alertRuleDialogOpen, setAlertRuleDialogOpen] = useState(false);
|
||||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
const [editingServer, setEditingServer] = useState<ServerResponse | null>(null);
|
const [editingServer, setEditingServer] = useState<ServerResponse | null>(null);
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
@ -282,28 +285,10 @@ const ServerList: React.FC = () => {
|
|||||||
{/* 顶部区域 - 统计和标题 */}
|
{/* 顶部区域 - 统计和标题 */}
|
||||||
<div className="flex-shrink-0 space-y-4 p-6">
|
<div className="flex-shrink-0 space-y-4 p-6">
|
||||||
{/* 标题栏 */}
|
{/* 标题栏 */}
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">服务器管理</h1>
|
<h1 className="text-3xl font-bold">服务器管理</h1>
|
||||||
<p className="text-muted-foreground mt-1">管理和监控服务器资源,支持SSH连接和分类管理</p>
|
<p className="text-muted-foreground mt-1">管理和监控服务器资源,支持SSH连接和分类管理</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setCategoryDialogOpen(true)}>
|
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
|
||||||
分类管理
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setEditingServer(null);
|
|
||||||
setEditDialogOpen(true);
|
|
||||||
}}
|
|
||||||
className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700"
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
新增服务器
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 统计卡片 - 更紧凑的布局 */}
|
{/* 统计卡片 - 更紧凑的布局 */}
|
||||||
<div className="grid gap-3 md:grid-cols-4">
|
<div className="grid gap-3 md:grid-cols-4">
|
||||||
@ -470,6 +455,23 @@ const ServerList: React.FC = () => {
|
|||||||
<Search className="h-4 w-4 mr-1.5" />
|
<Search className="h-4 w-4 mr-1.5" />
|
||||||
查询
|
查询
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => setAlertRuleDialogOpen(true)}>
|
||||||
|
<Bell className="h-4 w-4 mr-2" />
|
||||||
|
告警规则
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => setCategoryDialogOpen(true)}>
|
||||||
|
<Settings className="h-4 w-4 mr-2" />
|
||||||
|
分类管理
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setEditingServer(null);
|
||||||
|
setEditDialogOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
新增服务器
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -545,6 +547,7 @@ const ServerList: React.FC = () => {
|
|||||||
onTest={handleTestConnection}
|
onTest={handleTestConnection}
|
||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
onSSHConnect={handleSSHConnect}
|
||||||
isTesting={(serverId) => testingServerId === serverId}
|
isTesting={(serverId) => testingServerId === serverId}
|
||||||
getOsIcon={getOsIcon}
|
getOsIcon={getOsIcon}
|
||||||
/>
|
/>
|
||||||
@ -581,6 +584,13 @@ const ServerList: React.FC = () => {
|
|||||||
onSuccess={handleSuccess}
|
onSuccess={handleSuccess}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 告警规则管理对话框 */}
|
||||||
|
<AlertRuleManageDialog
|
||||||
|
open={alertRuleDialogOpen}
|
||||||
|
onOpenChange={setAlertRuleDialogOpen}
|
||||||
|
onSuccess={handleSuccess}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 服务器编辑对话框 */}
|
{/* 服务器编辑对话框 */}
|
||||||
<ServerEditDialog
|
<ServerEditDialog
|
||||||
open={editDialogOpen}
|
open={editDialogOpen}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { OsType, AuthType, ServerStatus } from './types';
|
import { OsType, AuthType, ServerStatus, AlertType } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器分类表单校验
|
* 服务器分类表单校验
|
||||||
@ -73,3 +73,35 @@ export const serverFormSchema = z.object({
|
|||||||
export type ServerCategoryFormValues = z.infer<typeof serverCategoryFormSchema>;
|
export type ServerCategoryFormValues = z.infer<typeof serverCategoryFormSchema>;
|
||||||
export type ServerFormValues = z.infer<typeof serverFormSchema>;
|
export type ServerFormValues = z.infer<typeof serverFormSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警规则表单校验
|
||||||
|
*/
|
||||||
|
export const alertRuleFormSchema = z.object({
|
||||||
|
serverId: z.union([z.number(), z.null()]).default(null),
|
||||||
|
ruleName: z.string().min(1, '规则名称不能为空').max(100, '规则名称不能超过100个字符'),
|
||||||
|
alertType: z.nativeEnum(AlertType, {
|
||||||
|
errorMap: () => ({ message: '请选择告警类型' })
|
||||||
|
}),
|
||||||
|
warningThreshold: z.number()
|
||||||
|
.min(0, '警告阈值不能小于0')
|
||||||
|
.max(100, '警告阈值不能大于100'),
|
||||||
|
criticalThreshold: z.number()
|
||||||
|
.min(0, '严重阈值不能小于0')
|
||||||
|
.max(100, '严重阈值不能大于100'),
|
||||||
|
durationMinutes: z.number()
|
||||||
|
.min(1, '持续时长至少为1分钟'),
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
description: z.string().max(500, '描述不能超过500个字符').optional(),
|
||||||
|
}).superRefine((data, ctx) => {
|
||||||
|
// 严重阈值必须大于警告阈值
|
||||||
|
if (data.criticalThreshold <= data.warningThreshold) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: 'custom',
|
||||||
|
message: '严重阈值必须大于警告阈值',
|
||||||
|
path: ['criticalThreshold'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AlertRuleFormValues = z.infer<typeof alertRuleFormSchema>;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,9 @@ import type {
|
|||||||
ServerResponse,
|
ServerResponse,
|
||||||
ServerRequest,
|
ServerRequest,
|
||||||
ServerConnectionTestResult,
|
ServerConnectionTestResult,
|
||||||
|
AlertRuleResponse,
|
||||||
|
AlertRuleQuery,
|
||||||
|
AlertRuleRequest,
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { ServerFormValues } from './schema';
|
import type { ServerFormValues } from './schema';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
@ -13,6 +16,7 @@ import type { Page } from '@/types/base';
|
|||||||
// API 基础路径
|
// API 基础路径
|
||||||
const CATEGORY_URL = '/api/v1/server-category';
|
const CATEGORY_URL = '/api/v1/server-category';
|
||||||
const SERVER_URL = '/api/v1/server';
|
const SERVER_URL = '/api/v1/server';
|
||||||
|
const ALERT_RULE_URL = '/api/v1/server/alert-rule';
|
||||||
|
|
||||||
// ==================== 服务器分类 ====================
|
// ==================== 服务器分类 ====================
|
||||||
|
|
||||||
@ -117,3 +121,41 @@ export const batchDeleteServers = (ids: number[]) =>
|
|||||||
export const testServerConnection = (id: number) =>
|
export const testServerConnection = (id: number) =>
|
||||||
request.post<ServerConnectionTestResult>(`${SERVER_URL}/${id}/test-connection`);
|
request.post<ServerConnectionTestResult>(`${SERVER_URL}/${id}/test-connection`);
|
||||||
|
|
||||||
|
// ==================== 告警规则 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取告警规则列表
|
||||||
|
*/
|
||||||
|
export const getAlertRules = (params?: AlertRuleQuery) =>
|
||||||
|
request.get<AlertRuleResponse[]>(`${ALERT_RULE_URL}/list`, { params });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取告警规则分页列表
|
||||||
|
*/
|
||||||
|
export const getAlertRulePage = (params?: AlertRuleQuery) =>
|
||||||
|
request.get<Page<AlertRuleResponse>>(`${ALERT_RULE_URL}/page`, { params });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取告警规则详情
|
||||||
|
*/
|
||||||
|
export const getAlertRule = (id: number) =>
|
||||||
|
request.get<AlertRuleResponse>(`${ALERT_RULE_URL}/${id}`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建告警规则
|
||||||
|
*/
|
||||||
|
export const createAlertRule = (data: AlertRuleRequest) =>
|
||||||
|
request.post<AlertRuleResponse>(ALERT_RULE_URL, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新告警规则
|
||||||
|
*/
|
||||||
|
export const updateAlertRule = (id: number, data: AlertRuleRequest) =>
|
||||||
|
request.put<AlertRuleResponse>(`${ALERT_RULE_URL}/${id}`, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除告警规则
|
||||||
|
*/
|
||||||
|
export const deleteAlertRule = (id: number) =>
|
||||||
|
request.delete<void>(`${ALERT_RULE_URL}/${id}`);
|
||||||
|
|
||||||
|
|||||||
@ -262,3 +262,93 @@ export interface ServerConnectionTestResult {
|
|||||||
diskSize: number;
|
diskSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 告警规则 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警类型枚举
|
||||||
|
*/
|
||||||
|
export enum AlertType {
|
||||||
|
/** CPU使用率 */
|
||||||
|
CPU = 'CPU',
|
||||||
|
/** 内存使用率 */
|
||||||
|
MEMORY = 'MEMORY',
|
||||||
|
/** 磁盘使用率 */
|
||||||
|
DISK = 'DISK',
|
||||||
|
/** 网络流量 */
|
||||||
|
NETWORK = 'NETWORK',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警规则响应类型
|
||||||
|
*/
|
||||||
|
export interface AlertRuleResponse extends BaseResponse {
|
||||||
|
/** 服务器ID(null表示全局规则) */
|
||||||
|
serverId: number | null;
|
||||||
|
/** 规则名称 */
|
||||||
|
ruleName: string;
|
||||||
|
/** 告警类型 */
|
||||||
|
alertType: AlertType;
|
||||||
|
/** 警告阈值 */
|
||||||
|
warningThreshold: number;
|
||||||
|
/** 严重阈值 */
|
||||||
|
criticalThreshold: number;
|
||||||
|
/** 持续时长(分钟) */
|
||||||
|
durationMinutes: number;
|
||||||
|
/** 描述 */
|
||||||
|
description?: string;
|
||||||
|
/** 服务器名称(关联数据) */
|
||||||
|
serverName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警规则查询参数
|
||||||
|
*/
|
||||||
|
export interface AlertRuleQuery {
|
||||||
|
/** 服务器ID */
|
||||||
|
serverId?: number | null;
|
||||||
|
/** 规则名称 */
|
||||||
|
ruleName?: string;
|
||||||
|
/** 告警类型 */
|
||||||
|
alertType?: AlertType;
|
||||||
|
/** 是否启用 */
|
||||||
|
enabled?: boolean;
|
||||||
|
/** 页码(从0开始) */
|
||||||
|
page?: number;
|
||||||
|
/** 每页数量 */
|
||||||
|
size?: number;
|
||||||
|
/** 排序(例如:id,desc) */
|
||||||
|
sort?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警规则请求类型
|
||||||
|
*/
|
||||||
|
export interface AlertRuleRequest {
|
||||||
|
/** 服务器ID(null表示全局规则) */
|
||||||
|
serverId: number | null;
|
||||||
|
/** 规则名称 */
|
||||||
|
ruleName: string;
|
||||||
|
/** 告警类型 */
|
||||||
|
alertType: AlertType;
|
||||||
|
/** 警告阈值 */
|
||||||
|
warningThreshold: number;
|
||||||
|
/** 严重阈值 */
|
||||||
|
criticalThreshold: number;
|
||||||
|
/** 持续时长(分钟) */
|
||||||
|
durationMinutes: number;
|
||||||
|
/** 是否启用 */
|
||||||
|
enabled: boolean;
|
||||||
|
/** 描述 */
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警类型标签映射
|
||||||
|
*/
|
||||||
|
export const AlertTypeLabels: Record<AlertType, { label: string; unit: string; description: string }> = {
|
||||||
|
[AlertType.CPU]: { label: 'CPU使用率', unit: '%', description: 'CPU使用率告警' },
|
||||||
|
[AlertType.MEMORY]: { label: '内存使用率', unit: '%', description: '内存使用率告警' },
|
||||||
|
[AlertType.DISK]: { label: '磁盘使用率', unit: '%', description: '磁盘使用率告警' },
|
||||||
|
[AlertType.NETWORK]: { label: '网络流量', unit: 'MB/s', description: '网络流量告警' },
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user