From 89ed8527d608de3b5d4998c979760830cf699efa Mon Sep 17 00:00:00 2001 From: kilo5hz <90814817+kilo5hz@users.noreply.github.com> Date: Fri, 7 Jan 2022 22:01:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=87=E7=AD=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20(#1026)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加标签功能 --- back/api/cron.ts | 46 +++++++++++++++++++- back/data/cron.ts | 14 ++++++ back/services/cron.ts | 81 ++++++++++++++++++++++++++--------- sample/config.sample.sh | 6 +-- src/pages/crontab/index.tsx | 68 ++++++++++++++++++++++------- src/pages/crontab/modal.tsx | 85 ++++++++++++++++++++++++++++++++++++- src/pages/script/index.tsx | 6 ++- 7 files changed, 262 insertions(+), 44 deletions(-) diff --git a/back/api/cron.ts b/back/api/cron.ts index 8babad4f..be7cfa4f 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.number().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.number().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,10 +186,11 @@ export default (app: Router) => { '/', celebrate({ body: Joi.object({ + labels: Joi.array().optional(), command: Joi.string().optional(), schedule: Joi.string().optional(), name: Joi.string().optional(), - id: Joi.string().required(), + id: Joi.number().required(), }), }), async (req: Request, res: Response, next: NextFunction) => { diff --git a/back/data/cron.ts b/back/data/cron.ts index 6b8678df..928e3dfd 100644 --- a/back/data/cron.ts +++ b/back/data/cron.ts @@ -14,6 +14,7 @@ export class Crontab { isDisabled?: 1 | 0; log_path?: string; isPinned?: 1 | 0; + labels: Array; last_running_time?: number; last_execution_time?: number; @@ -33,6 +34,7 @@ export class Crontab { this.isDisabled = options.isDisabled || 0; this.log_path = options.log_path || ''; this.isPinned = options.isPinned || 0; + this.labels = options.labels || ['']; this.last_running_time = options.last_running_time || 0; this.last_execution_time = options.last_execution_time || 0; } @@ -58,6 +60,18 @@ export const CrontabModel = sequelize.define('Crontab', { isDisabled: DataTypes.NUMBER, isPinned: DataTypes.NUMBER, log_path: DataTypes.STRING, + labels: { + type: DataTypes.STRING, + allowNull: false, + get() { + if (this.getDataValue('labels')) { + return this.getDataValue('labels').split(',') + } + }, + set(value) { + this.setDataValue('labels', value.join(',')); + }, + }, last_running_time: DataTypes.NUMBER, last_execution_time: DataTypes.NUMBER, }); diff --git a/back/services/cron.ts b/back/services/cron.ts index b1cc3263..6ca0d274 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -93,30 +93,61 @@ export default class CronService { await CrontabModel.update({ isPinned: 0 }, { where: { id: ids } }); } + public async addLabels(ids: string[],labels: string[]){ + const docs = await CrontabModel.findAll({ where: { id:ids }}); + for (const doc of docs) { + await CrontabModel.update({ + labels: Array.from(new Set(doc.labels.concat(labels))) + },{ where: {id:doc.id}}); + } + } + + public async removeLabels(ids: string[],labels: string[]){ + const docs = await CrontabModel.findAll({ where: { id:ids }}); + for (const doc of docs) { + await CrontabModel.update({ + labels: doc.labels.filter( label => !labels.includes(label) ) + },{ where: {id:doc.id}}); + } + } + public async crontabs(searchText?: string): Promise { let query = {}; if (searchText) { - const encodeText = encodeURIComponent(searchText); - const reg = { - [Op.or]: [ - { [Op.like]: `%${searchText}&` }, - { [Op.like]: `%${encodeText}%` }, - ], - }; - - query = { - [Op.or]: [ - { - name: reg, - }, - { - command: reg, - }, - { - schedule: reg, - }, - ], - }; + const textArray = searchText.split(":"); + switch (textArray[0]) { + case "name": + query = {name:{[Op.or]:createRegexp(textArray[1])}}; + break; + case "command": + query = {command:{[Op.or]:createRegexp(textArray[1])}}; + break; + case "schedule": + query = {schedule:{[Op.or]:createRegexp(textArray[1])}}; + break; + case "label": + query = {labels:{[Op.or]:createRegexp(textArray[1])}}; + break; + default: + const reg = createRegexp(searchText); + query = { + [Op.or]: [ + { + name: reg, + }, + { + command: reg, + }, + { + schedule: reg, + }, + { + labels: reg, + }, + ], + }; + break; + } } try { const result = await CrontabModel.findAll({ where: query }); @@ -124,6 +155,14 @@ export default class CronService { } catch (error) { throw error; } + function createRegexp(text:string) { + return { + [Op.or]: [ + { [Op.like]: `%${text}%` }, + { [Op.like]: `%${encodeURIComponent(text)}%` }, + ], + }; + } } public async get(id: number): Promise { diff --git a/sample/config.sample.sh b/sample/config.sample.sh index 8a463f0d..7d6f3131 100644 --- a/sample/config.sample.sh +++ b/sample/config.sample.sh @@ -119,8 +119,8 @@ export GOBOT_QQ="" ## gotify_url 填写gotify地址,如https://push.example.de:8080 ## gotify_token 填写gotify的消息应用token ## gotify_priority 填写推送消息优先级,默认为0 -export GOTIFY_URL=""; -export GOTIFY_TOKEN=""; -export GOTIFY_PRIORITY=0; +export GOTIFY_URL="" +export GOTIFY_TOKEN="" +export GOTIFY_PRIORITY=0 ## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可 diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx index 5c1ddb7e..3db26990 100644 --- a/src/pages/crontab/index.tsx +++ b/src/pages/crontab/index.tsx @@ -11,6 +11,7 @@ import { Menu, Typography, Input, + Popover, } from 'antd'; import { ClockCircleOutlined, @@ -30,7 +31,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'; @@ -77,20 +78,39 @@ const Crontab = ({ headerStyle, isPhone }: any) => { width: 150, align: 'center' as const, render: (text: string, record: any) => ( - { - goToScriptManager(record); - }} - > - {record.name || record.id}{' '} - {record.isPinned ? ( - - - - ) : ( - '' - )} - + <> + { + goToScriptManager(record); + }} + > + {record.name || record._id}{' '} + {record.isPinned ? ( + + + + ) : ( + '' + )} + + + {record.labels?.length > 0 && record.labels[0] !== '' ? + + {record.labels?.map((label: string, i: number) => ( + { onSearch(`label:${label}`) }}> + {label} + + ))} + + }> + {record.labels[0]} + + : ''} + + ), sorter: { compare: (a: any, b: any) => a.name.localeCompare(b.name), @@ -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 15d9d229..df0048b5 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 } diff --git a/src/pages/script/index.tsx b/src/pages/script/index.tsx index 6c6cf4e4..35a707f7 100644 --- a/src/pages/script/index.tsx +++ b/src/pages/script/index.tsx @@ -113,17 +113,19 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => { const initGetScript = () => { const { p, s } = history.location.query as any; if (s) { + const vkey = `${p}/${s}`; const obj = { node: { title: s, value: s, - key: p ? `${p}/${s}` : s, + key: p ? vkey : s, parent: p, }, }; setExpandedKeys([p]); - onTreeSelect([`${p}/${s}`], obj); + onTreeSelect([vkey], obj); } + history.push('/script'); }; const onSelect = (value: any, node: any) => {