任务视图增加状态筛选

This commit is contained in:
whyour 2022-09-03 01:56:47 +08:00
parent 2f05c95422
commit 9a3181bc44
5 changed files with 248 additions and 54 deletions

View File

@ -100,6 +100,40 @@ export default (app: Router) => {
},
);
route.put(
'/views/disable',
celebrate({
body: Joi.array().items(Joi.number().required()),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.put(
'/views/enable',
celebrate({
body: Joi.array().items(Joi.number().required()),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {

View File

@ -14,7 +14,7 @@ import dayjs from 'dayjs';
@Service()
export default class CronService {
constructor(@Inject('logger') private logger: winston.Logger) { }
constructor(@Inject('logger') private logger: winston.Logger) {}
private isSixCron(cron: Crontab) {
const { schedule } = cron;
@ -122,9 +122,31 @@ export default class CronService {
case 'Reg':
operate = Op.like;
break;
case 'Reg':
case 'NotReg':
operate = Op.notLike;
break;
case 'In':
query[Op.or] = [
{
[property]: value,
},
property === 'status' && value.includes(2)
? { isDisabled: 1 }
: {},
];
break;
case 'Nin':
query[Op.and] = [
{
[property]: {
[Op.notIn]: value,
},
},
property === 'status' && value.includes(2)
? { isDisabled: { [Op.ne]: 1 } }
: {},
];
break;
default:
break;
}
@ -134,13 +156,13 @@ export default class CronService {
{ [operate]: `%${value}%` },
{ [operate]: `%${encodeURIComponent(value)}%` },
],
}
};
}
}
}
}
private formatSearchText(query: any, searchText: string | undefined) {
private formatSearchText(query: any, searchText: string | undefined) {
if (searchText) {
const textArray = searchText.split(':');
switch (textArray[0]) {
@ -216,7 +238,7 @@ export default class CronService {
['status', 'ASC'],
['createdAt', 'DESC'],
];
this.formatViewQuery(query, viewQuery);
this.formatSearchText(query, searchText);
this.formatViewSort(order, viewQuery);
@ -274,9 +296,9 @@ export default class CronService {
const endTime = dayjs();
const diffTimeStr = doc.last_execution_time
? `,耗时 ${endTime.diff(
dayjs(doc.last_execution_time * 1000),
'second',
)}`
dayjs(doc.last_execution_time * 1000),
'second',
)}`
: '';
if (logFileExist) {
const str = err ? `\n${err}` : '';

View File

@ -369,6 +369,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const [isViewManageModalVisible, setIsViewManageModalVisible] =
useState(false);
const [cronViews, setCronViews] = useState<any[]>([]);
const [enabledCronViews, setEnabledCronViews] = useState<any[]>([]);
const goToScriptManager = (record: any) => {
const cmd = record.command.split(' ') as string[];
@ -960,7 +961,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
viewAction(key);
}}
items={[
...[...cronViews].slice(2).map((x) => ({
...[...enabledCronViews].slice(2).map((x) => ({
label: x.name,
key: x.id,
icon: <UnorderedListOutlined />,
@ -988,6 +989,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
.get(`${config.apiPrefix}crons/views`)
.then((data: any) => {
setCronViews(data.data);
setEnabledCronViews(data.data.filter((x) => !x.isDisabled));
})
.finally(() => {
setLoading(false);
@ -995,9 +997,9 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
};
const tabClick = (key: string) => {
const view = cronViews.find(x => x.id == key);
const view = enabledCronViews.find((x) => x.id == key);
setViewConf(view ? view : null);
}
};
return (
<PageContainer
@ -1028,7 +1030,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
tabPosition="top"
className="crontab-view"
tabBarExtraContent={
<Dropdown overlay={menu} trigger={['click']} overlayStyle={{minWidth: 200}}>
<Dropdown
overlay={menu}
trigger={['click']}
overlayStyle={{ minWidth: 200 }}
>
<div className="view-more">
<Space>
@ -1043,7 +1049,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
<Tabs.TabPane tab="全部任务" key="all">
{panelContent}
</Tabs.TabPane>
{[...cronViews].slice(0, 2).map((x) => (
{[...enabledCronViews].slice(0, 2).map((x) => (
<Tabs.TabPane tab={x.name} key={x.id}>
{panelContent}
</Tabs.TabPane>
@ -1083,9 +1089,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
/>
<ViewCreateModal
visible={isCreateViewModalVisible}
handleCancel={() => {
getCronViews();
handleCancel={(data) => {
setIsCreateViewModalVisible(false);
getCronViews();
if (data && data.id === viewConf.id) {
setViewConf({ ...viewConf, ...data });
}
}}
/>
<ViewManageModal
@ -1094,8 +1103,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
handleCancel={() => {
setIsViewManageModalVisible(false);
}}
cronViewChange={() => {
cronViewChange={(data) => {
getCronViews();
if (data && data.id === viewConf.id) {
setViewConf({ ...viewConf, ...data });
}
}}
/>
</PageContainer>

View File

@ -17,19 +17,31 @@ const PROPERTIES = [
{ name: '命令', value: 'command' },
{ name: '名称', value: 'name' },
{ name: '定时规则', value: 'schedule' },
{ name: '状态', value: 'status' },
];
const OPERATIONS = [
{ name: '包含', value: 'Reg' },
{ name: '不包含', value: 'NotReg' },
// { name: '属于', value: 'In' },
// { name: '不属于', value: 'Nin' },
{ name: '属于', value: 'In' },
{ name: '不属于', value: 'Nin' },
// { name: '等于', value: 'Eq' },
// { name: '不等于', value: 'Ne' },
// { name: '为空', value: 'IsNull' },
// { name: '不为空', value: 'NotNull' },
];
const SORTTYPES = [
{ name: '顺序', value: 'ASC' },
{ name: '倒序', value: 'DESC' },
];
const STATUS = [
{ name: '运行中', value: 0 },
{ name: '空闲中', value: 1 },
{ name: '已禁用', value: 2 },
];
const ViewCreateModal = ({
view,
handleCancel,
@ -37,10 +49,11 @@ const ViewCreateModal = ({
}: {
view?: any;
visible: boolean;
handleCancel: (param?: string) => void;
handleCancel: (param?: any) => void;
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [operationMap, setOperationMap] = useState<any>();
const handleOk = async (values: any) => {
setLoading(true);
@ -49,7 +62,7 @@ const ViewCreateModal = ({
const { code, data } = await request[method](
`${config.apiPrefix}crons/views`,
{
data: values,
data: view ? { ...values, id: view.id } : values,
},
);
if (code !== 200) {
@ -63,14 +76,19 @@ const ViewCreateModal = ({
};
useEffect(() => {
form.resetFields();
form.setFieldsValue({
filters: [{ property: 'command', operation: 'Reg' }],
});
form.setFieldsValue(view || {});
if (!view) {
form.resetFields();
}
}, [view, visible]);
const operationElement = (
<Select style={{ width: 80 }}>
<Select
style={{ width: 100 }}
onChange={() => {
setOperationMap({});
}}
>
{OPERATIONS.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
@ -79,9 +97,31 @@ const ViewCreateModal = ({
</Select>
);
const propertyElement = (
<Select style={{ width: 100 }}>
{PROPERTIES.map((x) => (
const propertyElement = (props: any) => {
return (
<Select style={{ width: 120 }}>
{props.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
};
const typeElement = (
<Select style={{ width: 120 }}>
{SORTTYPES.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
const statusElement = (
<Select mode="multiple" allowClear placeholder="请选择状态">
{STATUS.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
@ -110,7 +150,7 @@ const ViewCreateModal = ({
onCancel={() => handleCancel()}
confirmLoading={loading}
>
<Form form={form} layout="vertical" name="env_modal" initialValues={view}>
<Form form={form} layout="vertical" name="env_modal">
<Form.Item
name="name"
label="视图名称"
@ -134,7 +174,7 @@ const ViewCreateModal = ({
name={[name, 'property']}
rules={[{ required: true }]}
>
{propertyElement}
{propertyElement(PROPERTIES)}
</Form.Item>
<Form.Item
{...restField}
@ -148,7 +188,13 @@ const ViewCreateModal = ({
name={[name, 'value']}
rules={[{ required: true, message: '请输入内容' }]}
>
<Input />
{['In', 'Nin'].includes(
form.getFieldValue(['filters', index, 'operation']),
) ? (
statusElement
) : (
<Input placeholder="请输入内容" />
)}
</Form.Item>
{index !== 0 && (
<MinusCircleOutlined onClick={() => remove(name)} />
@ -159,9 +205,7 @@ const ViewCreateModal = ({
<Form.Item>
<a
href="#"
onClick={() =>
add({ property: 'command', operation: 'Reg' })
}
onClick={() => add({ property: 'command', operation: 'Reg' })}
>
<PlusOutlined />
@ -170,6 +214,49 @@ const ViewCreateModal = ({
</>
)}
</Form.List>
<Form.List name="sorts">
{(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(PROPERTIES)}
</Form.Item>
<Form.Item
{...restField}
name={[name, 'type']}
rules={[{ required: true }]}
>
{typeElement}
</Form.Item>
{index !== 0 && (
<MinusCircleOutlined onClick={() => remove(name)} />
)}
</Space>
</Form.Item>
))}
<Form.Item>
<a
href="#"
onClick={() => add({ property: 'command', operation: 'ASC' })}
>
<PlusOutlined />
</a>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Modal>
);

View File

@ -1,5 +1,14 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Modal, message, Space, Table, Tag, Typography, Button } from 'antd';
import {
Modal,
message,
Space,
Table,
Tag,
Typography,
Button,
Switch,
} from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
@ -59,12 +68,12 @@ const ViewManageModal = ({
cronViews,
handleCancel,
visible,
cronViewChange
cronViewChange,
}: {
cronViews: any[];
visible: boolean;
handleCancel: () => void;
cronViewChange: () => void;
cronViewChange: (data?: any) => void;
}) => {
const columns: any = [
{
@ -72,22 +81,26 @@ const ViewManageModal = ({
dataIndex: 'name',
key: 'name',
align: 'center' as const,
width: 70,
},
// {
// title: '显示',
// key: 'isDisabled',
// dataIndex: 'isDisabled',
// align: 'center' as const,
// width: 70,
// render: (text: string, record: any, index: number) => {
// return <Space size="middle" style={{ cursor: 'text' }}></Space>;
// },
// },
{
title: '显示',
key: 'isDisabled',
dataIndex: 'isDisabled',
align: 'center' as const,
width: 100,
render: (text: string, record: any, index: number) => {
return (
<Switch
checked={!record.isDisabled}
onChange={(checked) => onShowChange(checked, record, index)}
/>
);
},
},
{
title: '操作',
key: 'action',
width: 120,
width: 140,
align: 'center' as const,
render: (text: string, record: any, index: number) => {
return (
@ -106,10 +119,11 @@ const ViewManageModal = ({
const [list, setList] = useState<any[]>([]);
const [isCreateViewModalVisible, setIsCreateViewModalVisible] =
useState<boolean>(false);
const [editedView, setEditedView] = useState<any>(null);
const editView = (record: any, index: number) => {
// setEditedEnv(record);
// setIsModalVisible(true);
setEditedView(record);
setIsCreateViewModalVisible(true);
};
const deleteView = (record: any, index: number) => {
@ -142,6 +156,24 @@ const ViewManageModal = ({
});
};
const onShowChange = (checked: boolean, record: any, index: number) => {
console.log(checked);
request
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
data: [record.id],
})
.then((data: any) => {
if (data.code === 200) {
const _list = [...list];
_list.splice(index, 1, { ...list[index], isDisabled: !checked });
setList(_list);
cronViewChange();
} else {
message.error(data);
}
});
};
const components = {
body: {
row: DragableBodyRow,
@ -188,7 +220,13 @@ const ViewManageModal = ({
footer={false}
maskClosable={false}
>
<Space style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 10 }}>
<Space
style={{
display: 'flex',
justifyContent: 'flex-end',
marginBottom: 10,
}}
>
<Button
key="2"
type="primary"
@ -216,9 +254,10 @@ const ViewManageModal = ({
/>
</DndProvider>
<ViewCreateModal
view={editedView}
visible={isCreateViewModalVisible}
handleCancel={() => {
cronViewChange();
handleCancel={(data) => {
cronViewChange(data);
setIsCreateViewModalVisible(false);
}}
/>