mirror of
https://github.com/whyour/qinglong.git
synced 2026-07-17 09:34:31 +08:00
定时任务支持 @once 和 @boot 任务
This commit is contained in:
@@ -496,5 +496,8 @@
|
||||
"NPM 镜像源": "NPM Mirror Source",
|
||||
"PyPI 镜像源": "PyPI Mirror Source",
|
||||
"alpine linux 镜像源": "Alpine Linux Mirror Source",
|
||||
"如果恢复失败,可进入容器执行": "If recovery fails, you can enter the container and execute"
|
||||
"如果恢复失败,可进入容器执行": "If recovery fails, you can enter the container and execute",
|
||||
"常规定时": "Normal Timing",
|
||||
"手动运行": "Manual Run",
|
||||
"开机运行": "Boot Run"
|
||||
}
|
||||
|
||||
@@ -496,5 +496,9 @@
|
||||
"NPM 镜像源": "NPM 镜像源",
|
||||
"PyPI 镜像源": "PyPI 镜像源",
|
||||
"alpine linux 镜像源": "alpine linux 镜像源",
|
||||
"如果恢复失败,可进入容器执行": "如果恢复失败,可进入容器执行"
|
||||
"如果恢复失败,可进入容器执行": "如果恢复失败,可进入容器执行",
|
||||
"常规定时": "常规定时",
|
||||
"手动运行": "手动运行",
|
||||
"开机运行": "开机运行"
|
||||
}
|
||||
|
||||
@@ -263,7 +263,9 @@ const Crontab = () => {
|
||||
},
|
||||
},
|
||||
render: (text, record) => {
|
||||
return dayjs(record.nextRunTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
return record.nextRunTime
|
||||
? dayjs(record.nextRunTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
: '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -396,9 +398,13 @@ const Crontab = () => {
|
||||
|
||||
setValue(
|
||||
data.map((x) => {
|
||||
const specialSchedules = ['@once', '@boot'];
|
||||
const nextRunTime = specialSchedules.includes(x.schedule)
|
||||
? null
|
||||
: getCrontabsNextDate(x.schedule, x.extra_schedules);
|
||||
return {
|
||||
...x,
|
||||
nextRunTime: getCrontabsNextDate(x.schedule, x.extra_schedules),
|
||||
nextRunTime,
|
||||
subscription: subscriptionMap?.[x.sub_id],
|
||||
};
|
||||
}),
|
||||
|
||||
+112
-60
@@ -1,12 +1,30 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, message, Input, Form, Button, Space } from 'antd';
|
||||
import { Modal, message, Input, Form, Button, Space, Select } from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
import cronParse from 'cron-parser';
|
||||
import EditableTagGroup from '@/components/tag';
|
||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
enum ScheduleType {
|
||||
Normal = 'normal',
|
||||
Once = 'once',
|
||||
Boot = 'boot',
|
||||
}
|
||||
|
||||
const scheduleTypeMap = {
|
||||
[ScheduleType.Normal]: '',
|
||||
[ScheduleType.Once]: '@once',
|
||||
[ScheduleType.Boot]: '@boot',
|
||||
};
|
||||
|
||||
const getScheduleType = (schedule?: string): ScheduleType => {
|
||||
if (schedule?.startsWith('@once')) return ScheduleType.Once;
|
||||
if (schedule?.startsWith('@boot')) return ScheduleType.Boot;
|
||||
return ScheduleType.Normal;
|
||||
};
|
||||
|
||||
const CronModal = ({
|
||||
cron,
|
||||
handleCancel,
|
||||
@@ -18,15 +36,26 @@ const CronModal = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [scheduleType, setScheduleType] = useState<ScheduleType>(
|
||||
cron ? getScheduleType(cron.schedule) : ScheduleType.Normal,
|
||||
);
|
||||
|
||||
const handleOk = async (values: any) => {
|
||||
setLoading(true);
|
||||
const method = cron?.id ? 'put' : 'post';
|
||||
const payload = { ...values };
|
||||
if (cron?.id) {
|
||||
payload.id = cron.id;
|
||||
}
|
||||
try {
|
||||
const method = cron?.id ? 'put' : 'post';
|
||||
const payload = {
|
||||
...values,
|
||||
schedule:
|
||||
scheduleType !== ScheduleType.Normal
|
||||
? scheduleTypeMap[scheduleType]
|
||||
: values.schedule,
|
||||
};
|
||||
|
||||
if (cron?.id) {
|
||||
payload.id = cron.id;
|
||||
}
|
||||
|
||||
const { code, data } = await request[method](
|
||||
`${config.apiPrefix}crons`,
|
||||
payload,
|
||||
@@ -38,74 +67,53 @@ const CronModal = ({
|
||||
);
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
setScheduleType(getScheduleType(cron?.schedule));
|
||||
}, [cron, visible]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={cron?.id ? intl.get('编辑任务') : intl.get('创建任务')}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
handleOk(values);
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log('Validate Failed:', info);
|
||||
});
|
||||
}}
|
||||
onCancel={() => handleCancel()}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
name="form_in_modal"
|
||||
initialValues={cron}
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={intl.get('名称')}
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input placeholder={intl.get('请输入任务名称')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="command"
|
||||
label={intl.get('命令/脚本')}
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
autoSize={{ minRows: 1, maxRows: 5 }}
|
||||
placeholder={intl.get(
|
||||
'支持输入脚本路径/任意系统可执行命令/task 脚本路径',
|
||||
)}
|
||||
/>
|
||||
</Form.Item>
|
||||
const handleScheduleTypeChange = (type: ScheduleType) => {
|
||||
setScheduleType(type);
|
||||
form.setFieldValue('schedule', '');
|
||||
};
|
||||
|
||||
const renderScheduleOptions = () => (
|
||||
<Select defaultValue={scheduleType} value={scheduleType} onChange={handleScheduleTypeChange}>
|
||||
<Select.Option value={ScheduleType.Normal}>
|
||||
{intl.get('常规定时')}
|
||||
</Select.Option>
|
||||
<Select.Option value={ScheduleType.Once}>
|
||||
{intl.get('手动运行')}
|
||||
</Select.Option>
|
||||
<Select.Option value={ScheduleType.Boot}>
|
||||
{intl.get('开机运行')}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
);
|
||||
|
||||
const renderScheduleFields = () => {
|
||||
if (scheduleType !== ScheduleType.Normal) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="schedule"
|
||||
label={intl.get('定时规则')}
|
||||
rules={[
|
||||
{ required: true },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
validator: (_, value) => {
|
||||
if (!value || cronParse.parseExpression(value).hasNext()) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(intl.get('Cron表达式格式有误'));
|
||||
}
|
||||
return Promise.reject(intl.get('Cron表达式格式有误'));
|
||||
},
|
||||
},
|
||||
]}
|
||||
@@ -136,14 +144,58 @@ const CronModal = ({
|
||||
))}
|
||||
<Form.Item>
|
||||
<a onClick={() => add({ schedule: '' })}>
|
||||
<PlusOutlined />
|
||||
{intl.get('新增定时规则')}
|
||||
<PlusOutlined /> {intl.get('新增定时规则')}
|
||||
</a>
|
||||
</Form.Item>
|
||||
<Form.ErrorList errors={errors} />
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={cron?.id ? intl.get('编辑任务') : intl.get('创建任务')}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
onOk={() => form.validateFields().then(handleOk)}
|
||||
onCancel={() => handleCancel()}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
name="form_in_modal"
|
||||
initialValues={cron}
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={intl.get('名称')}
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input placeholder={intl.get('请输入任务名称')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="command"
|
||||
label={intl.get('命令/脚本')}
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
autoSize={{ minRows: 1, maxRows: 5 }}
|
||||
placeholder={intl.get(
|
||||
'支持输入脚本路径/任意系统可执行命令/task 脚本路径',
|
||||
)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={intl.get('定时类型')} required>
|
||||
{renderScheduleOptions()}
|
||||
</Form.Item>
|
||||
{renderScheduleFields()}
|
||||
<Form.Item name="labels" label={intl.get('标签')}>
|
||||
<EditableTagGroup />
|
||||
</Form.Item>
|
||||
@@ -155,7 +207,7 @@ const CronModal = ({
|
||||
)}
|
||||
rules={[
|
||||
{
|
||||
validator(rule, value) {
|
||||
validator(_, value) {
|
||||
if (
|
||||
value &&
|
||||
(value.includes(' task ') || value.startsWith('task '))
|
||||
@@ -183,7 +235,7 @@ const CronModal = ({
|
||||
)}
|
||||
rules={[
|
||||
{
|
||||
validator(rule, value) {
|
||||
validator(_, value) {
|
||||
if (
|
||||
value &&
|
||||
(value.includes(' task ') || value.startsWith('task '))
|
||||
|
||||
Reference in New Issue
Block a user