From f3e1b81a3a37bdf18d83f754093d858cbab195ce Mon Sep 17 00:00:00 2001 From: attacker Date: Sun, 14 Jun 2026 12:08:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=91=8A=E8=AD=A6=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=85=B3=E8=81=94=E7=9C=9F=E5=AE=9E=E9=80=9A=E7=9F=A5=E6=B8=A0?= =?UTF-8?q?=E9=81=93=20=E2=80=94=20=E8=A7=84=E5=88=99=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=B7=B2=E9=85=8D=E7=BD=AE=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3=E7=A1=AC=E7=BC=96=E7=A0=81=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 规则创建/编辑表单: 用实际已配置的通知渠道列表(运维钉钉群/DBA邮箱等)替代硬编码的email/sms/dingtalk类型开关 - 规则表notify_channels列: 渠道ID解析为渠道名称+类型图标(替代原先仅显示3个固定图标) - 组件挂载时预加载notifyChannels(原先仅在通知渠道弹窗打开时加载) - 向后兼容: resolveChannelRef同时支持新格式(渠道ID)和旧格式(类型字符串) - 数据修复: 恢复3条被软删除的种子通知渠道记录 --- .../src/components/monitor/AlertManager.tsx | 135 +++++++++++++----- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/monitor/AlertManager.tsx b/frontend/src/components/monitor/AlertManager.tsx index 4140485..743de5d 100644 --- a/frontend/src/components/monitor/AlertManager.tsx +++ b/frontend/src/components/monitor/AlertManager.tsx @@ -50,10 +50,6 @@ import { ResponsiveContainer, } from 'recharts'; -function hasChannel(notifyChannels: any, channel: string): boolean { - return parseChannels(notifyChannels).includes(channel); -} - // 将 notify_channels (可能是数组/JSON字符串/逗号分隔) 解析为数组 function parseChannels(notifyChannels: any): string[] { if (!notifyChannels) return []; @@ -70,6 +66,32 @@ function parseChannels(notifyChannels: any): string[] { return []; } +// 已知的渠道类型(用于旧数据兼容 — 旧规则存储类型字符串而非渠道 ID) +const KNOWN_CHANNEL_TYPES = ['email', 'sms', 'dingtalk', 'wechat', 'webhook']; + +// 根据渠道类型获取图标组件名(用字符串映射,组件内用 getChannelIcon 返回实际组件) +const CHANNEL_TYPE_LABELS: Record = { + dingtalk: '钉钉', + wechat: '企业微信', + email: '邮件', + sms: '短信', + webhook: 'Webhook', +}; + +// 解析单个 notify_channels 引用值 — 返回 {id?, type, name, isLegacy} +// 新格式存渠道 ID (string),旧格式存类型字符串 (email/dingtalk 等) +function resolveChannelRef(value: string, channels: any[]): { type: string; name: string; isLegacy: boolean } | null { + if (!value) return null; + // 尝试按 ID 匹配已配置渠道 + const ch = channels.find((c) => String(c.id) === String(value)); + if (ch) return { type: ch.type || 'unknown', name: ch.name || ch.type || '未命名', isLegacy: false }; + // 兼容旧格式:值本身是类型字符串 + if (KNOWN_CHANNEL_TYPES.includes(value)) { + return { type: value, name: CHANNEL_TYPE_LABELS[value] || value, isLegacy: true }; + } + return null; +} + export function AlertManager() { const [activeTab, setActiveTab] = useState('events'); const [searchQuery, setSearchQuery] = useState(''); @@ -288,15 +310,57 @@ export function AlertManager() { return CHANNEL_TYPES.find((t) => t.key === type)?.label || type; }; + // 根据渠道类型返回对应图标组件 + const getChannelIcon = (type: string) => { + switch (type) { + case 'email': + return Mail; + case 'sms': + return Phone; + case 'dingtalk': + case 'wechat': + return MessageSquare; + default: + return Bell; + } + }; + + // 渲染规则的通知渠道引用列表(兼容新格式=渠道ID 和 旧格式=类型字符串) + const renderRuleChannels = (rule: any) => { + const refs = parseChannels(rule.notify_channels); + if (refs.length === 0) return --; + return ( +
+ {refs.map((ref) => { + const resolved = resolveChannelRef(ref, notifyChannels); + if (!resolved) return null; + const Icon = getChannelIcon(resolved.type); + return ( + + + {resolved.name} + + ); + })} +
+ ); + }; + const loadData = async () => { try { setLoading(true); - const [alertsData, rulesData] = await Promise.all([ + const [alertsData, rulesData, channelsData] = await Promise.all([ alertApi.getAlerts(), alertApi.getRules(), + alertApi.getNotifyChannels(), ]); setAlerts((alertsData as any)?.list || []); setRules((rulesData as any)?.list || (Array.isArray(rulesData) ? rulesData : [])); + setNotifyChannels((channelsData as any)?.list || []); } catch (error) { console.error('Failed to load alert data:', error); } finally { @@ -702,17 +766,7 @@ export function AlertManager() { {(rule.duration || 0) / 60}分钟 -
- {hasChannel(rule.notify_channels, 'email') && ( - - )} - {hasChannel(rule.notify_channels, 'sms') && ( - - )} - {hasChannel(rule.notify_channels, 'dingtalk') && ( - - )} -
+ {renderRuleChannels(rule)}
-
- {[ - { key: 'email', label: '邮件', icon: Mail }, - { key: 'sms', label: '短信', icon: Phone }, - { key: 'dingtalk', label: '钉钉', icon: MessageSquare }, - ].map(({ key, label, icon: Icon }) => ( - - ))} -
+ {notifyChannels.length === 0 ? ( +
+ 暂无已配置的通知渠道,请先在「通知渠道配置」中添加 +
+ ) : ( +
+ {notifyChannels.map((ch) => { + const Icon = getChannelIcon(ch.type); + const checked = ruleForm.notify_channels.includes(String(ch.id)); + return ( + + ); + })} +
+ )}