From 8a45599919949fb8ca5bf4a2d545c0503fd77747 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 3 Apr 2021 16:39:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A8=E6=96=B0=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + back/api/cron.ts | 167 +++++++++++++++++- back/data/cron.ts | 27 +++ back/services/cron.ts | 183 +++++++++++++++++++ package.json | 4 +- shell/api.sh | 74 ++++++++ shell/git_pull.sh | 12 +- src/pages/crontab/index.tsx | 338 ++++++++++++++++++++++++++++++++---- src/pages/crontab/modal.tsx | 92 ++++++++++ 9 files changed, 861 insertions(+), 37 deletions(-) create mode 100644 back/data/cron.ts create mode 100644 back/services/cron.ts create mode 100755 shell/api.sh create mode 100644 src/pages/crontab/modal.tsx diff --git a/.gitignore b/.gitignore index db2d9a82..5cb8f362 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ /src/.umi-test /.env.local .env +.history /config /log diff --git a/back/api/cron.ts b/back/api/cron.ts index ddcdf9f1..4e44e6c4 100644 --- a/back/api/cron.ts +++ b/back/api/cron.ts @@ -3,15 +3,180 @@ import { Container } from 'typedi'; import { Logger } from 'winston'; import * as fs from 'fs'; import config from '../config'; +import CronService from '../services/cron'; +import { celebrate, Joi } from 'celebrate'; const route = Router(); export default (app: Router) => { app.use('/', route); route.get( - '/cron', + '/crons', async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { + const cookieService = Container.get(CronService); + const data = await cookieService.crontabs(); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.post( + '/crons', + celebrate({ + body: Joi.object({ + command: Joi.string().required(), + schedule: Joi.string().required(), + name: Joi.string(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.create(req.body); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.get( + '/crons/:id/run', + celebrate({ + params: Joi.object({ + id: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.run(req.params.id); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.get( + '/crons/:id/disable', + celebrate({ + params: Joi.object({ + id: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.disabled(req.params.id); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.get( + '/crons/:id/enable', + celebrate({ + params: Joi.object({ + id: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.enabled(req.params.id); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.get( + '/crons/:id/log', + celebrate({ + params: Joi.object({ + id: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.log(req.params.id); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.put( + '/crons', + celebrate({ + body: Joi.object({ + command: Joi.string().required(), + schedule: Joi.string().required(), + name: Joi.string(), + _id: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.update(req.body); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.delete( + '/crons/:id', + celebrate({ + params: Joi.object({ + id: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.remove(req.params.id); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.get( + '/crons/import', + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const cookieService = Container.get(CronService); + const data = await cookieService.import_crontab(); + return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); return next(e); diff --git a/back/data/cron.ts b/back/data/cron.ts new file mode 100644 index 00000000..ea2daa72 --- /dev/null +++ b/back/data/cron.ts @@ -0,0 +1,27 @@ +export class Crontab { + name?: string; + command: string; + schedule: string; + timestamp?: string; + created?: number; + saved?: boolean; + _id?: string; + status?: CrontabStatus; + + constructor(options: Crontab) { + this.name = options.name; + this.command = options.command; + this.schedule = options.schedule; + this.saved = options.saved; + this._id = options._id; + this.created = options.created; + this.status = options.status || CrontabStatus.idle; + this.timestamp = new Date().toString(); + } +} + +export enum CrontabStatus { + 'idle', + 'running', + 'disabled', +} diff --git a/back/services/cron.ts b/back/services/cron.ts new file mode 100644 index 00000000..10b96ed6 --- /dev/null +++ b/back/services/cron.ts @@ -0,0 +1,183 @@ +import { Service, Inject } from 'typedi'; +import winston from 'winston'; +import DataStore from 'nedb'; +import config from '../config'; +import { Crontab, CrontabStatus } from '../data/cron'; +import { exec, execSync, spawn } from 'child_process'; +import fs from 'fs'; +import cron_parser from 'cron-parser'; +import { getFileContentByName } from '../config/util'; + +@Service() +export default class CronService { + private cronDb = new DataStore({ filename: config.cronDbFile }); + + constructor(@Inject('logger') private logger: winston.Logger) { + this.cronDb.loadDatabase((err) => { + if (err) throw err; + }); + } + + public async create(payload: Crontab): Promise { + const tab = new Crontab(payload); + tab.created = new Date().valueOf(); + tab.saved = false; + this.cronDb.insert(tab); + await this.set_crontab(); + } + + public async update(payload: Crontab): Promise { + const { _id, ...other } = payload; + const doc = await this.get(_id); + const tab = new Crontab({ ...doc, ...other }); + tab.saved = false; + this.cronDb.update({ _id }, tab, { returnUpdatedDocs: true }); + await this.set_crontab(); + } + + public async status(_id: string, stopped: boolean) { + this.cronDb.update({ _id }, { $set: { stopped, saved: false } }); + } + + public async remove(_id: string) { + this.cronDb.remove({ _id }, {}); + await this.set_crontab(); + } + + public async crontabs(): Promise { + return new Promise((resolve) => { + this.cronDb + .find({}) + .sort({ created: -1 }) + .exec((err, docs) => { + resolve(docs); + }); + }); + } + + public async get(_id: string): Promise { + return new Promise((resolve) => { + this.cronDb.find({ _id }).exec((err, docs) => { + resolve(docs[0]); + }); + }); + } + + public async run(_id: string) { + this.cronDb.find({ _id }).exec((err, docs) => { + let res = docs[0]; + + this.logger.silly('Running job'); + this.logger.silly('ID: ' + _id); + this.logger.silly('Original command: ' + res.command); + + let logFile = `${config.manualLogPath}${res._id}.log`; + fs.writeFileSync(logFile, `${new Date().toString()}\n\n`); + + const cmd = spawn(res.command, { shell: true }); + + this.cronDb.update({ _id }, { $set: { status: CrontabStatus.running } }); + + cmd.stdout.on('data', (data) => { + this.logger.silly(`stdout: ${data}`); + fs.appendFileSync(logFile, data); + }); + + cmd.stderr.on('data', (data) => { + this.logger.error(`stderr: ${data}`); + fs.appendFileSync(logFile, data); + }); + + cmd.on('close', (code) => { + this.logger.silly(`child process exited with code ${code}`); + this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } }); + }); + + cmd.on('error', (err) => { + this.logger.silly(err); + fs.appendFileSync(logFile, err.stack); + }); + }); + } + + public async disabled(_id: string) { + this.cronDb.update({ _id }, { $set: { status: CrontabStatus.disabled } }); + await this.set_crontab(); + } + + public async enabled(_id: string) { + this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } }); + } + + public async log(_id: string) { + let logFile = `${config.manualLogPath}${_id}.log`; + return getFileContentByName(logFile); + } + + private make_command(tab: Crontab) { + const crontab_job_string = `ID=${tab._id} ${tab.command}`; + return crontab_job_string; + } + + private async set_crontab() { + const tabs = await this.crontabs(); + var crontab_string = ''; + tabs.forEach((tab) => { + if (tab.status !== CrontabStatus.disabled) { + crontab_string += tab.schedule; + crontab_string += ' '; + crontab_string += this.make_command(tab); + crontab_string += '\n'; + } + }); + + this.logger.silly(crontab_string); + fs.writeFileSync(config.crontabFile, crontab_string); + + execSync(`crontab ${config.crontabFile}`); + this.cronDb.update({}, { $set: { saved: true } }, { multi: true }); + } + + private reload_db() { + this.cronDb.loadDatabase(); + } + + public import_crontab() { + exec('crontab -l', (error, stdout, stderr) => { + var lines = stdout.split('\n'); + var namePrefix = new Date().getTime(); + + lines.reverse().forEach((line, index) => { + line = line.replace(/\t+/g, ' '); + var regex = /^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/; + var command = line.replace(regex, '').trim(); + var schedule = line.replace(command, '').trim(); + + var is_valid = false; + try { + is_valid = cron_parser.parseString(line).expressions.length > 0; + } catch (e) {} + if (command && schedule && is_valid) { + var name = namePrefix + '_' + index; + + this.cronDb.findOne({ command, schedule }, (err, doc) => { + if (err) { + throw err; + } + if (!doc) { + this.create({ name, command, schedule }); + } else { + doc.command = command; + doc.schedule = schedule; + this.update(doc); + } + }); + } + }); + }); + } + + public autosave_crontab() { + return this.set_crontab(); + } +} diff --git a/package.json b/package.json index 2b09bf36..29ae1c8a 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,14 @@ "celebrate": "^13.0.3", "codemirror": "^5.59.4", "cors": "^2.8.5", + "cron-parser": "^3.3.0", "darkreader": "^4.9.27", "dotenv": "^8.2.0", "express": "^4.17.1", "express-jwt": "^6.0.0", "got": "^11.8.2", "jsonwebtoken": "^8.5.1", - "mongoose": "^5.10.6", + "nedb": "^1.8.0", "node-fetch": "^2.6.1", "qrcode.react": "^1.0.1", "react-codemirror2": "^7.2.1", @@ -51,6 +52,7 @@ "@types/express": "^4.17.8", "@types/express-jwt": "^6.0.1", "@types/jsonwebtoken": "^8.5.0", + "@types/nedb": "^1.8.11", "@types/node": "^14.11.2", "@types/node-fetch": "^2.5.8", "@types/qrcode.react": "^1.0.1", diff --git a/shell/api.sh b/shell/api.sh new file mode 100755 index 00000000..25cdb988 --- /dev/null +++ b/shell/api.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +get_token() { + echo $AuthConf + local authInfo=$(cat $AuthConf) + token=$(get_json_value "$authInfo" "token") +} + +get_json_value() { + local json=$1 + local key=$2 + + if [[ -z "$3" ]]; then + local num=1 + else + local num=$3 + fi + + local value=$(echo "${json}" | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/'${key}'\042/){print $(i+1)}}}' | tr -d '"' | sed -n ${num}p) + + echo ${value} +} + +add_cron_api() { + local currentTimeStamp=$(date +%s%3) + if [ $# -eq 1 ]; then + local schedule=$(echo "$1" | awk -F ": " '{print $1}') + local command=$(echo "$1" | awk -F ": " '{print $2}') + local name=$(echo "$1" | awk -F ": " '{print $3}') + else + local schedule=$1 + local command=$2 + local name=$3 + fi + + local api=$(curl "http://localhost:5678/api/crons?t=$currentTimeStamp" \ + -H "Accept: application/json" \ + -H "Authorization: Bearer $token" \ + -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Origin: http://localhost:5700" \ + -H "Referer: http://localhost:5700/crontab" \ + -H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \ + --data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\"}" \ + --compressed) + echo $api + code=$(get_json_value $api "code") + if [[ $code == 200 ]]; then + echo -e "$name 添加成功" + else + echo -e "$name 添加失败" + fi +} + +del_cron_api() { + local id=$1 + local currentTimeStamp=$(date +%s%3) + local api=$(curl "http://localhost:5678/api/crons/$id?t=$currentTimeStamp" \ + -X 'DELETE' \ + -H "Accept: application/json" \ + -H "Authorization: Bearer $token" \ + -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Origin: http://localhost:5700" \ + -H "Referer: http://localhost:5700/crontab" \ + -H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7") + echo $api + code=$(get_json_value $api "code") + if [[ $code == 200 ]]; then + echo -e "$name 删除成功" + else + echo -e "$name 删除失败" + fi +} diff --git a/shell/git_pull.sh b/shell/git_pull.sh index 4cfc3541..7dae014c 100755 --- a/shell/git_pull.sh +++ b/shell/git_pull.sh @@ -16,6 +16,7 @@ ScriptsDir=$ShellDir/scripts ConfigDir=$ShellDir/config FileConf=$ConfigDir/config.sh CookieConf=$ConfigDir/cookie.sh +AuthConf=$ConfigDir/auth.json ExtraShell=$ConfigDir/extra.sh FileConfSample=$ShellDir/sample/config.sh.sample ListCronSample=$ShellDir/sample/crontab.list.sample @@ -44,6 +45,8 @@ Import_Conf() { fi [ -f $CookieConf ] && . $CookieConf [ -f $FileConf ] && . $FileConf + + . $ShellDir/shell/api.sh } # 更新shell @@ -198,7 +201,7 @@ Git_Pull_Scripts_Next() { Diff_Cron() { cat $ListCronRemote | grep -E "node.+j[drx]_\w+\.js" | perl -pe "s|.+(j[drx]_\w+)\.js.+|\1|" | sort -u >$ListRemoteTask - cat $ListCronCurrent | grep -E "$ShellJs j[drx]_\w+" | perl -pe "s|.*$ShellJs (j[drx]_\w+)\.*|\1|" | sort -u >$ListCurrentTask + cat $ListCronCurrent | grep -E "$ShellJs j[drx]_\w+" | perl -pe "s|.*ID=(.*)$ShellJs (j[drx]_\w+)\.*|\1|" | sort -u >$ListCurrentTask if [ -s $ListCurrentTask ]; then grep -vwf $ListCurrentTask $ListRemoteTask >$ListJsAdd else @@ -218,7 +221,7 @@ Del_Cron() { echo JsDrop=$(cat $ListJsDrop) for Cron in $JsDrop; do - perl -i -ne "{print unless / $Cron( |$)/}" $ListCronCurrent + del_cron_api "$Cron" done crontab $ListCronCurrent echo -e "成功删除失效的脚本与定时任务\n" @@ -234,7 +237,8 @@ Add_Cron() { if [[ $Cron == jd_bean_sign ]]; then echo "4 0,9 * * * $ShellJs $Cron" >> $ListCronCurrent else - cat $ListCronRemote | grep -E "\/$Cron\." | perl -pe "s|(^.+)node */scripts/(j[drx]_\w+)\.js.+|\1$ShellJs \2|" >> $ListCronCurrent + param=$(cat $ListCronRemote | grep -E "\/$Cron\." | perl -pe "s|(^.+)node */scripts/(j[drx]_\w+)\.js.+|\1\: $ShellJs \2: \2|") + add_cron_api "$param" fi done @@ -274,6 +278,8 @@ echo -e "--------------------------------------------------------------\n" Import_Conf Random_Pull_Cron +get_token + # 更新shell [ -f $ShellDir/package.json ] && PanelDependOld=$(cat $ShellDir/package.json) Git_Pull_Shell diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx index 5c606943..6583df91 100644 --- a/src/pages/crontab/index.tsx +++ b/src/pages/crontab/index.tsx @@ -1,39 +1,311 @@ import React, { PureComponent, Fragment, useState, useEffect } from 'react'; -import { Button, notification, Modal } from 'antd'; +import { + Button, + notification, + Modal, + Table, + Tag, + Space, + Tooltip, + Dropdown, + Menu, + Typography, +} from 'antd'; +import { + ClockCircleOutlined, + SyncOutlined, + CloseCircleOutlined, + FileTextOutlined, + EllipsisOutlined, + PlayCircleOutlined, + CheckCircleOutlined, + EditOutlined, + StopOutlined, + DeleteOutlined, +} from '@ant-design/icons'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; -import { Controlled as CodeMirror } from 'react-codemirror2'; import { request } from '@/utils/http'; +import CronModal from './modal'; + +const { Text } = Typography; + +enum CrontabStatus { + 'idle', + 'running', + 'disabled', +} const Crontab = () => { + const columns = [ + { + title: '任务名', + dataIndex: 'name', + key: 'name', + align: 'center' as const, + render: (text: string, record: any) => ( + {record.name || record._id} + ), + }, + { + title: '任务', + dataIndex: 'command', + key: 'command', + align: 'center' as const, + }, + { + title: '任务定时', + dataIndex: 'schedule', + key: 'schedule', + align: 'center' as const, + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + align: 'center' as const, + render: (text: string, record: any) => ( + <> + {record.status === CrontabStatus.idle && ( + } color="default"> + 空闲中 + + )} + {record.status === CrontabStatus.running && ( + } color="processing"> + 运行中 + + )} + {record.status === CrontabStatus.disabled && ( + } color="error"> + 已禁用 + + )} + + ), + }, + { + title: '操作', + key: 'action', + align: 'center' as const, + render: (text: string, record: any, index: number) => ( + + + { + runCron(record); + }} + > + + + + + { + logCron(record); + }} + > + + + + + + ), + }, + ]; + const [width, setWdith] = useState('100%'); const [marginLeft, setMarginLeft] = useState(0); const [marginTop, setMarginTop] = useState(-72); - const [value, setValue] = useState(''); + const [value, setValue] = useState(); const [loading, setLoading] = useState(true); + const [isModalVisible, setIsModalVisible] = useState(false); + const [editedCron, setEditedCron] = useState(); - const getConfig = () => { + const getCrons = () => { setLoading(true); request - .get(`${config.apiPrefix}config/crontab`) - .then((data) => { + .get(`${config.apiPrefix}crons`) + .then((data: any) => { setValue(data.data); }) .finally(() => setLoading(false)); }; - const updateConfig = () => { + const addCron = () => { + setIsModalVisible(true); + }; + + const editCron = (record: any) => { + setEditedCron(record); + setIsModalVisible(true); + }; + + const delCron = (record: any) => { + Modal.confirm({ + title: '确认删除', + content: ( + <> + 确认删除定时任务 {record.name} 吗 + + ), + onOk() { + request + .delete(`${config.apiPrefix}crons`, { + data: { _id: record._id }, + }) + .then((data: any) => { + if (data.code === 200) { + notification.success({ + message: '删除成功', + }); + getCrons(); + } else { + notification.error({ + message: data, + }); + } + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + const runCron = (record: any) => { + Modal.confirm({ + title: '确认运行', + content: ( + <> + 确认运行定时任务 {record.name} 吗 + + ), + onOk() { + request + .get(`${config.apiPrefix}crons/${record._id}/run`) + .then((data: any) => { + getCrons(); + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + const enabledOrDisabledCron = (record: any) => { + Modal.confirm({ + title: '确认禁用', + content: ( + <> + 确认禁用定时任务 {record.name} 吗 + + ), + onOk() { + request + .get( + `${config.apiPrefix}crons/${record._id}/${ + record.status === CrontabStatus.disabled ? 'enable' : 'disable' + }`, + { + data: { _id: record._id }, + }, + ) + .then((data: any) => { + if (data.code === 200) { + notification.success({ + message: '禁用成功', + }); + getCrons(); + } else { + notification.error({ + message: data, + }); + } + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + const logCron = (record: any) => { request - .post(`${config.apiPrefix}save`, { - data: { content: value, name: 'crontab.list' }, - }) - .then((data) => { - notification.success({ - message: data.msg, + .get(`${config.apiPrefix}crons/${record._id}/log`) + .then((data: any) => { + Modal.info({ + width: 650, + title: `${record.name || record._id}`, + content: ( +
+              {data.data || '暂无日志'}
+            
+ ), + onOk() {}, }); }); }; + const MoreBtn: React.FC<{ + record: any; + }> = ({ record }) => ( + action(key, record)}> + }> + 编辑 + + + ) : ( + + ) + } + > + {record.status === CrontabStatus.disabled ? '启用' : '禁用'} + + }> + 删除 + + + } + > + + + + + ); + + const action = (key: string | number, record: any) => { + switch (key) { + case 'edit': + editCron(record); + break; + case 'enableordisable': + enabledOrDisabledCron(record); + break; + case 'delete': + delCron(record); + break; + default: + break; + } + }; + + const handleCancel = (needUpdate?: boolean) => { + setIsModalVisible(false); + if (needUpdate) { + getCrons(); + } + }; + useEffect(() => { if (document.body.clientWidth < 768) { setWdith('auto'); @@ -44,16 +316,17 @@ const Crontab = () => { setMarginLeft(0); setMarginTop(-72); } - getConfig(); + getCrons(); }, []); return ( - 保存 + , ]} header={{ @@ -68,23 +341,24 @@ const Crontab = () => { marginLeft, }, }} - style={{ - height: '100vh', - }} > - { - setValue(value); - }} - onChange={(editor, data, value) => {}} + dataSource={value} + rowKey="pin" + size="middle" + bordered + scroll={{ x: 768 }} + /> + ); diff --git a/src/pages/crontab/modal.tsx b/src/pages/crontab/modal.tsx new file mode 100644 index 00000000..3cda9730 --- /dev/null +++ b/src/pages/crontab/modal.tsx @@ -0,0 +1,92 @@ +import React, { useEffect, useState } from 'react'; +import { Modal, notification, Input, Form } from 'antd'; +import { request } from '@/utils/http'; +import config from '@/utils/config'; +import cronParse from 'cron-parser'; + +const CronModal = ({ + cron, + handleCancel, + visible, +}: { + cron?: any; + visible: boolean; + handleCancel: (needUpdate?: boolean) => void; +}) => { + const [form] = Form.useForm(); + + const handleOk = async (values: any) => { + const method = cron ? 'put' : 'post'; + const payload = { ...values }; + if (cron) { + payload._id = cron._id; + } + const { code, data } = await request[method](`${config.apiPrefix}crons`, { + data: payload, + }); + if (code === 200) { + notification.success({ + message: cron ? '更新Cron成功' : '添加Cron成功', + }); + } else { + notification.error({ + message: data, + }); + } + handleCancel(true); + }; + + useEffect(() => { + if (cron) { + form.setFieldsValue(cron); + } + }, [cron]); + + return ( + { + form + .validateFields() + .then((values) => { + handleOk(values); + }) + .catch((info) => { + console.log('Validate Failed:', info); + }); + }} + onCancel={() => handleCancel()} + destroyOnClose + > +
+ + + + + + + { + if (cronParse.parseString(value).expressions.length > 0) { + return Promise.resolve(); + } else { + return Promise.reject('Cron表达式格式有误'); + } + }, + }, + ]} + > + + +
+
+ ); +}; + +export default CronModal;