/* === Batch Task Management Page === */
function PageOperationBatch() {
const [activeTab, setActiveTab] = React.useState("trigger");
const tabs = [
{ id: "trigger", label: "触发管理" },
{ id: "taskGroup", label: "任务定义" },
{ id: "monitor", label: "执行监控" },
{ id: "logs", label: "任务日志" }
];
return (
{tabs.map(tab => (
))}
{activeTab === "trigger" && }
{activeTab === "taskGroup" && }
{activeTab === "monitor" && }
{activeTab === "logs" && }
);
}
// 触发器管理组件
function TriggerManagement() {
const [triggers, setTriggers] = React.useState([]);
const [searchParams, setSearchParams] = React.useState({
triggerName: "",
status: ""
});
const [showModal, setShowModal] = React.useState(false);
const [editingTrigger, setEditingTrigger] = React.useState(null);
const [taskGroups, setTaskGroups] = React.useState([]);
const fetchTriggers = async () => {
try {
const params = new URLSearchParams();
if (searchParams.triggerName) params.append('trigger_name', searchParams.triggerName);
if (searchParams.status !== "") params.append('status', searchParams.status);
const data = await window.apiFetch(`/api/rule/trigger/list?${params}`);
setTriggers(Array.isArray(data) ? data : (data.items || []));
} catch (error) {
console.error('获取触发器列表失败:', error);
}
};
const fetchTaskGroups = async () => {
try {
const data = await window.apiFetch('/api/rule/taskGroup/list');
setTaskGroups(Array.isArray(data) ? data : (data.items || []));
} catch (error) {
console.error('获取任务组列表失败:', error);
}
};
React.useEffect(() => {
fetchTriggers();
fetchTaskGroups();
}, []);
const handleAdd = () => {
setEditingTrigger(null);
setShowModal(true);
};
const handleEdit = (trigger) => {
setEditingTrigger(trigger);
setShowModal(true);
};
const handleDelete = async (id) => {
if (!confirm('确定要删除该触发器吗?')) return;
try {
await window.apiFetch(`/api/rule/trigger/delete?id=${id}`, { method: 'DELETE' });
fetchTriggers();
} catch (error) {
alert('删除失败: ' + error.message);
}
};
const handleExecute = async (id) => {
if (!confirm('确定要立即执行该触发器吗?')) return;
try {
await window.apiFetch(`/api/rule/trigger/execute?id=${id}`, { method: 'POST' });
alert('执行已启动');
} catch (error) {
alert('执行失败: ' + error.message);
}
};
const handleToggle = async (id, status) => {
try {
await window.apiFetch(`/api/rule/trigger/toggle?id=${id}&status=${status}`, { method: 'POST' });
fetchTriggers();
} catch (error) {
alert('操作失败: ' + error.message);
}
};
const handleSave = async (formData) => {
try {
if (editingTrigger) {
await window.apiFetch('/api/rule/trigger/update', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...formData, id: editingTrigger.id })
});
} else {
await window.apiFetch('/api/rule/trigger/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
}
setShowModal(false);
fetchTriggers();
} catch (error) {
alert('保存失败: ' + error.message);
}
};
const getTaskGroupName = (groupId) => {
const group = taskGroups.find(g => g.id === groupId);
return group ? group.group_name : '-';
};
const statusText = { 1: '启用', 0: '禁用' };
const typeText = { CRON: '定时', MANUAL: '手动', API: 'API', EVENT: '事件' };
return (
| 触发器编码 |
触发器名称 |
类型 |
CRON表达式 |
绑定任务组 |
状态 |
创建时间 |
操作 |
{triggers.map(trigger => (
{trigger.trigger_code} |
{trigger.trigger_name} |
{typeText[trigger.trigger_type] || trigger.trigger_type} |
{trigger.cron_expr || '-'} |
{getTaskGroupName(trigger.task_group_id)} |
{statusText[trigger.status]}
|
{trigger.create_time?.split('T')[0]} |
{trigger.status === 1 && (
)}
|
))}
{showModal && (
setShowModal(false)}
onSave={handleSave}
/>
)}
);
}
// 触发器弹窗组件
function TriggerModal({ trigger, taskGroups, onClose, onSave }) {
const [formData, setFormData] = React.useState({
trigger_code: "",
trigger_name: "",
trigger_type: "CRON",
cron_expr: "",
task_group_id: "",
status: 1,
remark: ""
});
React.useEffect(() => {
if (trigger) {
setFormData({
trigger_code: trigger.trigger_code,
trigger_name: trigger.trigger_name,
trigger_type: trigger.trigger_type,
cron_expr: trigger.cron_expr || "",
task_group_id: trigger.task_group_id,
status: trigger.status,
remark: trigger.remark || ""
});
}
}, [trigger]);
const handleSubmit = (e) => {
e.preventDefault();
onSave(formData);
};
return (
e.stopPropagation()}>
{trigger ? '编辑触发器' : '新增触发器'}
);
}
// 任务组管理组件
function TaskGroupManagement() {
const [groups, setGroups] = React.useState([]);
const [selectedGroup, setSelectedGroup] = React.useState(null);
const [tasks, setTasks] = React.useState([]);
const [showGroupModal, setShowGroupModal] = React.useState(false);
const [showTaskModal, setShowTaskModal] = React.useState(false);
const [editingGroup, setEditingGroup] = React.useState(null);
const [editingTask, setEditingTask] = React.useState(null);
const fetchGroups = async () => {
try {
const data = await window.apiFetch('/api/rule/taskGroup/list');
const groupsData = Array.isArray(data) ? data : (data.items || []);
setGroups(groupsData);
if (groupsData.length > 0 && !selectedGroup) {
setSelectedGroup(groupsData[0]);
}
} catch (error) {
console.error('获取任务组失败:', error);
}
};
const fetchTasks = async (groupId) => {
if (!groupId) return;
try {
const data = await window.apiFetch(`/api/rule/taskDef/list?group_id=${groupId}`);
setTasks(Array.isArray(data) ? data : (data.items || []));
} catch (error) {
console.error('获取子任务失败:', error);
}
};
React.useEffect(() => {
fetchGroups();
}, []);
React.useEffect(() => {
if (selectedGroup) {
fetchTasks(selectedGroup.id);
}
}, [selectedGroup]);
const handleAddGroup = () => {
setEditingGroup(null);
setShowGroupModal(true);
};
const handleEditGroup = (group) => {
setEditingGroup(group);
setShowGroupModal(true);
};
const handleDeleteGroup = async (id) => {
if (!confirm('确定要删除该任务组吗?')) return;
try {
await window.apiFetch(`/api/rule/taskGroup/delete?id=${id}`, { method: 'DELETE' });
fetchGroups();
if (selectedGroup?.id === id) setSelectedGroup(null);
} catch (error) {
alert('删除失败: ' + error.message);
}
};
const handleSaveGroup = async (formData) => {
try {
if (editingGroup) {
await window.apiFetch('/api/rule/taskGroup/update', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...formData, id: editingGroup.id })
});
} else {
await window.apiFetch('/api/rule/taskGroup/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
}
setShowGroupModal(false);
fetchGroups();
} catch (error) {
alert('保存失败: ' + error.message);
}
};
const handleAddTask = () => {
setEditingTask(null);
setShowTaskModal(true);
};
const handleEditTask = (task) => {
setEditingTask(task);
setShowTaskModal(true);
};
const handleDeleteTask = async (id) => {
if (!confirm('确定要删除该子任务吗?')) return;
try {
await window.apiFetch(`/api/rule/taskDef/delete?id=${id}`, { method: 'DELETE' });
fetchTasks(selectedGroup.id);
} catch (error) {
alert('删除失败: ' + error.message);
}
};
const handleSaveTask = async (formData) => {
try {
const data = { ...formData, group_id: selectedGroup.id };
if (editingTask) {
await window.apiFetch('/api/rule/taskDef/update', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...data, id: editingTask.id })
});
} else {
await window.apiFetch('/api/rule/taskDef/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
setShowTaskModal(false);
fetchTasks(selectedGroup.id);
} catch (error) {
alert('保存失败: ' + error.message);
}
};
const typeText = { SEQUENCE: '顺序', PARALLEL: '并行', MIXED: '混合' };
const taskTypeText = { API: 'API', HTTP: 'HTTP', INNER: '内部' };
return (
任务组列表
{groups.map(group => (
setSelectedGroup(group)}
>
{group.group_name}
{typeText[group.group_type]}
))}
{selectedGroup ? (
<>
{selectedGroup.group_name}
编码: {selectedGroup.group_code}
类型: {typeText[selectedGroup.group_type]}
| 顺序 |
任务编码 |
任务名称 |
类型 |
URL |
重试 |
超时 |
执行控制 |
操作 |
{tasks.map(task => (
| {task.task_order} |
{task.task_code} |
{task.task_name} |
{taskTypeText[task.task_type]} |
{task.url || '-'} |
{task.retry_count}次 |
{task.timeout}秒 |
{task.ignore_failure === 1 && 忽略失败}
{task.forced_execution === 1 && 强制执行}
|
|
))}
{tasks.length === 0 && (
📭
暂无子任务
该任务组下暂无子任务
)}
>
) : (
📋
请选择任务组
请选择或创建一个任务组
)}
{showGroupModal && (
setShowGroupModal(false)}
onSave={handleSaveGroup}
/>
)}
{showTaskModal && (
setShowTaskModal(false)}
onSave={handleSaveTask}
/>
)}
);
}
// 任务组弹窗组件
function TaskGroupModal({ group, onClose, onSave }) {
const [formData, setFormData] = React.useState({
group_code: "",
group_name: "",
group_type: "SEQUENCE",
remark: ""
});
React.useEffect(() => {
if (group) {
setFormData({
group_code: group.group_code,
group_name: group.group_name,
group_type: group.group_type,
remark: group.remark || ""
});
}
}, [group]);
const handleSubmit = (e) => {
e.preventDefault();
onSave(formData);
};
return (
e.stopPropagation()}>
{group ? '编辑任务组' : '新增任务组'}
);
}
// 子任务弹窗组件
function TaskDefModal({ task, groupId, existingTasks, onClose, onSave }) {
const [formData, setFormData] = React.useState({
task_code: "",
task_name: "",
task_type: "API",
task_order: existingTasks.length + 1,
url: "",
method: "POST",
request_body: "",
retry_count: 0,
timeout: 30,
ignore_failure: 0,
forced_execution: 0,
dep_task_ids: [],
dep_type: "SUCCESS",
remark: ""
});
React.useEffect(() => {
if (task) {
setFormData({
task_code: task.task_code,
task_name: task.task_name,
task_type: task.task_type,
task_order: task.task_order,
url: task.url || "",
method: task.method || "POST",
request_body: task.request_body ? JSON.stringify(task.request_body, null, 2) : "",
retry_count: task.retry_count,
timeout: task.timeout,
ignore_failure: task.ignore_failure,
forced_execution: task.forced_execution,
dep_task_ids: task.dep_tasks?.map(t => t.id) || [],
dep_type: "SUCCESS",
remark: task.remark || ""
});
} else {
setFormData({
task_code: "",
task_name: "",
task_type: "API",
task_order: existingTasks.length + 1,
url: "",
method: "POST",
request_body: "",
retry_count: 0,
timeout: 30,
ignore_failure: 0,
forced_execution: 0,
dep_task_ids: [],
dep_type: "SUCCESS",
remark: ""
});
}
}, [task, existingTasks]);
const handleSubmit = (e) => {
e.preventDefault();
const data = { ...formData };
if (data.request_body) {
try {
data.request_body = JSON.parse(data.request_body);
} catch {
data.request_body = null;
}
}
onSave(data);
};
const availableTasks = existingTasks.filter(t => t.id !== task?.id);
return (
e.stopPropagation()}>
{task ? '编辑子任务' : '新增子任务'}
);
}
// 执行监控组件
function TaskMonitor() {
const [instances, setInstances] = React.useState([]);
const [selectedInstance, setSelectedInstance] = React.useState(null);
const [showDetail, setShowDetail] = React.useState(false);
const [searchParams, setSearchParams] = React.useState({
status: ""
});
const fetchInstances = async () => {
try {
const params = new URLSearchParams();
if (searchParams.status) params.append('status', searchParams.status);
const data = await window.apiFetch(`/api/rule/taskInstance/list?${params}`);
setInstances(Array.isArray(data) ? data : (data.items || []));
} catch (error) {
console.error('获取执行实例失败:', error);
}
};
const fetchInstanceDetail = async (id) => {
try {
const data = await window.apiFetch(`/api/rule/taskInstance/detail?id=${id}`);
setSelectedInstance(data);
setShowDetail(true);
} catch (error) {
console.error('获取实例详情失败:', error);
}
};
React.useEffect(() => {
fetchInstances();
const interval = setInterval(fetchInstances, 5000);
return () => clearInterval(interval);
}, [searchParams]);
const handleTerminate = async (id) => {
if (!confirm('确定要终止该执行实例吗?')) return;
try {
await window.apiFetch(`/api/rule/taskInstance/terminate?id=${id}`, { method: 'POST' });
fetchInstances();
} catch (error) {
alert('终止失败: ' + error.message);
}
};
const handleRetry = async (id) => {
if (!confirm('确定要重试该执行实例吗?')) return;
try {
await window.apiFetch(`/api/rule/taskInstance/retry?id=${id}`, { method: 'POST' });
fetchInstances();
} catch (error) {
alert('重试失败: ' + error.message);
}
};
const statusText = { RUNNING: '运行中', SUCCESS: '成功', FAILED: '失败', TERMINATED: '终止' };
return (
| 实例编码 |
任务组ID |
触发方式 |
状态 |
开始时间 |
结束时间 |
进度 |
操作 |
{instances.map(instance => {
const progress = instance.total_count > 0 ? Math.round(((instance.success_count + instance.fail_count) / instance.total_count) * 100) : 0;
return (
{instance.instance_code} |
{instance.group_id} |
{instance.trigger_type} |
{statusText[instance.status]} |
{instance.start_time?.split('T').join(' ')} |
{instance.end_time?.split('T').join(' ') || '-'} |
{instance.success_count + instance.fail_count}/{instance.total_count}
|
{instance.status === 'RUNNING' && (
)}
{(instance.status === 'FAILED' || instance.status === 'TERMINATED') && (
)}
|
);
})}
{showDetail && selectedInstance && (
setShowDetail(false)}
/>
)}
);
}
// 实例详情弹窗
function InstanceDetailModal({ instance, onClose }) {
const statusText = { PENDING: '待执行', RUNNING: '运行中', SUCCESS: '成功', FAILED: '失败' };
const statusColor = { PENDING: 'pending', RUNNING: 'running', SUCCESS: 'success', FAILED: 'failed' };
const taskLogs = instance.task_logs || [];
return (
e.stopPropagation()}>
{instance.instance_code}
{instance.status}
开始时间: {instance.start_time?.split('T').join(' ')}
结束时间: {instance.end_time?.split('T').join(' ') || '-'}
执行进度
总任务
{instance.total_count}
成功
{instance.success_count}
失败
{instance.fail_count}
{taskLogs.map(log => (
{taskLogs.indexOf(log) + 1}
{log.task_name || log.task_code}
{log.start_time?.split('T').join(' ') || '-'}
{log.end_time && 耗时: {log.elapsed || 0}ms}
{log.error_msg && (
)}
))}
{instance.context_data && (
上下文数据
{JSON.stringify(instance.context_data, null, 2)}
)}
);
}
// 任务日志组件
function TaskLogs() {
const [logs, setLogs] = React.useState([]);
const [selectedLog, setSelectedLog] = React.useState(null);
const [showDetail, setShowDetail] = React.useState(false);
const [searchParams, setSearchParams] = React.useState({
taskCode: "",
status: ""
});
const fetchLogs = async () => {
try {
const params = new URLSearchParams();
if (searchParams.taskCode) params.append('task_code', searchParams.taskCode);
if (searchParams.status) params.append('status', searchParams.status);
const data = await window.apiFetch(`/api/rule/taskLog/list?${params}`);
setLogs(Array.isArray(data) ? data : (data.items || []));
} catch (error) {
console.error('获取任务日志失败:', error);
}
};
const fetchLogDetail = async (id) => {
try {
const data = await window.apiFetch(`/api/rule/taskLog/detail?id=${id}`);
setSelectedLog(data);
setShowDetail(true);
} catch (error) {
console.error('获取日志详情失败:', error);
}
};
React.useEffect(() => {
fetchLogs();
}, []);
const statusText = { PENDING: '待执行', RUNNING: '运行中', SUCCESS: '成功', FAILED: '失败' };
return (
| ID |
实例编码 |
任务名称 |
任务编码 |
状态 |
开始时间 |
结束时间 |
耗时 |
重试 |
操作 |
{logs.map(log => (
| {log.id} |
{log.instance_code || '-'} |
{log.task_name || '-'} |
{log.task_code} |
{statusText[log.status]} |
{log.start_time?.split('T').join(' ')} |
{log.end_time?.split('T').join(' ') || '-'} |
{log.elapsed || 0}ms |
{log.retry_times}次 |
|
))}
{showDetail && selectedLog && (
setShowDetail(false)}
/>
)}
);
}
// 日志详情弹窗
function LogDetailModal({ log, onClose }) {
return (
e.stopPropagation()}>
实例编码
{log.instance_code || '-'}
任务名称
{log.task_name || '-'}
开始时间
{log.start_time?.split('T').join(' ')}
结束时间
{log.end_time?.split('T').join(' ') || '-'}
{log.request_data && (
请求数据
{JSON.stringify(log.request_data, null, 2)}
)}
{log.response_data && (
响应数据
{JSON.stringify(log.response_data, null, 2)}
)}
{log.error_msg && (
)}
);
}