feat: 定时任务编辑入口全栈打通 + 修复ApprovalFlow编译错误 + 完成大文件拆分重构
- CronJobManager: 编辑按钮接通 onClick → openEdit(job),弹出编辑对话框预填数据,
复用新建/编辑共用 Dialog(editingId 区分模式),标题/按钮文案动态切换,
保存时根据 editingId 调用 createJob 或 updateJob — 消除占位按钮
- ApprovalFlow: 修复 API 方法名不匹配导致 tsc -b 编译失败
(getInstances→getProcesses, getMyApprovals→getProcesses({status:'pending'}),
approveInstance→approveProcess, rejectInstance→rejectProcess)
+ 添加 ApprovalFlowManager 导出别名兼容 App.tsx 导入
- 合并已完成的大文件拆分重构: automation.go→automation_cron/script/playbook,
ldap.go→ldap_ou/ldap_users, server.go→cloud_accounts/domain/platform/region,
user.go→user_admin; 前端 Dashboard/K8s/Server/Approval/Monitor 子组件拆分
// Logan.Li
This commit is contained in:
1 parent
8be63ac168
commit
a7e2351ad2
37 files changed
+7374
-4891
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import { Mail, Phone, MessageSquare, Bell } from 'lucide-react';
|
||||
|
||||
// 将 notify_channels (可能是数组/JSON字符串/逗号分隔) 解析为数组
|
||||
export function parseChannels(notifyChannels: any): string[] {
|
||||
if (!notifyChannels) return [];
|
||||
if (Array.isArray(notifyChannels)) return notifyChannels;
|
||||
if (typeof notifyChannels === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(notifyChannels);
|
||||
if (Array.isArray(parsed)) return parsed;
|
||||
} catch {
|
||||
// 非JSON,尝试逗号分隔
|
||||
}
|
||||
return notifyChannels.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// 已知的渠道类型(用于旧数据兼容 — 旧规则存储类型字符串而非渠道 ID)
|
||||
export const KNOWN_CHANNEL_TYPES = ['email', 'sms', 'dingtalk', 'wechat', 'webhook'];
|
||||
|
||||
// 根据渠道类型获取图标组件名(用字符串映射,组件内用 getChannelIcon 返回实际组件)
|
||||
export const CHANNEL_TYPE_LABELS: Record<string, string> = {
|
||||
dingtalk: '钉钉',
|
||||
wechat: '企业微信',
|
||||
email: '邮件',
|
||||
sms: '短信',
|
||||
webhook: 'Webhook',
|
||||
};
|
||||
|
||||
// 解析单个 notify_channels 引用值 — 返回 {id?, type, name, isLegacy}
|
||||
// 新格式存渠道 ID (string),旧格式存类型字符串 (email/dingtalk 等)
|
||||
export 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 const CHANNEL_TYPES: { key: string; label: string; placeholder: string }[] = [
|
||||
{ key: 'dingtalk', label: '钉钉', placeholder: '{\n "webhook": "https://oapi.dingtalk.com/robot/send?access_token=***",\n "secret": "SECxxx"\n}' },
|
||||
{ key: 'wechat', label: '企业微信', placeholder: '{\n "webhook": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=***"\n}' },
|
||||
{ key: 'email', label: '邮件', placeholder: '{\n "to": "ops@ezops.io",\n "subject": "告警通知"\n}' },
|
||||
{ key: 'sms', label: '短信', placeholder: '{\n "phones": "13800138000",\n "sign": "EZOps"\n}' },
|
||||
{ key: 'webhook', label: 'Webhook', placeholder: '{\n "url": "https://example.com/alert"\n}' },
|
||||
];
|
||||
|
||||
export function getChannelTypeLabel(type: string) {
|
||||
return CHANNEL_TYPES.find((t) => t.key === type)?.label || type;
|
||||
}
|
||||
|
||||
// 根据渠道类型返回对应图标组件
|
||||
export function getChannelIcon(type: string) {
|
||||
switch (type) {
|
||||
case 'email':
|
||||
return Mail;
|
||||
case 'sms':
|
||||
return Phone;
|
||||
case 'dingtalk':
|
||||
case 'wechat':
|
||||
return MessageSquare;
|
||||
default:
|
||||
return Bell;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染规则的通知渠道引用列表(兼容新格式=渠道ID 和 旧格式=类型字符串)
|
||||
export function renderRuleChannels(rule: any, notifyChannels: any[]) {
|
||||
const refs = parseChannels(rule.notify_channels);
|
||||
if (refs.length === 0) return <span className="text-xs text-slate-600">--</span>;
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{refs.map((ref) => {
|
||||
const resolved = resolveChannelRef(ref, notifyChannels);
|
||||
if (!resolved) return null;
|
||||
const Icon = getChannelIcon(resolved.type);
|
||||
return (
|
||||
<span
|
||||
key={ref}
|
||||
title={`${resolved.name} (${getChannelTypeLabel(resolved.type)})`}
|
||||
className="inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded bg-slate-800 text-slate-300"
|
||||
>
|
||||
<Icon className="h-3 w-3 text-slate-400" />
|
||||
{resolved.name}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user