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:
attacker committed 2026-06-15 01:09:49 +08:00
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>
);
}