refactor: AlertCenter拆分(723→390行)+修复TopologyView类型错误

- AlertCenter.tsx: 723→390行,提取类型/helpers/渠道Tab到子组件
- alert-center-helpers.tsx: 新建(127行),共享Alert/AlertRule/NotificationChannel类型+mapAlert+badge helpers
- AlertChannelDialog.tsx: 新建(121行),通知渠道创建/编辑对话框(自包含useEffect同步)
- AlertChannelsTab.tsx: 新建(152行),通知渠道Tab自包含(状态+API+渲染+CRUD)
- AlertCenterDetail.tsx: 去重getSeverityBadge,统一从helpers导入
- TopologyView.tsx: 修复editForm.type类型推断(string→TopologyNode['type'])
This commit is contained in:
attacker committed 2026-06-15 13:07:01 +08:00
1 parent 805cb767a8
commit 76a41f5eeb
6 files changed
+458 -406

No files matched your search

@@ -0,0 +1,121 @@
// 通知渠道创建/编辑对话框 // 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: '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>
);
}