import intl from 'react-intl-universal'; import React, { useEffect, useRef, useState } from 'react'; import { Modal, message, Input, Form, Button, Card, Tag, List, Divider, Typography, Tooltip, } from 'antd'; import { ClockCircleOutlined, CloseCircleOutlined, FieldTimeOutlined, Loading3QuartersOutlined, FileOutlined, PlayCircleOutlined, PauseCircleOutlined, FullscreenOutlined, } from '@ant-design/icons'; import { CrontabStatus } from './type'; import { diffTime } from '@/utils/date'; import { request } from '@/utils/http'; import config from '@/utils/config'; import CronLogModal from './logModal'; import Editor from '@monaco-editor/react'; import IconFont from '@/components/iconfont'; import { getCommandScript, getEditorMode } from '@/utils'; import VirtualList from 'rc-virtual-list'; import useScrollHeight from '@/hooks/useScrollHeight'; import dayjs from 'dayjs'; const { Text } = Typography; const tabList = [ { key: 'log', tab: intl.get('日志'), }, { key: 'script', tab: intl.get('脚本'), }, ]; interface LogItem { directory: string; filename: string; } const CronDetailModal = ({ cron = {}, handleCancel, theme, isPhone, }: { cron?: any; handleCancel: (needUpdate?: boolean) => void; theme: string; isPhone: boolean; }) => { const [activeTabKey, setActiveTabKey] = useState('log'); const [loading, setLoading] = useState(true); const [logs, setLogs] = useState([]); const [log, setLog] = useState(''); const [value, setValue] = useState(''); const [isLogModalVisible, setIsLogModalVisible] = useState(false); const editorRef = useRef(null); const [scriptInfo, setScriptInfo] = useState({}); const [logUrl, setLogUrl] = useState(''); const [validTabs, setValidTabs] = useState(tabList); const [currentCron, setCurrentCron] = useState({}); const listRef = useRef(null); const tableScrollHeight = useScrollHeight(listRef); const contentList: any = { log: (
{(item) => ( onClickItem(item)}> {item.directory}/{item.filename} )}
), script: scriptInfo.filename && ( { editorRef.current = editor; }} /> ), }; const onClickItem = (item: LogItem) => { const url = `${config.apiPrefix}logs/detail?file=${item.filename}&path=${ item.directory || '' }`; localStorage.setItem('logCron', url); setLogUrl(url); request.get(url).then(({ code, data }) => { if (code === 200) { setLog(data); setIsLogModalVisible(true); } }); }; const onTabChange = (key: string) => { setActiveTabKey(key); }; const getLogs = () => { setLoading(true); request .get(`${config.apiPrefix}crons/${cron.id}/logs`) .then(({ code, data }) => { if (code === 200) { setLogs(data); } }) .finally(() => setLoading(false)); }; const getScript = () => { const result = getCommandScript(cron.command); if (Array.isArray(result)) { setValidTabs(validTabs); const [s, p] = result; setScriptInfo({ parent: p, filename: s }); request .get(`${config.apiPrefix}scripts/detail?file=${s}&path=${p || ''}`) .then(({ code, data }) => { if (code === 200) { setValue(data); } }); } else { setValidTabs([validTabs[0]]); setActiveTabKey('log'); } }; const saveFile = () => { Modal.confirm({ title: `确认保存`, content: ( <> {intl.get('确认保存文件')} {' '} {scriptInfo.filename} {intl.get(',保存后不可恢复')} ), onOk() { const content = editorRef.current ? editorRef.current.getValue().replace(/\r\n/g, '\n') : value; return new Promise((resolve, reject) => { request .put(`${config.apiPrefix}scripts`, { filename: scriptInfo.filename, path: scriptInfo.parent || '', content, }) .then(({ code, data }) => { if (code === 200) { setValue(content); message.success(`保存成功`); } resolve(null); }) .catch((e) => reject(e)); }); }, }); }; const runCron = () => { Modal.confirm({ title: intl.get('确认运行'), content: ( <> {intl.get('确认运行定时任务')}{' '} {currentCron.name} {' '} {intl.get('吗')} ), onOk() { request .put(`${config.apiPrefix}crons/run`, [currentCron.id]) .then(({ code, data }) => { if (code === 200) { setCurrentCron({ ...currentCron, status: CrontabStatus.running }); setTimeout(() => { getLogs(); }, 1000); } }); }, }); }; const stopCron = () => { Modal.confirm({ title: intl.get('确认停止'), content: ( <> {intl.get('确认停止定时任务')}{' '} {currentCron.name} {' '} {intl.get('吗')} ), onOk() { request .put(`${config.apiPrefix}crons/stop`, [currentCron.id]) .then(({ code, data }) => { if (code === 200) { setCurrentCron({ ...currentCron, status: CrontabStatus.idle }); } }); }, }); }; const enabledOrDisabledCron = () => { Modal.confirm({ title: `确认${ currentCron.isDisabled === 1 ? intl.get('启用') : intl.get('禁用') }`, content: ( <> {intl.get('确认')} {currentCron.isDisabled === 1 ? intl.get('启用') : intl.get('禁用')} {intl.get('定时任务')}{' '} {currentCron.name} {' '} {intl.get('吗')} ), onOk() { request .put( `${config.apiPrefix}crons/${ currentCron.isDisabled === 1 ? 'enable' : 'disable' }`, [currentCron.id], ) .then(({ code, data }) => { if (code === 200) { setCurrentCron({ ...currentCron, isDisabled: currentCron.isDisabled === 1 ? 0 : 1, }); } }); }, }); }; const pinOrUnPinCron = () => { Modal.confirm({ title: `确认${ currentCron.isPinned === 1 ? intl.get('取消置顶') : intl.get('置顶') }`, content: ( <> {intl.get('确认')} {currentCron.isPinned === 1 ? intl.get('取消置顶') : intl.get('置顶')} {intl.get('定时任务')}{' '} {currentCron.name} {' '} {intl.get('吗')} ), onOk() { request .put( `${config.apiPrefix}crons/${ currentCron.isPinned === 1 ? 'unpin' : 'pin' }`, [currentCron.id], ) .then(({ code, data }) => { if (code === 200) { setCurrentCron({ ...currentCron, isPinned: currentCron.isPinned === 1 ? 0 : 1, }); } }); }, }); }; const fullscreen = () => { const editorElement = editorRef.current._domElement as HTMLElement; editorElement.parentElement?.requestFullscreen(); }; useEffect(() => { if (cron && cron.id) { setCurrentCron(cron); getLogs(); getScript(); } }, [cron]); return (
{currentCron.name} {currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && ( )} {currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && currentCron.labels?.map((label: string, i: number) => ( {label} ))}
} centered open={true} forceRender footer={false} onCancel={() => handleCancel()} wrapClassName="crontab-detail" width={!isPhone ? '80vw' : ''} >
{intl.get('任务')}
{currentCron.command}
{intl.get('状态')}
{(!currentCron.isDisabled || currentCron.status !== CrontabStatus.idle) && ( <> {currentCron.status === CrontabStatus.idle && ( } color="default"> {intl.get('空闲中')} )} {currentCron.status === CrontabStatus.running && ( } color="processing" > {intl.get('运行中')} )} {currentCron.status === CrontabStatus.queued && ( } color="default"> {intl.get('队列中')} )} )} {currentCron.isDisabled === 1 && currentCron.status === CrontabStatus.idle && ( } color="error"> {intl.get('已禁用')} )}
{intl.get('定时')}
{currentCron.schedule}
{currentCron.extra_schedules?.map((x) => (
{x.schedule}
))}
{intl.get('最后运行时间')}
{currentCron.last_execution_time ? dayjs(currentCron.last_execution_time * 1000).format( 'YYYY-MM-DD HH:mm:ss', ) : '-'}
{intl.get('最后运行时长')}
{currentCron.last_running_time ? diffTime(currentCron.last_running_time) : '-'}
{intl.get('下次运行时间')}
{currentCron.nextRunTime && dayjs(currentCron.nextRunTime).format('YYYY-MM-DD HH:mm:ss')}
{ onTabChange(key); }} tabBarExtraContent={ activeTabKey === 'script' && ( <>
{isLogModalVisible && ( { setIsLogModalVisible(false); }} cron={cron} data={log} logUrl={logUrl} /> )}
); }; export default CronDetailModal;