/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ import { createStyles, Icon, useAPIClient, useApp, usePlugin, useRequest, useCollectionManager, useCompile, } from '@nocobase/client'; import { Button, Empty, Modal, Popconfirm, Popover, Progress, Space, Table, Tag, Tooltip } from 'antd'; import React, { useCallback, useEffect, useState } from 'react'; import { useCurrentAppInfo } from '@nocobase/client'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; import relativeTime from 'dayjs/plugin/relativeTime'; import { useT } from '../locale'; const useStyles = createStyles(({ token }) => { return { button: { // @ts-ignore color: token.colorTextHeaderMenu + ' !important', }, }; }); // Configure dayjs dayjs.extend(relativeTime); const renderTaskResult = (status, t) => { if (status.type !== 'success' || !status.payload?.message?.messageId) { return null; } const { messageId, messageValues } = status.payload.message; return (
{t(messageId, messageValues)}
); }; const useAsyncTask = () => { const { data, refreshAsync, loading } = useRequest({ url: 'asyncTasks:list', }); return { loading, tasks: data?.data || [], refresh: refreshAsync }; }; const AsyncTasksButton = (props) => { const { popoverVisible, setPopoverVisible, tasks, refresh, loading, hasProcessingTasks } = props; const app = useApp(); const api = useAPIClient(); const appInfo = useCurrentAppInfo(); const t = useT(); const { styles } = useStyles(); const plugin = usePlugin('async-task-manager'); const cm = useCollectionManager(); const compile = useCompile(); const showTaskResult = (task) => { setPopoverVisible(false); }; const columns = [ { title: t('Created at'), dataIndex: 'createdAt', key: 'createdAt', width: 180, render: (createdAt: string) => ( {dayjs(createdAt).fromNow()} ), }, { title: t('Task'), dataIndex: 'title', key: 'title', render: (_, record: any) => { const title = record.title; if (!title) { return '-'; } const collection = cm.getCollection(title.collection); const actionTypeMap = { export: t('Export'), import: t('Import'), 'export-attachments': t('Export attachments'), }; const actionText = actionTypeMap[title.actionType] || title.actionType; const taskTypeMap = { 'export-attachments': t('Export {collection} attachments'), export: t('Export {collection} data'), import: t('Import {collection} data'), }; const taskTemplate = taskTypeMap[title.actionType] || `${actionText}`; return taskTemplate.replace('{collection}', compile(collection?.title || title.collection)); }, }, { title: t('Status'), dataIndex: 'status', key: 'status', width: 160, render: (status: any, record: any) => { const statusMap = { pending: { color: 'default', text: t('Waiting'), icon: 'ClockCircleOutlined', }, running: { color: 'processing', text: t('Processing'), icon: 'LoadingOutlined', }, success: { color: 'success', text: t('Completed'), icon: 'CheckCircleOutlined', }, failed: { color: 'error', text: t('Failed'), icon: 'CloseCircleOutlined', }, cancelled: { color: 'warning', text: t('Cancelled'), icon: 'StopOutlined', }, }; const { color, text } = statusMap[status.type] || {}; const renderProgress = () => { const commonStyle = { width: 100, margin: 0, }; switch (status.indicator) { case 'spinner': return ( ); case 'progress': return ( `${percent.toFixed(1)}%`} /> ); case 'success': return ( ''} /> ); case 'error': return ( ''} /> ); default: return null; } }; return (
{renderProgress()}
: null} style={{ margin: 0, padding: '0 4px', height: 22, width: 22 }} /> {renderTaskResult(status, t)}
); }, }, { title: t('Actions'), key: 'actions', width: 180, render: (_, record: any) => { const actions = []; const isTaskCancelling = false; if ((record.status.type === 'running' || record.status.type === 'pending') && record.cancelable) { actions.push( { await api.request({ url: 'asyncTasks:cancel', params: { filterByTk: record.taskId, }, }); refresh(); }} okText={t('Confirm')} cancelText={t('Cancel')} disabled={isTaskCancelling} > , ); } if (record.status.type === 'success') { if (record.status.resultType === 'file') { actions.push( , ); } else if (record.status.payload) { actions.push( , ); } } if (record.status.type === 'failed') { actions.push( , ); } return {actions}; }, }, ]; const content = (
0 ? 800 : 200 }}> {tasks.length > 0 ? ( ) : (
)} ); return ( <>