diff --git a/web/src/api/tasks.ts b/web/src/api/tasks.ts index 708357a..2781f71 100644 --- a/web/src/api/tasks.ts +++ b/web/src/api/tasks.ts @@ -14,9 +14,19 @@ export interface AutomationTask { export type AutomationTaskCreate = Omit; export type AutomationTaskUpdate = Partial; +export interface QueuedTask { + id: string; + name: string; + status: 'pending' | 'running' | 'success' | 'failed'; + result?: any; + error?: string; + task_info: Record; +} + export const tasksApi = { list: () => request('/tasks/'), create: (payload: AutomationTaskCreate) => request('/tasks/', { method: 'POST', json: payload }), update: (id: number, payload: AutomationTaskUpdate) => request(`/tasks/${id}`, { method: 'PUT', json: payload }), remove: (id: number) => request(`/tasks/${id}`, { method: 'DELETE' }), + getQueue: () => request('/tasks/queue'), }; \ No newline at end of file diff --git a/web/src/pages/TasksPage.tsx b/web/src/pages/TasksPage.tsx index 1cb9a45..2a8032a 100644 --- a/web/src/pages/TasksPage.tsx +++ b/web/src/pages/TasksPage.tsx @@ -1,7 +1,7 @@ import { memo, useState, useEffect, useCallback } from 'react'; -import { Table, Button, Space, Drawer, Form, Input, Switch, message, Typography, Popconfirm, Select } from 'antd'; +import { Table, Button, Space, Drawer, Form, Input, Switch, message, Typography, Popconfirm, Select, Modal, Tag } from 'antd'; import PageCard from '../components/PageCard'; -import { tasksApi, type AutomationTask } from '../api/tasks'; +import { tasksApi, type AutomationTask, type QueuedTask } from '../api/tasks'; import { processorsApi, type ProcessorTypeMeta } from '../api/processors'; import { ProcessorConfigForm } from '../components/ProcessorConfigForm'; @@ -12,6 +12,9 @@ const TasksPage = memo(function TasksPage() { const [editing, setEditing] = useState(null); const [form] = Form.useForm(); const [availableProcessors, setAvailableProcessors] = useState([]); + const [queueModalOpen, setQueueModalOpen] = useState(false); + const [queuedTasks, setQueuedTasks] = useState([]); + const [queueLoading, setQueueLoading] = useState(false); const fetchList = useCallback(async () => { setLoading(true); @@ -86,11 +89,50 @@ const TasksPage = memo(function TasksPage() { } }; + const fetchQueue = async () => { + setQueueLoading(true); + try { + const tasks = await tasksApi.getQueue(); + setQueuedTasks(tasks); + } catch (e: any) { + message.error(e.message || '加载队列失败'); + } finally { + setQueueLoading(false); + } + }; + + const openQueueModal = () => { + setQueueModalOpen(true); + fetchQueue(); + }; + + const toggleEnabled = async (rec: AutomationTask, enabled: boolean) => { + setEditing(rec); + setLoading(true); + try { + await tasksApi.update(rec.id, { enabled }); + message.success('状态已更新'); + fetchList(); + } catch (e: any) { + message.error(e.message || '更新失败'); + } finally { + setEditing(null); + setLoading(false); + } + }; + const columns = [ { title: '名称', dataIndex: 'name' }, { title: '触发事件', dataIndex: 'event', width: 120 }, { title: '处理器', dataIndex: 'processor_type', width: 180 }, - { title: '启用', dataIndex: 'enabled', width: 80, render: (v: boolean) => }, + { + title: '启用', dataIndex: 'enabled', width: 80, render: (v: boolean, rec: AutomationTask) => toggleEnabled(rec, checked)} + /> + }, { title: '操作', width: 160, @@ -115,6 +157,7 @@ const TasksPage = memo(function TasksPage() { extra={ + } @@ -174,6 +217,40 @@ const TasksPage = memo(function TasksPage() { /> + setQueueModalOpen(false)} + width={800} + footer={[ + , + + ]} + > + {id.slice(0, 8)} }, + { title: '任务名', dataIndex: 'name' }, + { title: '参数', dataIndex: 'task_info', render: (info) => {JSON.stringify(info)} }, + { + title: '状态', dataIndex: 'status', width: 100, render: (status: QueuedTask['status']) => { + const colorMap = { + pending: 'default', + running: 'processing', + success: 'success', + failed: 'error' + }; + return {status}; + } + }, + ]} + /> + ); });