From ef24fc83546bc8c2f9458e047b0f81ff7c2d4aae Mon Sep 17 00:00:00 2001 From: whyour Date: Wed, 7 Apr 2021 23:53:06 +0800 Subject: [PATCH 01/13] =?UTF-8?q?=E6=9A=82=E6=97=B6=E7=A7=BB=E9=99=A4nginx?= =?UTF-8?q?=20ipv6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/front.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/front.conf b/docker/front.conf index b670a9c3..837a64a7 100644 --- a/docker/front.conf +++ b/docker/front.conf @@ -4,7 +4,6 @@ upstream api { server { listen 5700; - listen [::]:5700; root /ql/dist; ssl_session_timeout 5m; From fb98bc44e4989e5a76b24f9c310afd3a04672c71 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 10 Apr 2021 00:21:51 +0800 Subject: [PATCH 02/13] =?UTF-8?q?ck=E7=AE=A1=E7=90=86=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=EF=BC=8C=E7=A6=81=E7=94=A8=EF=BC=8C=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E7=8A=B6=E6=80=81=E5=8F=98=E6=88=90=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/api/cookie.ts | 180 ++++++++++------ back/api/cron.ts | 40 ++-- back/config/index.ts | 2 + back/data/cookie.ts | 27 +++ back/services/cookie.ts | 371 +++++++++++++-------------------- back/services/cron.ts | 14 +- package.json | 2 + src/app.tsx | 2 +- src/layouts/defaultProps.tsx | 6 - src/pages/code/index.less | 0 src/pages/code/index.tsx | 77 ------- src/pages/cookie/index.less | 7 + src/pages/cookie/index.tsx | 355 +++++++++++++++++++++---------- src/pages/cookie/modal.tsx | 26 +-- src/pages/crontab/index.tsx | 20 +- src/pages/crontab/logModal.tsx | 10 +- src/utils/http.ts | 4 +- 17 files changed, 621 insertions(+), 522 deletions(-) create mode 100644 back/data/cookie.ts delete mode 100644 src/pages/code/index.less delete mode 100644 src/pages/code/index.tsx diff --git a/back/api/cookie.ts b/back/api/cookie.ts index cf502294..f23a6b1b 100644 --- a/back/api/cookie.ts +++ b/back/api/cookie.ts @@ -2,53 +2,18 @@ import { Router, Request, Response, NextFunction } from 'express'; import { Container } from 'typedi'; import CookieService from '../services/cookie'; import { Logger } from 'winston'; +import { celebrate, Joi } from 'celebrate'; const route = Router(); export default (app: Router) => { app.use('/', route); - route.get( - '/qrcode', - async (req: Request, res: Response, next: NextFunction) => { - const logger: Logger = Container.get('logger'); - try { - if (req) { - const cookieService = Container.get(CookieService); - const { qrurl } = await cookieService.getQrUrl(); - return res.send({ code: 200, qrcode: qrurl }); - } else { - return res.send({ code: 1, msg: 'loginFaild' }); - } - } catch (e) { - logger.error('🔥 error: %o', e); - return next(e); - } - }, - ); - route.get( '/cookies', async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const cookieService = Container.get(CookieService); - const data = await cookieService.getCookies(); - return res.send({ code: 200, data }); - } catch (e) { - logger.error('🔥 error: %o', e); - return next(e); - } - }, - ); - - route.get( - '/cookie', - async (req: Request, res: Response, next: NextFunction) => { - const logger: Logger = Container.get('logger'); - try { - const cookieService = Container.get(CookieService); - const data = await cookieService.addQrCookie( - req.query.cookie as string, - ); + const data = await cookieService.cookies(); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -58,17 +23,16 @@ export default (app: Router) => { ); route.post( - '/cookie', + '/cookies', + celebrate({ + body: Joi.array().items(Joi.string().required()).min(1), + }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const cookieService = Container.get(CookieService); - const data = await cookieService.addCookie(req.body.cookies); - if (data) { - return res.send({ code: 400, data }); - } else { - return res.send({ code: 200, data: '新建成功' }); - } + const data = await cookieService.create(req.body); + return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); return next(e); @@ -77,17 +41,19 @@ export default (app: Router) => { ); route.put( - '/cookie', + '/cookies', + celebrate({ + body: Joi.object({ + value: Joi.string().required(), + _id: Joi.string().required(), + }), + }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const cookieService = Container.get(CookieService); - const data = await cookieService.updateCookie(req.body); - if (data) { - return res.send({ code: 400, data }); - } else { - return res.send({ code: 200, data: '新建成功' }); - } + const data = await cookieService.update(req.body); + return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); return next(e); @@ -96,19 +62,18 @@ export default (app: Router) => { ); route.delete( - '/cookie', + '/cookies/: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(CookieService); - const data = await cookieService.deleteCookie( - req.body.cookie as string, - ); - if (data) { - return res.send({ code: 400, data }); - } else { - return res.send({ code: 200, data: '新建成功' }); - } + const data = await cookieService.remove(req.params.id); + return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); return next(e); @@ -116,13 +81,102 @@ export default (app: Router) => { }, ); - route.post( - '/cookie/refresh', + route.put( + '/cookies/:id/move', + celebrate({ + params: Joi.object({ + id: Joi.string().required(), + }), + body: Joi.object({ + fromIndex: Joi.number().required(), + toIndex: Joi.number().required(), + }), + }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const cookieService = Container.get(CookieService); - const data = await cookieService.refreshCookie(req.body); + const data = await cookieService.move(req.params.id, req.body); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.get( + '/cookies/:id/refresh', + 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(CookieService); + const data = await cookieService.refreshCookie(req.params.id); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.get( + '/cookies/: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(CookieService); + 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( + '/cookies/: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(CookieService); + 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( + '/cookies/: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(CookieService); + const data = await cookieService.get(req.params.id); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); diff --git a/back/api/cron.ts b/back/api/cron.ts index 2c7bee03..2f6811ef 100644 --- a/back/api/cron.ts +++ b/back/api/cron.ts @@ -14,8 +14,8 @@ export default (app: Router) => { async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { - const cookieService = Container.get(CronService); - const data = await cookieService.crontabs( + const cronService = Container.get(CronService); + const data = await cronService.crontabs( req.query.searchValue as string, ); return res.send({ code: 200, data }); @@ -38,8 +38,8 @@ export default (app: Router) => { 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); + const cronService = Container.get(CronService); + const data = await cronService.create(req.body); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -58,8 +58,8 @@ export default (app: Router) => { 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); + const cronService = Container.get(CronService); + const data = await cronService.run(req.params.id); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -78,8 +78,8 @@ export default (app: Router) => { 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); + const cronService = Container.get(CronService); + const data = await cronService.disabled(req.params.id); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -98,8 +98,8 @@ export default (app: Router) => { 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); + const cronService = Container.get(CronService); + const data = await cronService.enabled(req.params.id); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -118,8 +118,8 @@ export default (app: Router) => { 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); + const cronService = Container.get(CronService); + const data = await cronService.log(req.params.id); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -141,8 +141,8 @@ export default (app: Router) => { 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); + const cronService = Container.get(CronService); + const data = await cronService.update(req.body); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -161,8 +161,8 @@ export default (app: Router) => { 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); + const cronService = Container.get(CronService); + const data = await cronService.remove(req.params.id); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -176,8 +176,8 @@ export default (app: Router) => { 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(); + const cronService = Container.get(CronService); + const data = await cronService.import_crontab(); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -196,8 +196,8 @@ export default (app: Router) => { async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { - const cookieService = Container.get(CronService); - const data = await cookieService.get(req.params.id); + const cronService = Container.get(CronService); + const data = await cronService.get(req.params.id); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); diff --git a/back/config/index.ts b/back/config/index.ts index ca68177e..39781407 100644 --- a/back/config/index.ts +++ b/back/config/index.ts @@ -20,6 +20,7 @@ const configString = 'config sample crontab shareCode diy'; const dbPath = path.join(rootPath, 'db/'); const manualLogPath = path.join(rootPath, 'manual_log/'); const cronDbFile = path.join(rootPath, 'db/crontab.db'); +const cookieDbFile = path.join(rootPath, 'db/cookie.db'); if (envFound.error) { throw new Error("⚠️ Couldn't find .env file ⚠️"); @@ -53,5 +54,6 @@ export default { }, dbPath, cronDbFile, + cookieDbFile, manualLogPath, }; diff --git a/back/data/cookie.ts b/back/data/cookie.ts new file mode 100644 index 00000000..62cc3747 --- /dev/null +++ b/back/data/cookie.ts @@ -0,0 +1,27 @@ +export class Cookie { + value?: string; + timestamp?: string; + created?: number; + _id?: string; + status?: CookieStatus; + position?: number; + + constructor(options: Cookie) { + this.value = options.value; + this._id = options._id; + this.created = options.created || new Date().valueOf(); + this.status = options.status || CookieStatus.noacquired; + this.timestamp = new Date().toString(); + this.position = options.position; + } +} + +export enum CookieStatus { + 'noacquired', + 'normal', + 'disabled', + 'invalid', + 'abnormal', +} + +export const initCookiePosition = 9999999999; diff --git a/back/services/cookie.ts b/back/services/cookie.ts index 3836d5f4..7ff736c2 100644 --- a/back/services/cookie.ts +++ b/back/services/cookie.ts @@ -5,180 +5,21 @@ import { getFileContentByName } from '../config/util'; import config from '../config'; import * as fs from 'fs'; import got from 'got'; - -enum Status { - '正常', - '失效', - '状态异常', -} +import DataStore from 'nedb'; +import { Cookie, CookieStatus, initCookiePosition } from '../data/cookie'; @Service() export default class CookieService { - private cookies: string = ''; - private s_token: string = ''; - private guid: string = ''; - private lsid: string = ''; - private lstoken: string = ''; - private okl_token: string = ''; - private token: string = ''; - constructor(@Inject('logger') private logger: winston.Logger) {} - - public async getQrUrl(): Promise<{ qrurl: string }> { - await this.step1(); - const qrurl = await this.step2(); - return { qrurl }; + private cronDb = new DataStore({ filename: config.cookieDbFile }); + constructor(@Inject('logger') private logger: winston.Logger) { + this.cronDb.loadDatabase((err) => { + if (err) throw err; + }); } - private async step1() { - try { - let timeStamp = new Date().getTime(); - let url = - 'https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance?lang=chs&appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=' + - timeStamp + - '&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport'; - const text = await fetch(url, { - method: 'get', - headers: { - Connection: 'Keep-Alive', - 'Content-Type': 'application/x-www-form-urlencoded', - Accept: 'application/json, text/plain, */*', - 'Accept-Language': 'zh-cn', - Referer: - 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=' + - timeStamp + - '&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', - Host: 'plogin.m.jd.com', - }, - }); - await this.praseSetCookies(text); - } catch (error) { - this.logger.error(error); - } - } - - private async step2() { - try { - if (this.cookies == '') { - return ''; - } - let timeStamp = new Date().getTime(); - let url = - 'https://plogin.m.jd.com/cgi-bin/m/tmauthreflogurl?s_token=' + - this.s_token + - '&v=' + - timeStamp + - '&remember=true'; - const response: any = await fetch(url, { - method: 'post', - body: JSON.stringify({ - lang: 'chs', - appid: 300, - returnurl: - 'https://wqlogin2.jd.com/passport/LoginRedirect?state=' + - timeStamp + - '&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action', - source: 'wq_passport', - }), - headers: { - Connection: 'Keep-Alive', - 'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8', - Accept: 'application/json, text/plain, */*', - Cookie: this.cookies, - Referer: - 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=' + - timeStamp + - '&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', - Host: 'plogin.m.jd.com', - }, - }); - const body = await response.json(); - this.token = body.token; - const setCookies = response.headers.get('set-cookie'); - this.okl_token = setCookies.match(/okl_token=(.+?);/)[1]; - var qrUrl = - 'https://plogin.m.jd.com/cgi-bin/m/tmauth?appid=300&client_type=m&token=' + - this.token; - return qrUrl; - } catch (error) { - console.log(error.response.body); - return ''; - } - } - - private async praseSetCookies(response: any) { - const body = await response.json(); - this.s_token = body.s_token; - const setCookies = response.headers.get('set-cookie'); - this.guid = setCookies.match(/guid=(.+?);/)[1]; - this.lsid = setCookies.match(/lsid=(.+?);/)[1]; - this.lstoken = setCookies.match(/lstoken=(.+?);/)[1]; - this.cookies = - 'guid=' + - this.guid + - '; lang=chs; lsid=' + - this.lsid + - '; lstoken=' + - this.lstoken + - '; '; - } - - private getCookie(response: any) { - const setCookies = response.headers['set-cookie']; - var TrackerID = setCookies[0].match(/TrackerID=(.+?);/)[1]; - var pt_key = setCookies[1].match(/pt_key=(.+?);/)[1]; - var pt_pin = setCookies[2].match(/pt_pin=(.+?);/)[1]; - var pt_token = setCookies[3].match(/pt_token=(.+?);/)[1]; - var pwdt_id = setCookies[4].match(/pwdt_id=(.+?);/)[1]; - var s_key = setCookies[5].match(/s_key=(.+?);/)[1]; - var s_pin = setCookies[6].match(/s_pin=(.+?);/)[1]; - this.cookies = - 'TrackerID=' + - TrackerID + - '; pt_key=' + - pt_key + - '; pt_pin=' + - pt_pin + - '; pt_token=' + - pt_token + - '; pwdt_id=' + - pwdt_id + - '; s_key=' + - s_key + - '; s_pin=' + - s_pin + - '; wq_skey='; - var userCookie = 'pt_key=' + pt_key + ';pt_pin=' + pt_pin + ';'; - return userCookie; - } - - public async addQrCookie(cookie: string) { - const res: any = await this.checkLogin(); - if (res.body.errcode === 0) { - let ucookie = this.getCookie(res); - let content = getFileContentByName(config.confFile); - const regx = /.*Cookie[0-9]{1}\=\"(.+?)\"/g; - if (content.match(regx)) { - const lastCookie = cookie || (content.match(regx) as any[]).pop(); - const cookieRegx = /Cookie([0-9]+)\=.+?/.exec(lastCookie); - if (cookieRegx) { - const num = parseInt(cookieRegx[1]) + 1; - const newCookie = `${lastCookie}\nCookie${num}="${ucookie}"`; - const result = content.replace(lastCookie, newCookie); - fs.writeFileSync(config.confFile, result); - } - } else { - const newCookie = `Cookie1="${ucookie}"`; - const result = content.replace(`Cookie1=""`, newCookie); - fs.writeFileSync(config.confFile, result); - } - return { cookie: ucookie }; - } else { - return res.body; - } + public async getCookies() { + const content = getFileContentByName(config.cookieFile); + return this.formatCookie(content.split('\n').filter((x) => !!x)); } public async addCookie(cookies: string[]) { @@ -215,53 +56,6 @@ export default class CookieService { } } - private async checkLogin() { - try { - if (this.cookies == '') { - return ''; - } - let timeStamp = new Date().getTime(); - let url = - 'https://plogin.m.jd.com/cgi-bin/m/tmauthchecktoken?&token=' + - this.token + - '&ou_state=0&okl_token=' + - this.okl_token; - return got.post(url, { - responseType: 'json', - form: { - lang: 'chs', - appid: 300, - returnurl: - 'https://wqlogin2.jd.com/passport/LoginRedirect?state=1100399130787&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action', - source: 'wq_passport', - }, - headers: { - Referer: - 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=' + - timeStamp + - '&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport', - Cookie: this.cookies, - Connection: 'Keep-Alive', - 'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8', - Accept: 'application/json, text/plain, */*', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', - }, - }); - } catch (error) { - console.log(error); - let res: any = {}; - res.body = { check_ip: 0, errcode: 222, message: '出错' }; - res.headers = {}; - return res; - } - } - - public async getCookies() { - const content = getFileContentByName(config.cookieFile); - return this.formatCookie(content.split('\n').filter((x) => !!x)); - } - private async formatCookie(data: any[]) { const result = []; for (const x of data) { @@ -285,14 +79,12 @@ export default class CookieService { return result; } - public async refreshCookie(body: any) { - const { cookie } = body; - const { nickname, status } = await this.getJdInfo(cookie); + public async refreshCookie(_id: string) { + const current = await this.get(_id); + const { status } = await this.getJdInfo(current.value); return { - pin: cookie.match(/pt_pin=(.+?);/)[1], - cookie, + ...current, status, - nickname: nickname, }; } @@ -317,11 +109,140 @@ export default class CookieService { .then((x) => x.json()) .then((x) => { if (x.retcode === '0' && x.data && x.data.userInfo) { - return { nickname: x.data.userInfo.baseInfo.nickname, status: 0 }; + return { + nickname: x.data.userInfo.baseInfo.nickname, + status: CookieStatus.normal, + }; } else if (x.retcode === 13) { - return { status: 1, nickname: '-' }; + return { status: CookieStatus.invalid, nickname: '-' }; } - return { status: 2, nickname: '-' }; + return { status: CookieStatus.abnormal, nickname: '-' }; }); } + + public async create(payload: string[]): Promise { + const cookies = await this.cookies('', { postion: 1 }); + let position = initCookiePosition; + if (cookies && cookies.length > 0) { + position = cookies[0].position / 2; + } + const tabs = payload.map((x) => { + const cookie = new Cookie({ value: x, position }); + position = position / 2; + return cookie; + }); + this.cronDb.insert(tabs); + await this.set_cookies(); + } + + public async update(payload: Cookie): Promise { + const { _id, ...other } = payload; + const doc = await this.get(_id); + const tab = new Cookie({ ...doc, ...other }); + this.cronDb.update({ _id }, tab, { returnUpdatedDocs: true }); + await this.set_cookies(); + } + + public async remove(_id: string) { + this.cronDb.remove({ _id }, {}); + await this.set_cookies(); + } + + public async move( + _id: string, + { + fromIndex, + toIndex, + }: { + fromIndex: number; + toIndex: number; + }, + ) { + let targetPosition: number; + const isUpward = fromIndex > toIndex; + const cookies = await this.cookies(); + if (toIndex === 0 || toIndex === cookies.length - 1) { + targetPosition = isUpward + ? (cookies[0].position * 2 + 1) / 2 + : (cookies[toIndex].position * 2 - 1) / 2; + } else { + targetPosition = isUpward + ? (cookies[toIndex].position + cookies[toIndex - 1].position) / 2 + : (cookies[toIndex].position + cookies[toIndex + 1].position) / 2; + } + this.update({ + _id, + position: targetPosition, + }); + await this.set_cookies(); + } + + public async cookies( + searchText?: string, + sort: any = { position: -1 }, + ): Promise { + let query = {}; + if (searchText) { + const reg = new RegExp(searchText); + query = { + $or: [ + { + name: reg, + }, + { + command: reg, + }, + ], + }; + } + return new Promise((resolve) => { + this.cronDb + .find(query) + .sort({ ...sort }) + .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 getBySort(sort: any): Promise { + return new Promise((resolve) => { + this.cronDb + .find({}) + .sort({ ...sort }) + .limit(1) + .exec((err, docs) => { + resolve(docs[0]); + }); + }); + } + + public async disabled(_id: string) { + this.cronDb.update({ _id }, { $set: { status: CookieStatus.disabled } }); + await this.set_cookies(); + } + + public async enabled(_id: string) { + this.cronDb.update({ _id }, { $set: { status: CookieStatus.noacquired } }); + } + + private async set_cookies() { + const cookies = await this.cookies(); + let cookie_string = ''; + cookies.forEach((tab) => { + if (tab.status !== CookieStatus.disabled) { + cookie_string += tab.value; + cookie_string += '\n'; + } + }); + fs.writeFileSync(config.cookieFile, cookie_string); + } } diff --git a/back/services/cron.ts b/back/services/cron.ts index 3bbba6aa..b44ebbf0 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -90,7 +90,7 @@ export default class CronService { this.logger.silly('Original command: ' + res.command); let logFile = `${config.manualLogPath}${res._id}.log`; - fs.writeFileSync(logFile, `${new Date().toString()}\n\n`); + fs.writeFileSync(logFile, `开始执行...\n\n${new Date().toString()}\n`); let cmdStr = res.command; if (res.command.startsWith('js')) { @@ -121,6 +121,18 @@ export default class CronService { this.logger.silly(err); fs.appendFileSync(logFile, err.stack); }); + + cmd.on('exit', (code: number, signal: any) => { + this.logger.silly(`cmd exit ${code}`); + this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } }); + fs.appendFileSync(logFile, `\n\n执行结束...`); + }); + + cmd.on('disconnect', () => { + this.logger.silly(`cmd disconnect`); + this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } }); + fs.appendFileSync(logFile, `\n\n连接断开...`); + }); }); } diff --git a/package.json b/package.json index 29ae1c8a..944ee9a1 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "qrcode.react": "^1.0.1", "react-codemirror2": "^7.2.1", "react-diff-viewer": "^3.1.1", + "react-dnd": "^14.0.2", + "react-dnd-html5-backend": "^14.0.0", "reflect-metadata": "^0.1.13", "typedi": "^0.8.0", "umi": "^3.3.9", diff --git a/src/app.tsx b/src/app.tsx index e567ee09..2e3aef04 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -18,7 +18,7 @@ export function render(oldRender: any) { }) .catch((e) => { console.log(e); - if (e.response.status === 401) { + if (e.response && e.response.status === 401) { localStorage.removeItem(config.authKey); history.push('/login'); oldRender(); diff --git a/src/layouts/defaultProps.tsx b/src/layouts/defaultProps.tsx index 8c039d1b..59b44c58 100644 --- a/src/layouts/defaultProps.tsx +++ b/src/layouts/defaultProps.tsx @@ -49,12 +49,6 @@ export default { icon: , component: '@/pages/diff/index', }, - { - path: '/code', - name: '互助码', - icon: , - component: '@/pages/code/index', - }, { path: '/log', name: '日志', diff --git a/src/pages/code/index.less b/src/pages/code/index.less deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/code/index.tsx b/src/pages/code/index.tsx deleted file mode 100644 index b5e4194c..00000000 --- a/src/pages/code/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { PureComponent, Fragment, useState, useEffect } from 'react'; -import { Button, notification, Modal } from 'antd'; -import config from '@/utils/config'; -import { PageContainer } from '@ant-design/pro-layout'; -import { Controlled as CodeMirror } from 'react-codemirror2'; -import { request } from '@/utils/http'; - -const Crontab = () => { - const [width, setWdith] = useState('100%'); - const [marginLeft, setMarginLeft] = useState(0); - const [marginTop, setMarginTop] = useState(-72); - const [value, setValue] = useState(''); - const [loading, setLoading] = useState(true); - - const getConfig = () => { - setLoading(true); - request - .get(`${config.apiPrefix}config/shareCode`) - .then((data) => { - setValue(data.data); - }) - .finally(() => setLoading(false)); - }; - - useEffect(() => { - if (document.body.clientWidth < 768) { - setWdith('auto'); - setMarginLeft(0); - setMarginTop(0); - } else { - setWdith('100%'); - setMarginLeft(0); - setMarginTop(-72); - } - getConfig(); - }, []); - - return ( - - { - setValue(value); - }} - onChange={(editor, data, value) => {}} - /> - - ); -}; - -export default Crontab; diff --git a/src/pages/cookie/index.less b/src/pages/cookie/index.less index e69de29b..17c955e6 100644 --- a/src/pages/cookie/index.less +++ b/src/pages/cookie/index.less @@ -0,0 +1,7 @@ +tr.drop-over-downward td { + border-bottom: 2px dashed #1890ff; +} + +tr.drop-over-upward td { + border-top: 2px dashed #1890ff; +} diff --git a/src/pages/cookie/index.tsx b/src/pages/cookie/index.tsx index cdf91d9a..e5c10943 100644 --- a/src/pages/cookie/index.tsx +++ b/src/pages/cookie/index.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent, Fragment, useState, useEffect } from 'react'; +import React, { useCallback, useRef, useState, useEffect } from 'react'; import { Button, notification, @@ -7,48 +7,121 @@ import { Tag, Space, Typography, + Tooltip, } from 'antd'; -import { EditOutlined, DeleteOutlined } from '@ant-design/icons'; +import { + EditOutlined, + DeleteOutlined, + SyncOutlined, + CheckCircleOutlined, + StopOutlined, +} from '@ant-design/icons'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; import { request } from '@/utils/http'; import QRCode from 'qrcode.react'; import CookieModal from './modal'; +import { DndProvider, useDrag, useDrop } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import './index.less'; const { Text } = Typography; enum Status { + '未获取', '正常', - '失效', + '已禁用', + '已失效', '状态异常', } + enum StatusColor { + 'default', 'success', - 'error', 'warning', + 'error', } +const type = 'DragableBodyRow'; + +const DragableBodyRow = ({ + index, + moveRow, + className, + style, + ...restProps +}: any) => { + const ref = useRef(); + const [{ isOver, dropClassName }, drop] = useDrop( + () => ({ + accept: type, + collect: (monitor) => { + const { index: dragIndex } = monitor.getItem() || ({} as any); + if (dragIndex === index) { + return {}; + } + return { + isOver: monitor.isOver(), + dropClassName: + dragIndex < index ? ' drop-over-downward' : ' drop-over-upward', + }; + }, + drop: (item: any) => { + moveRow(item.index, index); + }, + }), + [index], + ); + const [, drag, preview] = useDrag( + () => ({ + type, + item: { index }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }), + [index], + ); + drop(drag(ref)); + + return ( + + {restProps.children} + + ); +}; + const Config = () => { const columns = [ + { + title: '序号', + align: 'center' as const, + render: (text: string, record: any, index: number) => { + return {index + 1} ; + }, + }, { title: '用户名', dataIndex: 'pin', key: 'pin', align: 'center' as const, render: (text: string, record: any) => { - return {decodeURIComponent(text)}; + const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/); + const val = (match && match[1]) || '未匹配用户名'; + return ( + {decodeURIComponent(val)} + ); }, }, - { - title: '昵称', - dataIndex: 'nickname', - key: 'nickname', - align: 'center' as const, - }, { title: '值', - dataIndex: 'cookie', - key: 'cookie', + dataIndex: 'value', + key: 'value', align: 'center' as const, width: '50%', render: (text: string, record: any) => { @@ -58,6 +131,7 @@ const Config = () => { textAlign: 'left', display: 'inline-block', wordBreak: 'break-all', + cursor: 'text', }} > {text} @@ -70,12 +144,23 @@ const Config = () => { key: 'status', dataIndex: 'status', align: 'center' as const, - render: (text: string, record: any) => { + width: '15%', + render: (text: string, record: any, index: number) => { return ( - - + + {Status[record.status]} + {record.status !== Status.已禁用 && ( + + refreshStatus(record, index)}> + + + + )} ); }, @@ -86,8 +171,25 @@ const Config = () => { align: 'center' as const, render: (text: string, record: any, index: number) => ( - editCookie(record, index)} /> - deleteCookie(record, index)} /> + + editCookie(record, index)}> + + + + + enabledOrDisabledCron(record, index)}> + {record.status === Status.已禁用 ? ( + + ) : ( + + )} + + + + deleteCookie(record, index)}> + + + ), }, @@ -95,7 +197,7 @@ const Config = () => { 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 [editedCookie, setEditedCookie] = useState(); @@ -110,98 +212,77 @@ const Config = () => { .finally(() => setLoading(false)); }; - function sleep(time: number) { - return new Promise((resolve) => setTimeout(resolve, time)); - } - - const showQrCode = (oldCookie?: string) => { - request.get(`${config.apiPrefix}qrcode`).then(async (data) => { - if (data.qrcode) { - const modal = Modal.info({ - title: '二维码', - content: ( -
- -
- ), - }); - getCookie(modal, oldCookie); - } else { - notification.error({ message: '获取二维码失败' }); - } - }); - }; - - const getCookie = async ( - modal: { destroy: () => void }, - oldCookie: string = '', - ) => { - for (let i = 0; i < 50; i++) { - const { - data: { cookie, errcode, message }, - } = await request.get(`${config.apiPrefix}cookie?cookie=${oldCookie}`); - if (cookie) { - notification.success({ - message: 'Cookie获取成功', - }); - modal.destroy(); - Modal.success({ - title: '获取Cookie成功', - content:
{cookie}
, - }); - getCookies(); - break; - } - if (errcode !== 176) { - notification.error({ message }); - break; - } - await sleep(2000); - } - }; - - const reacquire = async (record: any) => { - await showQrCode(record.cookie); - }; - const refreshStatus = (record: any, index: number) => { request - .post(`${config.apiPrefix}cookie/refresh`, { - data: { cookie: record.cookie }, - }) + .get(`${config.apiPrefix}cookies/${record._id}/refresh`) .then(async (data: any) => { - if (data.data && data.data.cookie) { + if (data.data && data.data.value) { (value as any).splice(index, 1, data.data); setValue([...(value as any)] as any); - notification.success({ message: '更新状态成功' }); } else { notification.error({ message: '更新状态失败' }); } }); }; + const enabledOrDisabledCron = (record: any, index: number) => { + Modal.confirm({ + title: `确认${record.status === Status.已禁用 ? '启用' : '禁用'}`, + content: ( + <> + 确认{record.status === Status.已禁用 ? '启用' : '禁用'} + Cookie{' '} + + {record.value} + {' '} + 吗 + + ), + onOk() { + request + .get( + `${config.apiPrefix}cookies/${record._id}/${ + record.status === Status.已禁用 ? 'enable' : 'disable' + }`, + { + data: { _id: record._id }, + }, + ) + .then((data: any) => { + if (data.code === 200) { + notification.success({ + message: `${ + record.status === Status.已禁用 ? '启用' : '禁用' + }成功`, + }); + const newStatus = + record.status === Status.已禁用 ? Status.未获取 : Status.已禁用; + const result = [...value]; + result.splice(index, 1, { + ...record, + status: newStatus, + }); + setValue(result); + } else { + notification.error({ + message: data, + }); + } + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + const addCookie = () => { setEditedCookie(null as any); setIsModalVisible(true); }; const editCookie = (record: any, index: number) => { - setEditedCookie(record.cookie); + setEditedCookie(record); setIsModalVisible(true); }; @@ -210,20 +291,24 @@ const Config = () => { title: '确认删除', content: ( <> - 确认删除Cookie {record.cookie} 吗 + 确认删除Cookie{' '} + + {record.value} + {' '} + 吗 ), onOk() { request - .delete(`${config.apiPrefix}cookie`, { - data: { cookie: record.cookie }, - }) + .delete(`${config.apiPrefix}cookies/${record._id}`) .then((data: any) => { if (data.code === 200) { notification.success({ message: '删除成功', }); - getCookies(); + const result = [...value]; + result.splice(index, 1); + setValue(result); } else { notification.error({ message: data, @@ -240,10 +325,53 @@ const Config = () => { const handleCancel = (needUpdate?: boolean) => { setIsModalVisible(false); if (needUpdate) { - getCookies(); + getCookieDetail(editedCookie); } }; + const getCookieDetail = (cookie: any) => { + request + .get(`${config.apiPrefix}cookies/${cookie._id}`) + .then((data: any) => { + const index = value.findIndex((x) => x._id === cookie._id); + const result = [...value]; + result.splice(index, 1, { + ...cookie, + ...data.data, + }); + setValue(result); + }) + .finally(() => setLoading(false)); + }; + + const components = { + body: { + row: DragableBodyRow, + }, + }; + + const moveRow = useCallback( + (dragIndex, hoverIndex) => { + const dragRow = value[dragIndex]; + const newData = [...value]; + newData.splice(dragIndex, 1); + newData.splice(hoverIndex, 0, dragRow); + setValue([...newData]); + request + .put(`${config.apiPrefix}cookies/${dragRow._id}/move`, { + data: { fromIndex: dragIndex, toIndex: hoverIndex }, + }) + .then((data: any) => { + if (data.code !== 200) { + notification.error({ + message: data, + }); + } + }); + }, + [value], + ); + useEffect(() => { if (document.body.clientWidth < 768) { setWdith('auto'); @@ -283,15 +411,24 @@ const Config = () => { height: '100vh', }} > - + +
{ + return { + index, + moveRow, + } as any; + }} + /> + void; }) => { const [form] = Form.useForm(); const handleOk = async (values: any) => { - const cookies = values.cookie + const cookies = values.value .split('\n') .map((x: any) => x.trim().replace(/\s/g, '')); let flag = false; @@ -30,10 +30,8 @@ const CookieModal = ({ return; } const method = cookie ? 'put' : 'post'; - const payload = cookie - ? { cookie: cookies[0], oldCookie: cookie } - : { cookies }; - const { code, data } = await request[method](`${config.apiPrefix}cookie`, { + const payload = cookie ? { value: cookies[0], _id: cookie._id } : cookies; + const { code, data } = await request[method](`${config.apiPrefix}cookies`, { data: payload, }); if (code === 200) { @@ -49,11 +47,7 @@ const CookieModal = ({ }; useEffect(() => { - if (cookie) { - form.setFieldsValue({ cookie }); - } else { - form.resetFields(); - } + form.resetFields(); }, [cookie]); return ( @@ -74,9 +68,15 @@ const CookieModal = ({ onCancel={() => handleCancel()} destroyOnClose > -
+ { title: '确认删除', content: ( <> - 确认删除定时任务 {record.name} 吗 + 确认删除定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -201,7 +205,11 @@ const Crontab = () => { title: '确认运行', content: ( <> - 确认运行定时任务 {record.name} 吗 + 确认运行定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -236,7 +244,11 @@ const Crontab = () => { content: ( <> 确认{record.status === CrontabStatus.disabled ? '启用' : '禁用'} - 定时任务 {record.name} 吗 + 定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -415,7 +427,7 @@ const Crontab = () => { defaultPageSize: 20, }} dataSource={value} - rowKey="pin" + rowKey="_id" size="middle" bordered scroll={{ x: 768 }} diff --git a/src/pages/crontab/logModal.tsx b/src/pages/crontab/logModal.tsx index dcfd255b..ef42d94a 100644 --- a/src/pages/crontab/logModal.tsx +++ b/src/pages/crontab/logModal.tsx @@ -3,6 +3,12 @@ import { Modal, notification, Input, Form } from 'antd'; import { request } from '@/utils/http'; import config from '@/utils/config'; +enum CrontabStatus { + 'running', + 'idle', + 'disabled', +} + const CronLogModal = ({ cron, handleCancel, @@ -12,14 +18,14 @@ const CronLogModal = ({ visible: boolean; handleCancel: () => void; }) => { - const [value, setValue] = useState('运行中...'); + const [value, setValue] = useState('启动中...'); const [logTimer, setLogTimer] = useState(); const getCronLog = () => { request .get(`${config.apiPrefix}crons/${cron._id}/log`) .then((data: any) => { - setValue(data.data); + setValue(data.data || '暂无日志'); }); }; diff --git a/src/utils/http.ts b/src/utils/http.ts index e0f801d8..b07d9af8 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -5,7 +5,9 @@ import config from './config'; const time = Date.now(); const errorHandler = function (error: any) { if (error.response) { - const message = error.data ? error.data.message : error.response.statusText; + const message = error.data + ? error.data.message || error.data + : error.response.statusText; if (error.response.status !== 401) { notification.error({ message }); } From f9c299cfbe99ef51a0c29d457a11056233064f12 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 10 Apr 2021 13:48:44 +0800 Subject: [PATCH 03/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B7=BB=E5=8A=A0ck?= =?UTF-8?q?=E5=92=8Ccron=E6=9C=AA=E6=9B=B4=E6=96=B0=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/services/cookie.ts | 43 +++++++++++++++++++++++++++++++------ back/services/cron.ts | 39 +++++++++++++++++++++++++++++---- src/pages/cookie/index.tsx | 33 ++++++++++++++-------------- src/pages/cookie/modal.tsx | 2 +- src/pages/crontab/index.tsx | 19 +++++++++++++--- src/pages/crontab/modal.tsx | 2 +- 6 files changed, 107 insertions(+), 31 deletions(-) diff --git a/back/services/cookie.ts b/back/services/cookie.ts index 7ff736c2..689e6abc 100644 --- a/back/services/cookie.ts +++ b/back/services/cookie.ts @@ -120,7 +120,7 @@ export default class CookieService { }); } - public async create(payload: string[]): Promise { + public async create(payload: string[]): Promise { const cookies = await this.cookies('', { postion: 1 }); let position = initCookiePosition; if (cookies && cookies.length > 0) { @@ -131,16 +131,47 @@ export default class CookieService { position = position / 2; return cookie; }); - this.cronDb.insert(tabs); + const docs = await this.insert(tabs); await this.set_cookies(); + return docs; } - public async update(payload: Cookie): Promise { + public async insert(payload: Cookie[]): Promise { + return new Promise((resolve) => { + this.cronDb.insert(payload, (err, docs) => { + if (err) { + this.logger.error(err); + } else { + resolve(docs); + } + }); + }); + } + + public async update(payload: Cookie): Promise { const { _id, ...other } = payload; const doc = await this.get(_id); const tab = new Cookie({ ...doc, ...other }); - this.cronDb.update({ _id }, tab, { returnUpdatedDocs: true }); + const newDoc = await this.updateDb(tab); await this.set_cookies(); + return newDoc; + } + + public async updateDb(payload: Cookie): Promise { + return new Promise((resolve) => { + this.cronDb.update( + { _id: payload._id }, + payload, + { returnUpdatedDocs: true }, + (err, docs) => { + if (err) { + this.logger.error(err); + } else { + resolve(docs as Cookie); + } + }, + ); + }); } public async remove(_id: string) { @@ -163,8 +194,8 @@ export default class CookieService { const cookies = await this.cookies(); if (toIndex === 0 || toIndex === cookies.length - 1) { targetPosition = isUpward - ? (cookies[0].position * 2 + 1) / 2 - : (cookies[toIndex].position * 2 - 1) / 2; + ? cookies[0].position * 2 + : cookies[toIndex].position / 2; } else { targetPosition = isUpward ? (cookies[toIndex].position + cookies[toIndex - 1].position) / 2 diff --git a/back/services/cron.ts b/back/services/cron.ts index b44ebbf0..4eb24a61 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -22,21 +22,52 @@ export default class CronService { return this.cronDb; } - public async create(payload: Crontab): Promise { + public async create(payload: Crontab): Promise { const tab = new Crontab(payload); tab.created = new Date().valueOf(); tab.saved = false; - this.cronDb.insert(tab); + const doc = await this.insert(tab); await this.set_crontab(); + return doc; } - public async update(payload: Crontab): Promise { + public async insert(payload: Crontab): Promise { + return new Promise((resolve) => { + this.cronDb.insert(payload, (err, docs) => { + if (err) { + this.logger.error(err); + } else { + resolve(docs); + } + }); + }); + } + + 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 }); + const newDoc = await this.update(tab); await this.set_crontab(); + return newDoc; + } + + public async updateDb(payload: Crontab): Promise { + return new Promise((resolve) => { + this.cronDb.update( + { _id: payload._id }, + payload, + { returnUpdatedDocs: true }, + (err, num, docs: any) => { + if (err) { + this.logger.error(err); + } else { + resolve(docs); + } + }, + ); + }); } public async status(_id: string, stopped: boolean) { diff --git a/src/pages/cookie/index.tsx b/src/pages/cookie/index.tsx index e5c10943..8ace424c 100644 --- a/src/pages/cookie/index.tsx +++ b/src/pages/cookie/index.tsx @@ -322,26 +322,24 @@ const Config = () => { }); }; - const handleCancel = (needUpdate?: boolean) => { + const handleCancel = (cookie: any) => { setIsModalVisible(false); - if (needUpdate) { - getCookieDetail(editedCookie); + if (cookie) { + handleCookies(cookie); } }; - const getCookieDetail = (cookie: any) => { - request - .get(`${config.apiPrefix}cookies/${cookie._id}`) - .then((data: any) => { - const index = value.findIndex((x) => x._id === cookie._id); - const result = [...value]; - result.splice(index, 1, { - ...cookie, - ...data.data, - }); - setValue(result); - }) - .finally(() => setLoading(false)); + const handleCookies = (cookie: any) => { + const index = value.findIndex((x) => x._id === cookie._id); + const result = [...value]; + if (index === -1) { + result.push(...cookie); + } else { + result.splice(index, 1, { + ...cookie, + }); + } + setValue(result); }; const components = { @@ -352,6 +350,9 @@ const Config = () => { const moveRow = useCallback( (dragIndex, hoverIndex) => { + if (dragIndex === hoverIndex) { + return; + } const dragRow = value[dragIndex]; const newData = [...value]; newData.splice(dragIndex, 1); diff --git a/src/pages/cookie/modal.tsx b/src/pages/cookie/modal.tsx index c2b469d2..aa21d609 100644 --- a/src/pages/cookie/modal.tsx +++ b/src/pages/cookie/modal.tsx @@ -43,7 +43,7 @@ const CookieModal = ({ message: data, }); } - handleCancel(true); + handleCancel(data); }; useEffect(() => { diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx index 61bd1bf7..78f88f90 100644 --- a/src/pages/crontab/index.tsx +++ b/src/pages/crontab/index.tsx @@ -347,10 +347,10 @@ const Crontab = () => { } }; - const handleCancel = (needUpdate?: boolean) => { + const handleCancel = (cron?: any) => { setIsModalVisible(false); - if (needUpdate) { - getCronDetail(editedCron); + if (cron) { + handleCrons(cron); } }; @@ -358,6 +358,19 @@ const Crontab = () => { setSearchText(value); }; + const handleCrons = (cron: any) => { + const index = value.findIndex((x) => x._id === cron._id); + const result = [...value]; + if (index === -1) { + result.push(cron); + } else { + result.splice(index, 1, { + ...cron, + }); + } + setValue(result); + }; + const getCronDetail = (cron: any) => { request .get(`${config.apiPrefix}crons/${cron._id}`) diff --git a/src/pages/crontab/modal.tsx b/src/pages/crontab/modal.tsx index db1643dd..79a18cb0 100644 --- a/src/pages/crontab/modal.tsx +++ b/src/pages/crontab/modal.tsx @@ -33,7 +33,7 @@ const CronModal = ({ message: data, }); } - handleCancel(true); + handleCancel(data); }; useEffect(() => { From 9a1891c909c2b04ebde7fd6f2f3014a670dce706 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 10 Apr 2021 19:50:52 +0800 Subject: [PATCH 04/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcron=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shell/git_pull.sh | 2 +- src/pages/crontab/index.tsx | 9 +++++++-- src/pages/crontab/logModal.tsx | 35 +++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/shell/git_pull.sh b/shell/git_pull.sh index 2d2629db..eb91c93e 100755 --- a/shell/git_pull.sh +++ b/shell/git_pull.sh @@ -105,7 +105,7 @@ function Npm_InstallSub() { npm install --no-save --registry=https://registry.npm.taobao.org || npm install --no-save else echo -e "检测到本机安装了 yarn,使用 yarn 替代 npm...\n" - yarn install --registry=https://registry.npm.taobao.org || yarn install + yarn install --registry=https://registry.npm.taobao.org --network-timeout 1000000000 || yarn install fi } diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx index 78f88f90..005f1c4f 100644 --- a/src/pages/crontab/index.tsx +++ b/src/pages/crontab/index.tsx @@ -120,8 +120,7 @@ const Crontab = () => { { - setLogCron(record); - setIsLogModalVisible(true); + setLogCron({ ...record, timestamp: Date.now() }); }} > @@ -386,6 +385,12 @@ const Crontab = () => { .finally(() => setLoading(false)); }; + useEffect(() => { + if (logCron) { + setIsLogModalVisible(true); + } + }, [logCron]); + useEffect(() => { getCrons(); }, [searchText]); diff --git a/src/pages/crontab/logModal.tsx b/src/pages/crontab/logModal.tsx index ef42d94a..abc47442 100644 --- a/src/pages/crontab/logModal.tsx +++ b/src/pages/crontab/logModal.tsx @@ -20,12 +20,37 @@ const CronLogModal = ({ }) => { const [value, setValue] = useState('启动中...'); const [logTimer, setLogTimer] = useState(); + const [loading, setLoading] = useState(true); - const getCronLog = () => { + const getCronLog = (isFirst?: boolean) => { + if (isFirst) { + setLoading(true); + } request .get(`${config.apiPrefix}crons/${cron._id}/log`) .then((data: any) => { - setValue(data.data || '暂无日志'); + const log = data.data as string; + setValue(log || '暂无日志'); + if (log.includes('执行结束')) { + if (logTimer) { + clearInterval(logTimer); + } + } else { + const timer = setInterval(() => { + getCronLog(); + }, 2000); + setLogTimer(timer); + } + }) + .catch(() => { + if (logTimer) { + clearInterval(logTimer); + } + }) + .finally(() => { + if (isFirst) { + setLoading(false); + } }); }; @@ -36,10 +61,7 @@ const CronLogModal = ({ useEffect(() => { if (cron) { - const timer = setInterval(() => { - getCronLog(); - }, 2000); - setLogTimer(timer); + getCronLog(true); } }, [cron]); @@ -50,7 +72,6 @@ const CronLogModal = ({ forceRender onOk={() => cancel()} onCancel={() => cancel()} - destroyOnClose >
         {value}

From 7105952670db75ace1dc2e9ad55435802c50c5a1 Mon Sep 17 00:00:00 2001
From: whyour 
Date: Sat, 10 Apr 2021 20:02:55 +0800
Subject: [PATCH 05/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcron=E6=97=A5=E5=BF=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/pages/crontab/logModal.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/crontab/logModal.tsx b/src/pages/crontab/logModal.tsx
index abc47442..a61ccbb5 100644
--- a/src/pages/crontab/logModal.tsx
+++ b/src/pages/crontab/logModal.tsx
@@ -35,7 +35,7 @@ const CronLogModal = ({
           if (logTimer) {
             clearInterval(logTimer);
           }
-        } else {
+        } else if (isFirst) {
           const timer = setInterval(() => {
             getCronLog();
           }, 2000);

From 2a7d298f056a4c22b0058cf1c2ebd3f932ad305f Mon Sep 17 00:00:00 2001
From: whyour 
Date: Sun, 11 Apr 2021 10:31:30 +0800
Subject: [PATCH 06/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcron=E6=97=A5=E5=BF=97?=
 =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 back/services/cron.ts          |  2 +-
 src/pages/crontab/logModal.tsx | 18 +++---------------
 2 files changed, 4 insertions(+), 16 deletions(-)

diff --git a/back/services/cron.ts b/back/services/cron.ts
index 4eb24a61..5ea4dcfc 100644
--- a/back/services/cron.ts
+++ b/back/services/cron.ts
@@ -124,7 +124,7 @@ export default class CronService {
       fs.writeFileSync(logFile, `开始执行...\n\n${new Date().toString()}\n`);
 
       let cmdStr = res.command;
-      if (res.command.startsWith('js')) {
+      if (res.command.startsWith('js') && !res.command.endsWith('now')) {
         cmdStr = `${res.command} now`;
       } else if (/&& (.*) >>/.test(res.command)) {
         cmdStr = res.command.match(/&& (.*) >>/)[1];
diff --git a/src/pages/crontab/logModal.tsx b/src/pages/crontab/logModal.tsx
index a61ccbb5..4fc03f14 100644
--- a/src/pages/crontab/logModal.tsx
+++ b/src/pages/crontab/logModal.tsx
@@ -19,7 +19,6 @@ const CronLogModal = ({
   handleCancel: () => void;
 }) => {
   const [value, setValue] = useState('启动中...');
-  const [logTimer, setLogTimer] = useState();
   const [loading, setLoading] = useState(true);
 
   const getCronLog = (isFirst?: boolean) => {
@@ -31,20 +30,10 @@ const CronLogModal = ({
       .then((data: any) => {
         const log = data.data as string;
         setValue(log || '暂无日志');
-        if (log.includes('执行结束')) {
-          if (logTimer) {
-            clearInterval(logTimer);
-          }
-        } else if (isFirst) {
-          const timer = setInterval(() => {
+        if (log && log.includes('执行结束')) {
+          setTimeout(() => {
             getCronLog();
           }, 2000);
-          setLogTimer(timer);
-        }
-      })
-      .catch(() => {
-        if (logTimer) {
-          clearInterval(logTimer);
         }
       })
       .finally(() => {
@@ -55,7 +44,6 @@ const CronLogModal = ({
   };
 
   const cancel = () => {
-    clearInterval(logTimer);
     handleCancel();
   };
 
@@ -74,7 +62,7 @@ const CronLogModal = ({
       onCancel={() => cancel()}
     >
       
-        {value}
+        {!loading && value}
       
); From 2217f200dca79fbca1bb18828f4183a428efa3cd Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 11 Apr 2021 11:28:46 +0800 Subject: [PATCH 07/13] =?UTF-8?q?=E6=8A=BD=E7=A6=BB=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=EF=BC=8C=E5=8F=AA=E5=86=8Drebuild=E6=97=B6?= =?UTF-8?q?=E5=AE=89=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 26 +++++++++++++------------- shell/git_pull.sh | 6 +++--- shell/rebuild.sh | 1 + 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 944ee9a1..400d144a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build-back": "tsc -p tsconfig.back.json", "start-back": "nodemon", "pm2": "npm run build-back && node build/app.js", - "postinstall": "umi generate tmp", + "prepare": "umi generate tmp", "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", "test": "umi-test", "test:coverage": "umi-test --coverage" @@ -23,14 +23,10 @@ ] }, "dependencies": { - "@ant-design/pro-layout": "^6.5.0", - "@umijs/plugin-antd": "^0.9.1", "body-parser": "^1.19.0", "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", @@ -38,18 +34,22 @@ "jsonwebtoken": "^8.5.1", "nedb": "^1.8.0", "node-fetch": "^2.6.1", - "qrcode.react": "^1.0.1", + "reflect-metadata": "^0.1.13", + "typedi": "^0.8.0", + "winston": "^3.3.3" + }, + "devDependencies": { + "umi": "^3.3.9", + "umi-request": "^1.3.5", "react-codemirror2": "^7.2.1", "react-diff-viewer": "^3.1.1", "react-dnd": "^14.0.2", "react-dnd-html5-backend": "^14.0.0", - "reflect-metadata": "^0.1.13", - "typedi": "^0.8.0", - "umi": "^3.3.9", - "umi-request": "^1.3.5", - "winston": "^3.3.3" - }, - "devDependencies": { + "qrcode.react": "^1.0.1", + "darkreader": "^4.9.27", + "codemirror": "^5.59.4", + "@ant-design/pro-layout": "^6.5.0", + "@umijs/plugin-antd": "^0.9.1", "@types/cors": "^2.8.10", "@types/express": "^4.17.8", "@types/express-jwt": "^6.0.1", diff --git a/shell/git_pull.sh b/shell/git_pull.sh index eb91c93e..e445694b 100755 --- a/shell/git_pull.sh +++ b/shell/git_pull.sh @@ -100,12 +100,12 @@ Npm_Install() { ## npm install 子程序,判断是否为安卓,判断是否安装有yarn function Npm_InstallSub() { if [ -n "$isTermux" ]; then - npm install --no-save --no-bin-links --registry=https://registry.npm.taobao.org || npm install --no-bin-links --no-save + npm install --production --no-save --no-bin-links || npm install --production --no-bin-links --no-save --registry=https://registry.npm.taobao.org elif ! type yarn >/dev/null 2>&1; then - npm install --no-save --registry=https://registry.npm.taobao.org || npm install --no-save + npm install --production --no-save || npm install --production --no-save --registry=https://registry.npm.taobao.org else echo -e "检测到本机安装了 yarn,使用 yarn 替代 npm...\n" - yarn install --registry=https://registry.npm.taobao.org --network-timeout 1000000000 || yarn install + yarn install --production --network-timeout 1000000000 || yarn install --production --registry=https://registry.npm.taobao.org --network-timeout 1000000000 fi } diff --git a/shell/rebuild.sh b/shell/rebuild.sh index 84531eea..42e40958 100755 --- a/shell/rebuild.sh +++ b/shell/rebuild.sh @@ -9,6 +9,7 @@ git pull echo -e "更新shell完成...\n" echo -e "重新build...\n" +yarn install --network-timeout 1000000000 || yarn install --registry=https://registry.npm.taobao.org --network-timeout 1000000000 yarn build yarn build-back echo -e "重新build完成...\n" From d13e1d84dac09c709ce0ee23cbd2bad2b5480076 Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 11 Apr 2021 16:17:30 +0800 Subject: [PATCH 08/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d63ad334..a79d7297 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -72,5 +72,6 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories COPY --from=build /ql/node_modules /ql/node_modules/ RUN cd ${QL_DIR} \ && yarn build \ - && yarn build-back + && yarn build-back \ + && rm -rf node_modules ENTRYPOINT ["entrypoint"] \ No newline at end of file From 39e17db9b1d173b2572b96a775ac7cd53e51bf31 Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 11 Apr 2021 16:31:32 +0800 Subject: [PATCH 09/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a79d7297..0ed133c8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -73,5 +73,5 @@ COPY --from=build /ql/node_modules /ql/node_modules/ RUN cd ${QL_DIR} \ && yarn build \ && yarn build-back \ - && rm -rf node_modules + && yarn install --production --network-timeout 100000 ENTRYPOINT ["entrypoint"] \ No newline at end of file From 5ebb9f419672cd2c23dd3b512d1624b1b1e3fef0 Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 11 Apr 2021 17:35:20 +0800 Subject: [PATCH 10/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 0ed133c8..4d348db1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -73,5 +73,6 @@ COPY --from=build /ql/node_modules /ql/node_modules/ RUN cd ${QL_DIR} \ && yarn build \ && yarn build-back \ - && yarn install --production --network-timeout 100000 + && yarn install --production --network-timeout 100000 \ + && yarn cache clean ENTRYPOINT ["entrypoint"] \ No newline at end of file From 688fdbff2a902cf8113b6d2a8082abaea631b59a Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 11 Apr 2021 18:18:58 +0800 Subject: [PATCH 11/13] =?UTF-8?q?=E5=A4=A7=E5=B9=85=E7=BC=A9=E5=87=8Fdocke?= =?UTF-8?q?r=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/Dockerfile | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 4d348db1..aabae0ba 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,26 +1,3 @@ -FROM node:lts-alpine as build -LABEL maintainer="whyour" -ARG QL_BASE_URL=https://github.com.cnpmjs.org/whyour/qinglong -ARG QL_BASE_BRANCH=master -ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ - LANG=zh_CN.UTF-8 \ - SHELL=/bin/bash \ - PS1="\u@\h:\w \$ " \ - QL_DIR=/ql -RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ - && apk update -f \ - && apk upgrade \ - && apk --no-cache add -f coreutils \ - moreutils \ - git \ - python \ - make \ - g++ \ - yarn \ - && git clone -b ${QL_BASE_BRANCH} ${QL_BASE_URL} ${QL_DIR} \ - && cd ${QL_DIR} \ - && cp -f .env.example .env \ - && yarn --network-timeout 100000 FROM node:lts-alpine LABEL maintainer="whyour" ARG QL_BASE_URL=https://github.com.cnpmjs.org/whyour/qinglong @@ -45,9 +22,7 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories perl \ openssl \ nginx \ - python \ - make \ - g++ \ + python3 \ yarn \ && rm -rf /var/cache/apk/* \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ @@ -68,9 +43,8 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && chmod 777 ${QL_DIR}/shell/*.sh \ && chmod 777 ${QL_DIR}/docker/*.sh \ && npm install -g pm2 \ - && rm -rf /root/.npm -COPY --from=build /ql/node_modules /ql/node_modules/ -RUN cd ${QL_DIR} \ + && rm -rf /root/.npm \ + && yarn install --network-timeout 100000 \ && yarn build \ && yarn build-back \ && yarn install --production --network-timeout 100000 \ From f706b1aa2e6bd3bfb4ce05b6337f6c7cb68e49fe Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 11 Apr 2021 18:32:45 +0800 Subject: [PATCH 12/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcron=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/crontab/logModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/crontab/logModal.tsx b/src/pages/crontab/logModal.tsx index 4fc03f14..17fbc423 100644 --- a/src/pages/crontab/logModal.tsx +++ b/src/pages/crontab/logModal.tsx @@ -30,7 +30,7 @@ const CronLogModal = ({ .then((data: any) => { const log = data.data as string; setValue(log || '暂无日志'); - if (log && log.includes('执行结束')) { + if (log && !log.includes('执行结束')) { setTimeout(() => { getCronLog(); }, 2000); From 17c50d0c9e8e998342edd34c83df6b2e3c413b0d Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 11 Apr 2021 18:51:31 +0800 Subject: [PATCH 13/13] =?UTF-8?q?=E4=BD=BF=E7=94=A8jq=E8=8E=B7=E5=8F=96jso?= =?UTF-8?q?n=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/Dockerfile | 1 + shell/api.sh | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index aabae0ba..b759760d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,6 +24,7 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories nginx \ python3 \ yarn \ + jq \ && rm -rf /var/cache/apk/* \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ diff --git a/shell/api.sh b/shell/api.sh index 1627468e..b633ff40 100755 --- a/shell/api.sh +++ b/shell/api.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash get_token() { - local authInfo=$(cat $AuthConf) - token=$(get_json_value "$authInfo" "token") + token=$(cat $AuthConf | jq -r .token) } get_json_value() { @@ -42,8 +41,7 @@ add_cron_api() { -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") + code=$(echo $api | jq -r .code) if [[ $code == 200 ]]; then echo -e "$name 添加成功" else @@ -63,8 +61,7 @@ del_cron_api() { -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") + code=$(echo $api | jq -r .code) if [[ $code == 200 ]]; then echo -e "$name 删除成功" else