diff --git a/back/api/cron.ts b/back/api/cron.ts index ff2600a0..9890d0ce 100644 --- a/back/api/cron.ts +++ b/back/api/cron.ts @@ -28,6 +28,7 @@ export default (app: Router) => { command: Joi.string().required(), schedule: Joi.string().required(), name: Joi.string().optional(), + labels: Joi.array().optional(), }), }), async (req: Request, res: Response, next: NextFunction) => { @@ -83,6 +84,48 @@ export default (app: Router) => { }, ); + route.put( + '/removelabels', + celebrate({ + body: Joi.object({ + ids:Joi.array().items(Joi.string().required()), + labels:Joi.array().items(Joi.string().required()), + }) + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cronService = Container.get(CronService); + const data = await cronService.removeLabels(req.body.ids,req.body.labels); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.put( + '/addlabels', + celebrate({ + body: Joi.object({ + ids:Joi.array().items(Joi.string().required()), + labels:Joi.array().items(Joi.string().required()), + }) + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cronService = Container.get(CronService); + const data = await cronService.addLabels(req.body.ids,req.body.labels); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + route.put( '/disable', celebrate({ @@ -143,6 +186,7 @@ export default (app: Router) => { '', celebrate({ body: Joi.object({ + labels: Joi.array().optional(), command: Joi.string().optional(), schedule: Joi.string().optional(), name: Joi.string().optional(), diff --git a/back/data/cron.ts b/back/data/cron.ts index ea5e9170..fa722edc 100644 --- a/back/data/cron.ts +++ b/back/data/cron.ts @@ -12,6 +12,7 @@ export class Crontab { isDisabled?: 1 | 0; log_path?: string; isPinned?: 1 | 0; + labels: Array; constructor(options: Crontab) { this.name = options.name; @@ -29,6 +30,7 @@ export class Crontab { this.isDisabled = options.isDisabled || 0; this.log_path = options.log_path || ''; this.isPinned = options.isPinned || 0; + this.labels = options.labels || []; } } diff --git a/back/services/cron.ts b/back/services/cron.ts index f9d49f61..53ccefc0 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -156,25 +156,69 @@ export default class CronService { }); } + public async addLabels(ids: string[],labels: string[]){ + return new Promise((resolve: any) => { + this.cronDb.update( + { _id: { $in: ids } }, + { $addToSet: { labels: { $each: labels} } }, + { multi: true }, + async (err) => { + resolve(); + }, + ); + }); + } + + public async removeLabels(ids: string[],labels: string[]){ + return new Promise((resolve: any) => { + this.cronDb.update( + { _id: { $in: ids } }, + { $pull: { labels: { $in: labels} } }, + { multi: true }, + async (err) => { + resolve(); + }, + ); + }); + } + public async crontabs(searchText?: string): Promise { let query = {}; if (searchText) { - const encodeText = encodeURIComponent(searchText); - const reg = new RegExp(`${searchText}|${encodeText}`, 'i'); - - query = { - $or: [ - { - name: reg, - }, - { - command: reg, - }, - { - schedule: reg, - }, - ], - }; + const textArray = searchText.split(":"); + switch (textArray[0]) { + case "name": + query = {name:createRegexp(textArray[1])}; + break; + case "command": + query = {command:createRegexp(textArray[1])}; + break; + case "schedule": + query = {schedule:createRegexp(textArray[1])}; + break; + case "label": + query = {labels:createRegexp(textArray[1])}; + break; + default: + const reg = createRegexp(searchText); + query = { + $or: [ + { + name: reg, + }, + { + command: reg, + }, + { + schedule: reg, + }, + { + labels: reg, + }, + ], + }; + break; + } } return new Promise((resolve) => { this.cronDb @@ -184,6 +228,11 @@ export default class CronService { resolve(docs); }); }); + function createRegexp(text:string) :RegExp { + const encodeText = encodeURIComponent(text); + const reg = new RegExp(`${text}|${encodeText}`, 'i'); + return reg; + } } public async get(_id: string): Promise { diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx index 1ece0e65..0537f5b6 100644 --- a/src/pages/crontab/index.tsx +++ b/src/pages/crontab/index.tsx @@ -30,7 +30,7 @@ import { import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; import { request } from '@/utils/http'; -import CronModal from './modal'; +import CronModal,{CronLabelModal} from './modal'; import CronLogModal from './logModal'; import cron_parser from 'cron-parser'; import { diffTime } from '@/utils/date'; @@ -275,6 +275,26 @@ const Crontab = ({ headerStyle, isPhone }: any) => { ), }, + { + title: '标签', + key: 'labels', + dataIndex: 'labels', + align: 'center' as const, + width: 100, + render: (text: string, record: any) => ( + <> + {record.labels?.map((label: string) => ( + { onSearch(`label:${label}`) }}>{label} + ))} + + ), + }, { title: '操作', key: 'action', @@ -325,6 +345,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => { const [value, setValue] = useState([]); const [loading, setLoading] = useState(true); const [isModalVisible, setIsModalVisible] = useState(false); + const [isLabelModalVisible, setisLabelModalVisible] = useState(false); const [editedCron, setEditedCron] = useState(); const [searchText, setSearchText] = useState(''); const [isLogModalVisible, setIsLogModalVisible] = useState(false); @@ -853,6 +874,13 @@ const Crontab = ({ headerStyle, isPhone }: any) => { > 批量取消置顶 + 已选择 {selectedRowIds?.length}项 @@ -893,6 +921,16 @@ const Crontab = ({ headerStyle, isPhone }: any) => { handleCancel={handleCancel} cron={editedCron} /> + { + setisLabelModalVisible(false); + if (needUpdate) { + getCrons(); + } + }} + ids={selectedRowIds} + /> ); }; diff --git a/src/pages/crontab/modal.tsx b/src/pages/crontab/modal.tsx index 301db116..e96f7424 100644 --- a/src/pages/crontab/modal.tsx +++ b/src/pages/crontab/modal.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Modal, message, Input, Form } from 'antd'; +import { Modal, message, Input, Form, Button } from 'antd'; import { request } from '@/utils/http'; import config from '@/utils/config'; import cronParse from 'cron-parser'; @@ -48,6 +48,9 @@ const CronModal = ({ form .validateFields() .then((values) => { + if (typeof values.labels === "string") { + values.labels = values.labels.split(/,|,/); + } handleOk(values); }) .catch((info) => { @@ -66,6 +69,9 @@ const CronModal = ({ + + + ; + visible: boolean; + handleCancel: (needUpdate?: boolean) => void; +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + + const update = async (action: string) => { + form + .validateFields() + .then(async (values) => { + if (typeof values.labels === "string") { + values.labels = values.labels.split(/,|,/); + } + setLoading(true); + const payload = { ids, labels: values.labels }; + const { code, data } = await request.put(`${config.apiPrefix}crons/${action}`, { + data: payload, + }); + if (code === 200) { + message.success(action === 'addLabels' ? '添加Labels成功' : '删除Labels成功'); + } else { + message.error(data); + } + setLoading(false); + handleCancel(true); + }) + .catch((info) => { + console.log('Validate Failed:', info); + }); + } + + useEffect(() => { + form.resetFields(); + }, [ids, visible]); + + const buttons = [ + , + , + + ]; + + return ( + handleCancel(false)} + confirmLoading={loading} + > +
+ + + +
+
+ ); +}; + +export { CronModal as default, CronLabelModal }