feat: 网络双线图+告警联系人/联系组+Agent v2.0.0 + 一键报警阈值

- HostMonitor: 网络改为双线图 rx/tx 网卡带宽吞吐(双Area线), 移除单rx图
- Agent: 默认版本号从 dev 改为 v2.0.0, 重建二进制 5.2MB
- 告警中心: 新增报警联系人/报警联系组 Tab, ContactDialog/ContactGroupDialog
- 后端: AlertContact/AlertContactGroup 模型+CRUD, monitor_alert_contacts/groups 表
- 路由: /alerts/contacts + /alerts/contact-groups 8个REST端点
- 一键报警: 更新描述为6条规则(含TCP+进程), 切换到 /alerts/quick-setup
- 测试规则: 内存>10% 触发告警已创建, 飞书通知验证通过
This commit is contained in:
attacker committed 2026-06-24 08:47:40 +00:00
1 parent 5333f6f04a
commit e1cfbaf45e
11 files changed
+810 -16

No files matched your search

@@ -0,0 +1,143 @@
// 报警联系人创建/编辑对话框 // Logan.Li
import React, { useState, useEffect } from 'react';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { toast } from 'sonner';
import { alertApi } from '@/services/api';
import type { Contact } from './alert-center-helpers';
export function ContactDialog({
open,
onOpenChange,
editingContact,
onSaved,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
editingContact: Contact | null;
onSaved: () => void;
}) {
const [form, setForm] = useState({
name: '', phone: '', email: '', dingtalk: '', feishu: '', wechat: '',
});
const [saving, setSaving] = useState(false);
useEffect(() => {
if (open) {
if (editingContact) {
setForm({
name: editingContact.name || '',
phone: editingContact.phone || '',
email: editingContact.email || '',
dingtalk: editingContact.dingtalk || '',
feishu: editingContact.feishu || '',
wechat: editingContact.wechat || '',
});
} else {
setForm({ name: '', phone: '', email: '', dingtalk: '', feishu: '', wechat: '' });
}
}
}, [open, editingContact]);
const handleSave = async () => {
if (!form.name.trim()) return;
setSaving(true);
try {
const payload = {
name: form.name.trim(),
phone: form.phone.trim() || undefined,
email: form.email.trim() || undefined,
dingtalk: form.dingtalk.trim() || undefined,
feishu: form.feishu.trim() || undefined,
wechat: form.wechat.trim() || undefined,
};
if (editingContact) {
await alertApi.updateContact(editingContact.id, payload);
toast.success('联系人已更新');
} else {
await alertApi.createContact(payload);
toast.success('联系人已添加');
}
onOpenChange(false);
onSaved();
} catch {
toast.error('保存失败');
} finally {
setSaving(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{editingContact ? '编辑报警联系人' : '添加报警联系人'}</DialogTitle>
<DialogDescription>配置报警联系人信息(姓名必填,渠道选填)</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>姓名 *</Label>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="如:张三"
/>
</div>
<div className="space-y-2">
<Label>手机号</Label>
<Input
value={form.phone}
onChange={(e) => setForm({ ...form, phone: e.target.value })}
placeholder="如:13800138000"
/>
</div>
<div className="space-y-2">
<Label>邮箱</Label>
<Input
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
placeholder="如:zhangsan@example.com"
/>
</div>
<div className="space-y-2">
<Label>钉钉(webhook URL)</Label>
<Input
value={form.dingtalk}
onChange={(e) => setForm({ ...form, dingtalk: e.target.value })}
placeholder="https://oapi.dingtalk.com/robot/send?access_token=..."
className="font-mono text-sm"
/>
</div>
<div className="space-y-2">
<Label>飞书(webhook URL)</Label>
<Input
value={form.feishu}
onChange={(e) => setForm({ ...form, feishu: e.target.value })}
placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/..."
className="font-mono text-sm"
/>
</div>
<div className="space-y-2">
<Label>企业微信(webhook URL)</Label>
<Input
value={form.wechat}
onChange={(e) => setForm({ ...form, wechat: e.target.value })}
placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
className="font-mono text-sm"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>取消</Button>
<Button onClick={handleSave} disabled={!form.name.trim() || saving}>
{saving ? '保存中...' : editingContact ? '保存修改' : '添加联系人'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}