任务视图增加状态筛选

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) => { route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');
try { try {

View File

@ -14,7 +14,7 @@ import dayjs from 'dayjs';
@Service() @Service()
export default class CronService { export default class CronService {
constructor(@Inject('logger') private logger: winston.Logger) { } constructor(@Inject('logger') private logger: winston.Logger) {}
private isSixCron(cron: Crontab) { private isSixCron(cron: Crontab) {
const { schedule } = cron; const { schedule } = cron;
@ -122,9 +122,31 @@ export default class CronService {
case 'Reg': case 'Reg':
operate = Op.like; operate = Op.like;
break; break;
case 'Reg': case 'NotReg':
operate = Op.notLike; operate = Op.notLike;
break; 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: default:
break; break;
} }
@ -134,7 +156,7 @@ export default class CronService {
{ [operate]: `%${value}%` }, { [operate]: `%${value}%` },
{ [operate]: `%${encodeURIComponent(value)}%` }, { [operate]: `%${encodeURIComponent(value)}%` },
], ],
} };
} }
} }
} }
@ -274,9 +296,9 @@ export default class CronService {
const endTime = dayjs(); const endTime = dayjs();
const diffTimeStr = doc.last_execution_time const diffTimeStr = doc.last_execution_time
? `,耗时 ${endTime.diff( ? `,耗时 ${endTime.diff(
dayjs(doc.last_execution_time * 1000), dayjs(doc.last_execution_time * 1000),
'second', 'second',
)}` )}`
: ''; : '';
if (logFileExist) { if (logFileExist) {
const str = err ? `\n${err}` : ''; const str = err ? `\n${err}` : '';

View File

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

View File

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

View File

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