全新定时任务管理

This commit is contained in:
whyour
2021-04-03 16:39:10 +08:00
parent 32be5a6591
commit 8a45599919
9 changed files with 861 additions and 37 deletions
+306 -32
View File
@@ -1,39 +1,311 @@
import React, { PureComponent, Fragment, useState, useEffect } from 'react';
import { Button, notification, Modal } from 'antd';
import {
Button,
notification,
Modal,
Table,
Tag,
Space,
Tooltip,
Dropdown,
Menu,
Typography,
} from 'antd';
import {
ClockCircleOutlined,
SyncOutlined,
CloseCircleOutlined,
FileTextOutlined,
EllipsisOutlined,
PlayCircleOutlined,
CheckCircleOutlined,
EditOutlined,
StopOutlined,
DeleteOutlined,
} from '@ant-design/icons';
import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
import { Controlled as CodeMirror } from 'react-codemirror2';
import { request } from '@/utils/http';
import CronModal from './modal';
const { Text } = Typography;
enum CrontabStatus {
'idle',
'running',
'disabled',
}
const Crontab = () => {
const columns = [
{
title: '任务名',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
render: (text: string, record: any) => (
<span>{record.name || record._id}</span>
),
},
{
title: '任务',
dataIndex: 'command',
key: 'command',
align: 'center' as const,
},
{
title: '任务定时',
dataIndex: 'schedule',
key: 'schedule',
align: 'center' as const,
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
align: 'center' as const,
render: (text: string, record: any) => (
<>
{record.status === CrontabStatus.idle && (
<Tag icon={<ClockCircleOutlined />} color="default">
</Tag>
)}
{record.status === CrontabStatus.running && (
<Tag icon={<SyncOutlined spin />} color="processing">
</Tag>
)}
{record.status === CrontabStatus.disabled && (
<Tag icon={<CloseCircleOutlined />} color="error">
</Tag>
)}
</>
),
},
{
title: '操作',
key: 'action',
align: 'center' as const,
render: (text: string, record: any, index: number) => (
<Space size="middle">
<Tooltip title="运行">
<a
onClick={() => {
runCron(record);
}}
>
<PlayCircleOutlined />
</a>
</Tooltip>
<Tooltip title="日志">
<a
onClick={() => {
logCron(record);
}}
>
<FileTextOutlined />
</a>
</Tooltip>
<MoreBtn key="more" record={record} />
</Space>
),
},
];
const [width, setWdith] = useState('100%');
const [marginLeft, setMarginLeft] = useState(0);
const [marginTop, setMarginTop] = useState(-72);
const [value, setValue] = useState('');
const [value, setValue] = useState();
const [loading, setLoading] = useState(true);
const [isModalVisible, setIsModalVisible] = useState(false);
const [editedCron, setEditedCron] = useState();
const getConfig = () => {
const getCrons = () => {
setLoading(true);
request
.get(`${config.apiPrefix}config/crontab`)
.then((data) => {
.get(`${config.apiPrefix}crons`)
.then((data: any) => {
setValue(data.data);
})
.finally(() => setLoading(false));
};
const updateConfig = () => {
const addCron = () => {
setIsModalVisible(true);
};
const editCron = (record: any) => {
setEditedCron(record);
setIsModalVisible(true);
};
const delCron = (record: any) => {
Modal.confirm({
title: '确认删除',
content: (
<>
<Text type="warning">{record.name}</Text>
</>
),
onOk() {
request
.delete(`${config.apiPrefix}crons`, {
data: { _id: record._id },
})
.then((data: any) => {
if (data.code === 200) {
notification.success({
message: '删除成功',
});
getCrons();
} else {
notification.error({
message: data,
});
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const runCron = (record: any) => {
Modal.confirm({
title: '确认运行',
content: (
<>
<Text type="warning">{record.name}</Text>
</>
),
onOk() {
request
.get(`${config.apiPrefix}crons/${record._id}/run`)
.then((data: any) => {
getCrons();
});
},
onCancel() {
console.log('Cancel');
},
});
};
const enabledOrDisabledCron = (record: any) => {
Modal.confirm({
title: '确认禁用',
content: (
<>
<Text type="warning">{record.name}</Text>
</>
),
onOk() {
request
.get(
`${config.apiPrefix}crons/${record._id}/${
record.status === CrontabStatus.disabled ? 'enable' : 'disable'
}`,
{
data: { _id: record._id },
},
)
.then((data: any) => {
if (data.code === 200) {
notification.success({
message: '禁用成功',
});
getCrons();
} else {
notification.error({
message: data,
});
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const logCron = (record: any) => {
request
.post(`${config.apiPrefix}save`, {
data: { content: value, name: 'crontab.list' },
})
.then((data) => {
notification.success({
message: data.msg,
.get(`${config.apiPrefix}crons/${record._id}/log`)
.then((data: any) => {
Modal.info({
width: 650,
title: `${record.name || record._id}`,
content: (
<pre style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word' }}>
{data.data || '暂无日志'}
</pre>
),
onOk() {},
});
});
};
const MoreBtn: React.FC<{
record: any;
}> = ({ record }) => (
<Dropdown
arrow
trigger={['click', 'hover']}
overlay={
<Menu onClick={({ key }) => action(key, record)}>
<Menu.Item key="edit" icon={<EditOutlined />}>
</Menu.Item>
<Menu.Item
key="enableordisable"
icon={
record.status === CrontabStatus.disabled ? (
<CheckCircleOutlined />
) : (
<StopOutlined />
)
}
>
{record.status === CrontabStatus.disabled ? '启用' : '禁用'}
</Menu.Item>
<Menu.Item key="delete" icon={<DeleteOutlined />}>
</Menu.Item>
</Menu>
}
>
<a>
<EllipsisOutlined />
</a>
</Dropdown>
);
const action = (key: string | number, record: any) => {
switch (key) {
case 'edit':
editCron(record);
break;
case 'enableordisable':
enabledOrDisabledCron(record);
break;
case 'delete':
delCron(record);
break;
default:
break;
}
};
const handleCancel = (needUpdate?: boolean) => {
setIsModalVisible(false);
if (needUpdate) {
getCrons();
}
};
useEffect(() => {
if (document.body.clientWidth < 768) {
setWdith('auto');
@@ -44,16 +316,17 @@ const Crontab = () => {
setMarginLeft(0);
setMarginTop(-72);
}
getConfig();
getCrons();
}, []);
return (
<PageContainer
className="code-mirror-wrapper"
title="crontab.list"
title="定时任务"
loading={loading}
extra={[
<Button key="1" type="primary" onClick={updateConfig}>
<Button key="2" type="primary" onClick={() => addCron()}>
</Button>,
]}
header={{
@@ -68,23 +341,24 @@ const Crontab = () => {
marginLeft,
},
}}
style={{
height: '100vh',
}}
>
<CodeMirror
value={value}
options={{
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true,
matchBrackets: true,
mode: 'shell',
<Table
columns={columns}
pagination={{
hideOnSinglePage: true,
showSizeChanger: true,
defaultPageSize: 20,
}}
onBeforeChange={(editor, data, value) => {
setValue(value);
}}
onChange={(editor, data, value) => {}}
dataSource={value}
rowKey="pin"
size="middle"
bordered
scroll={{ x: 768 }}
/>
<CronModal
visible={isModalVisible}
handleCancel={handleCancel}
cron={editedCron}
/>
</PageContainer>
);
+92
View File
@@ -0,0 +1,92 @@
import React, { useEffect, useState } from 'react';
import { Modal, notification, Input, Form } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import cronParse from 'cron-parser';
const CronModal = ({
cron,
handleCancel,
visible,
}: {
cron?: any;
visible: boolean;
handleCancel: (needUpdate?: boolean) => void;
}) => {
const [form] = Form.useForm();
const handleOk = async (values: any) => {
const method = cron ? 'put' : 'post';
const payload = { ...values };
if (cron) {
payload._id = cron._id;
}
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
data: payload,
});
if (code === 200) {
notification.success({
message: cron ? '更新Cron成功' : '添加Cron成功',
});
} else {
notification.error({
message: data,
});
}
handleCancel(true);
};
useEffect(() => {
if (cron) {
form.setFieldsValue(cron);
}
}, [cron]);
return (
<Modal
title={cron ? '编辑定时' : '新建定时'}
visible={visible}
onOk={() => {
form
.validateFields()
.then((values) => {
handleOk(values);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
onCancel={() => handleCancel()}
destroyOnClose
>
<Form form={form} layout="vertical" name="form_in_modal" preserve={false}>
<Form.Item name="name" label="名称">
<Input />
</Form.Item>
<Form.Item name="command" label="任务" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item
name="schedule"
label="时间"
rules={[
{ required: true },
{
validator: (rule, value) => {
if (cronParse.parseString(value).expressions.length > 0) {
return Promise.resolve();
} else {
return Promise.reject('Cron表达式格式有误');
}
},
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
);
};
export default CronModal;