mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 22:36:06 +08:00
修复定时任务排序
This commit is contained in:
parent
918d68d140
commit
3b3d45da29
|
@ -28,7 +28,7 @@ export default (app: Router) => {
|
|||
celebrate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
sorts: Joi.string().required(),
|
||||
sorts: Joi.string().optional(),
|
||||
filters: Joi.string().optional(),
|
||||
}),
|
||||
}),
|
||||
|
@ -107,6 +107,8 @@ export default (app: Router) => {
|
|||
searchText: Joi.string().required().allow(''),
|
||||
page: Joi.string().required(),
|
||||
size: Joi.string().required(),
|
||||
sortField: Joi.string().optional(),
|
||||
sortType: Joi.string().optional(),
|
||||
t: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -117,10 +117,14 @@ export default class CronService {
|
|||
searchText: string;
|
||||
page: string;
|
||||
size: string;
|
||||
sortField: string;
|
||||
sortType: string;
|
||||
}): Promise<{ data: Crontab[]; total: number }> {
|
||||
const searchText = params?.searchText;
|
||||
const page = Number(params?.page || '0');
|
||||
const size = Number(params?.size || '0');
|
||||
const sortField = params?.sortField || '';
|
||||
const sortType = params?.sortType || '';
|
||||
|
||||
let query = {};
|
||||
if (searchText) {
|
||||
|
@ -166,14 +170,18 @@ export default class CronService {
|
|||
break;
|
||||
}
|
||||
}
|
||||
let order = [
|
||||
['isPinned', 'DESC'],
|
||||
['isDisabled', 'ASC'],
|
||||
['status', 'ASC'],
|
||||
['createdAt', 'DESC'],
|
||||
];
|
||||
if (sortType && sortField) {
|
||||
order.unshift([sortField, sortType]);
|
||||
}
|
||||
let condition: any = {
|
||||
where: query,
|
||||
order: [
|
||||
['isPinned', 'DESC'],
|
||||
['isDisabled', 'ASC'],
|
||||
['status', 'ASC'],
|
||||
['createdAt', 'DESC'],
|
||||
],
|
||||
order: order,
|
||||
};
|
||||
if (page && size) {
|
||||
condition.offset = (page - 1) * size;
|
||||
|
@ -298,11 +306,13 @@ export default class CronService {
|
|||
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
|
||||
cmdStr = `task ${cmdStr}`;
|
||||
}
|
||||
if (cmdStr.endsWith('.js')
|
||||
|| cmdStr.endsWith('.py')
|
||||
|| cmdStr.endsWith('.pyc')
|
||||
|| cmdStr.endsWith('.sh')
|
||||
|| cmdStr.endsWith('.ts')) {
|
||||
if (
|
||||
cmdStr.endsWith('.js') ||
|
||||
cmdStr.endsWith('.py') ||
|
||||
cmdStr.endsWith('.pyc') ||
|
||||
cmdStr.endsWith('.sh') ||
|
||||
cmdStr.endsWith('.ts')
|
||||
) {
|
||||
cmdStr = `${cmdStr} now`;
|
||||
}
|
||||
|
||||
|
|
|
@ -126,3 +126,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-create-modal-filters {
|
||||
display: flex;
|
||||
|
||||
.ant-space-item:nth-child(3) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
Input,
|
||||
Popover,
|
||||
Tabs,
|
||||
TablePaginationConfig,
|
||||
} from 'antd';
|
||||
import {
|
||||
ClockCircleOutlined,
|
||||
|
@ -46,6 +47,8 @@ import { history } from 'umi';
|
|||
import './index.less';
|
||||
import ViewCreateModal from './viewCreateModal';
|
||||
import ViewManageModal from './viewManageModal';
|
||||
import pagination from 'antd/lib/pagination';
|
||||
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { Search } = Input;
|
||||
|
@ -130,7 +133,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
),
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a?.name?.localeCompare(b?.name),
|
||||
multiple: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -155,7 +157,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
},
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.command.localeCompare(b.command),
|
||||
multiple: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -166,7 +167,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
align: 'center' as const,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.schedule.localeCompare(b.schedule),
|
||||
multiple: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -353,9 +353,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||
const [logCron, setLogCron] = useState<any>();
|
||||
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
||||
const [pageConf, setPageConf] = useState<{ page: number; size: number }>(
|
||||
{} as any,
|
||||
);
|
||||
const [pageConf, setPageConf] = useState<{
|
||||
page: number;
|
||||
size: number;
|
||||
sorter: any;
|
||||
}>({} as any);
|
||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
||||
const [detailCron, setDetailCron] = useState<any>();
|
||||
|
@ -365,6 +367,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
useState(false);
|
||||
const [isViewManageModalVisible, setIsViewManageModalVisible] =
|
||||
useState(false);
|
||||
const [cronViews, setCronViews] = useState<any[]>([]);
|
||||
|
||||
const goToScriptManager = (record: any) => {
|
||||
const cmd = record.command.split(' ') as string[];
|
||||
|
@ -386,10 +389,15 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
|
||||
const getCrons = () => {
|
||||
setLoading(true);
|
||||
const { page, size, sorter } = pageConf;
|
||||
let url = `${config.apiPrefix}crons?searchText=${searchText}&page=${page}&size=${size}`;
|
||||
if (sorter && sorter.field) {
|
||||
url += `&sortField=${sorter.field}&sortType=${
|
||||
sorter.order === 'ascend' ? 'ASC' : 'DESC'
|
||||
}`;
|
||||
}
|
||||
request
|
||||
.get(
|
||||
`${config.apiPrefix}crons?searchText=${searchText}&page=${pageConf.page}&size=${pageConf.size}`,
|
||||
)
|
||||
.get(url)
|
||||
.then((_data: any) => {
|
||||
const { data, total } = _data.data;
|
||||
setValue(
|
||||
|
@ -791,8 +799,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
});
|
||||
};
|
||||
|
||||
const onPageChange = (page: number, pageSize: number | undefined) => {
|
||||
setPageConf({ page, size: pageSize as number });
|
||||
const onPageChange = (
|
||||
pagination: TablePaginationConfig,
|
||||
filters: Record<string, FilterValue | null>,
|
||||
sorter: SorterResult<any> | SorterResult<any>[],
|
||||
) => {
|
||||
const { current, pageSize } = pagination;
|
||||
setPageConf({ page: current as number, size: pageSize as number, sorter });
|
||||
localStorage.setItem('pageSize', String(pageSize));
|
||||
};
|
||||
|
||||
|
@ -821,10 +834,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
setPageConf({
|
||||
page: 1,
|
||||
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
||||
sorter: {},
|
||||
});
|
||||
setTimeout(() => {
|
||||
setTableScrollHeight(getTableScroll());
|
||||
});
|
||||
getCronViews();
|
||||
}, []);
|
||||
|
||||
const panelContent = (
|
||||
|
@ -889,7 +904,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
columns={columns}
|
||||
pagination={{
|
||||
current: pageConf.page,
|
||||
onChange: onPageChange,
|
||||
pageSize: pageConf.size,
|
||||
showSizeChanger: true,
|
||||
simple: isPhone,
|
||||
|
@ -915,6 +929,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
loading={loading}
|
||||
rowSelection={rowSelection}
|
||||
rowClassName={getRowClassName}
|
||||
onChange={onPageChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -940,11 +955,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
viewAction(key);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
label: 'Clicking me will not close the menu.',
|
||||
key: '1',
|
||||
...[...cronViews].slice(5).map((x) => ({
|
||||
label: x.name,
|
||||
key: x.id,
|
||||
icon: <UnorderedListOutlined />,
|
||||
},
|
||||
})),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
|
@ -962,6 +977,18 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
/>
|
||||
);
|
||||
|
||||
const getCronViews = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/views`)
|
||||
.then((data: any) => {
|
||||
setCronViews(data.data);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper crontab-wrapper ql-container-wrapper-has-tab"
|
||||
|
@ -1005,6 +1032,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||
<Tabs.TabPane tab="全部任务" key="all">
|
||||
{panelContent}
|
||||
</Tabs.TabPane>
|
||||
{[...cronViews].slice(0, 5).map((x) => (
|
||||
<Tabs.TabPane tab={x.name} key={x.id}>
|
||||
{panelContent}
|
||||
</Tabs.TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
<CronLogModal
|
||||
visible={isLogModalVisible}
|
||||
|
|
|
@ -1,7 +1,30 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, message, Input, Form, Statistic, Button } from 'antd';
|
||||
import {
|
||||
Modal,
|
||||
message,
|
||||
Input,
|
||||
Form,
|
||||
Statistic,
|
||||
Button,
|
||||
Space,
|
||||
Select,
|
||||
} from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
const PROPERTIES = [
|
||||
{ name: '命令', value: 'command' },
|
||||
{ name: '名称', value: 'name' },
|
||||
{ name: '定时规则', value: 'schedule' },
|
||||
];
|
||||
|
||||
const OPERATIONS = [
|
||||
{ name: '包含', value: 'contains' },
|
||||
{ name: '不包含', value: 'noncontains' },
|
||||
// { name: '属于', value: 'belong' },
|
||||
// { name: '不属于', value: 'nonbelong' },
|
||||
];
|
||||
|
||||
const ViewCreateModal = ({
|
||||
view,
|
||||
|
@ -17,19 +40,12 @@ const ViewCreateModal = ({
|
|||
|
||||
const handleOk = async (values: any) => {
|
||||
setLoading(true);
|
||||
const { value, name } = values;
|
||||
const method = view ? 'put' : 'post';
|
||||
let payload;
|
||||
if (!view) {
|
||||
payload = [{ value, name }];
|
||||
} else {
|
||||
payload = { ...values, id: view.id };
|
||||
}
|
||||
try {
|
||||
const { code, data } = await request[method](
|
||||
`${config.apiPrefix}crons/views`,
|
||||
{
|
||||
data: payload,
|
||||
data: values,
|
||||
},
|
||||
);
|
||||
if (code !== 200) {
|
||||
|
@ -44,13 +60,37 @@ const ViewCreateModal = ({
|
|||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
form.setFieldsValue({
|
||||
filters: [{ property: 'command', operation: 'contains' }],
|
||||
});
|
||||
}, [view, visible]);
|
||||
|
||||
const operationElement = (
|
||||
<Select style={{ width: 80 }}>
|
||||
{OPERATIONS.map((x) => (
|
||||
<Select.Option key={x.name} value={x.value}>
|
||||
{x.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
|
||||
const propertyElement = (
|
||||
<Select style={{ width: 100 }}>
|
||||
{PROPERTIES.map((x) => (
|
||||
<Select.Option key={x.name} value={x.value}>
|
||||
{x.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={view ? '编辑视图' : '新建视图'}
|
||||
visible={visible}
|
||||
forceRender
|
||||
width={580}
|
||||
centered
|
||||
maskClosable={false}
|
||||
onOk={() => {
|
||||
|
@ -74,6 +114,58 @@ const ViewCreateModal = ({
|
|||
>
|
||||
<Input placeholder="请输入视图名称" />
|
||||
</Form.Item>
|
||||
<Form.List name="filters">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name, ...restField }, index) => (
|
||||
<Form.Item
|
||||
label={index === 0 ? '筛选条件' : ''}
|
||||
required={true}
|
||||
key={key}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Space className="view-create-modal-filters" align="baseline">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'property']}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
{propertyElement}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'operation']}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
{operationElement}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'value']}
|
||||
rules={[{ required: true, message: '请输入内容' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
{index !== 0 && (
|
||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||
)}
|
||||
</Space>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<a
|
||||
href=""
|
||||
onClick={() =>
|
||||
add({ property: 'command', operation: 'contains' })
|
||||
}
|
||||
>
|
||||
<PlusOutlined />
|
||||
新增筛选条件
|
||||
</a>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Modal, message, Space, Table, Tag, Typography } from 'antd';
|
||||
import { Modal, message, Space, Table, Tag, Typography, Button } from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
|
@ -7,6 +7,7 @@ import { PageLoading } from '@ant-design/pro-layout';
|
|||
import Paragraph from 'antd/lib/skeleton/Paragraph';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import ViewCreateModal from './viewCreateModal';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
@ -69,6 +70,7 @@ const ViewManageModal = ({
|
|||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center' as const,
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
title: '显示',
|
||||
|
@ -101,6 +103,8 @@ const ViewManageModal = ({
|
|||
];
|
||||
const [list, setList] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState<any>(true);
|
||||
const [isCreateViewModalVisible, setIsCreateViewModalVisible] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const editView = (record: any, index: number) => {
|
||||
// setEditedEnv(record);
|
||||
|
@ -190,11 +194,22 @@ const ViewManageModal = ({
|
|||
title="视图管理"
|
||||
visible={visible}
|
||||
centered
|
||||
width={620}
|
||||
onCancel={() => handleCancel()}
|
||||
className="view-manage-modal"
|
||||
forceRender
|
||||
footer={false}
|
||||
maskClosable={false}
|
||||
>
|
||||
<Space style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
key="2"
|
||||
type="primary"
|
||||
onClick={() => setIsCreateViewModalVisible(true)}
|
||||
>
|
||||
新建视图
|
||||
</Button>
|
||||
</Space>
|
||||
{loading ? (
|
||||
<PageLoading />
|
||||
) : (
|
||||
|
@ -216,6 +231,12 @@ const ViewManageModal = ({
|
|||
/>
|
||||
</DndProvider>
|
||||
)}
|
||||
<ViewCreateModal
|
||||
visible={isCreateViewModalVisible}
|
||||
handleCancel={() => {
|
||||
setIsCreateViewModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user