- 后端: NotifyChannel 模型新增 feishu 类型, FeishuWebhookConfig - 后端: TestNotifyChannel 真实发送飞书测试消息(文本格式) - 后端: 告警引擎触发/恢复时自动发送飞书通知(sendFeishuAlert/sendFeishuRecovery) - 后端: AlertEngine 新增 tcp_time_wait/tcp_close_wait/service_process 指标检测 - 后端: NetworkStatModel 新增 TCPCloseWait 字段 - 后端: QuickSetupRules 一键创建6条告警规则(CPU/内存/磁盘/TIME_WAIT/CLOSE_WAIT/进程) - 后端: 注册 /alerts/quick-setup 路由 - 前端: AlertChannelDialog 新增飞书类型选项 - 前端: helpers 新增 getChannelIcon/getChannelTypeLabel 支持 feishu/dingtalk/wechat
123 lines
4.5 KiB
TypeScript
123 lines
4.5 KiB
TypeScript
// 通知渠道创建/编辑对话框 // 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 {
|
||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||
} from '@/components/ui/select';
|
||
import { toast } from 'sonner';
|
||
import { alertApi } from '@/services/api';
|
||
import type { NotificationChannel } from './alert-center-helpers';
|
||
|
||
const CHANNEL_TYPES = [
|
||
{ value: 'dingtalk', label: '钉钉', hint: '钉钉机器人: webhook URL + secret' },
|
||
{ value: 'wechat', label: '企业微信', hint: '企业微信: webhook URL' },
|
||
{ value: 'feishu', label: '飞书', hint: '飞书机器人: {"url":"https://open.feishu.cn/open-apis/bot/v2/hook/..."}' },
|
||
{ value: 'email', label: '邮件', hint: '邮件: {"to":"ops@ezops.io"}' },
|
||
{ value: 'sms', label: '短信', hint: '短信: {"phones":"13800138000"}' },
|
||
{ value: 'webhook', label: 'Webhook', hint: 'Webhook: {"url":"https://..."}' },
|
||
];
|
||
|
||
export function AlertChannelDialog({
|
||
open,
|
||
onOpenChange,
|
||
editingChannel,
|
||
onSaved,
|
||
}: {
|
||
open: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
editingChannel: NotificationChannel | null;
|
||
onSaved: () => void;
|
||
}) {
|
||
const [form, setForm] = useState({ name: '', type: 'dingtalk', config: '' });
|
||
const [saving, setSaving] = useState(false);
|
||
|
||
// Logan.Li — 对话框打开时同步编辑数据
|
||
useEffect(() => {
|
||
if (open) {
|
||
if (editingChannel) {
|
||
setForm({ name: editingChannel.name, type: editingChannel.type, config: editingChannel.config || '' });
|
||
} else {
|
||
setForm({ name: '', type: 'dingtalk', config: '' });
|
||
}
|
||
}
|
||
}, [open, editingChannel]);
|
||
|
||
const handleSave = async () => {
|
||
if (!form.name.trim()) return;
|
||
setSaving(true);
|
||
try {
|
||
const payload = { name: form.name.trim(), type: form.type, config: form.config.trim() };
|
||
if (editingChannel) {
|
||
await alertApi.updateNotifyChannel(editingChannel.id, payload);
|
||
toast.success('渠道已更新');
|
||
} else {
|
||
await alertApi.createNotifyChannel(payload);
|
||
toast.success('渠道已添加');
|
||
}
|
||
onOpenChange(false);
|
||
onSaved();
|
||
} catch {
|
||
toast.error('保存失败');
|
||
} finally {
|
||
setSaving(false);
|
||
}
|
||
};
|
||
|
||
const currentHint = CHANNEL_TYPES.find(t => t.value === form.type)?.hint || '';
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>{editingChannel ? '编辑通知渠道' : '添加通知渠道'}</DialogTitle>
|
||
<DialogDescription>配置告警通知渠道(钉钉/企业微信/邮件/短信/Webhook)</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>
|
||
<Select value={form.type} onValueChange={(v) => setForm({ ...form, type: v })}>
|
||
<SelectTrigger>
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{CHANNEL_TYPES.map(t => (
|
||
<SelectItem key={t.value} value={t.value}>{t.label}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label>配置(JSON)</Label>
|
||
<Input
|
||
value={form.config}
|
||
onChange={(e) => setForm({ ...form, config: e.target.value })}
|
||
placeholder='{"webhook":"https://...","secret":"..."}'
|
||
className="font-mono text-sm"
|
||
/>
|
||
<p className="text-xs text-muted-foreground">{currentHint}</p>
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => onOpenChange(false)}>取消</Button>
|
||
<Button onClick={handleSave} disabled={!form.name.trim() || saving}>
|
||
{saving ? '保存中...' : editingChannel ? '保存修改' : '添加渠道'}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
}
|