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..689e6abc 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,171 @@ 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; + }); + const docs = await this.insert(tabs); + await this.set_cookies(); + return docs; + } + + 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 }); + 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) { + 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 + : cookies[toIndex].position / 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..5ea4dcfc 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) { @@ -90,10 +121,10 @@ 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')) { + if (res.command.startsWith('js') && !res.command.endsWith('now')) { cmdStr = `${res.command} now`; } else if (/&& (.*) >>/.test(res.command)) { cmdStr = res.command.match(/&& (.*) >>/)[1]; @@ -121,6 +152,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/docker/Dockerfile b/docker/Dockerfile index 8a8ee020..fe073563 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,25 +1,3 @@ -FROM node:lts-alpine as build -LABEL maintainer="whyour" -ARG QL_URL=https://github.com.cnpmjs.org/whyour/qinglong -ARG QL_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++ \ - && git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \ - && cd ${QL_DIR} \ - && cp -f .env.example .env \ - && yarn --network-timeout 100000 FROM node:lts-alpine LABEL maintainer="whyour" ARG QL_URL=https://github.com.cnpmjs.org/whyour/qinglong @@ -44,9 +22,8 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories perl \ openssl \ nginx \ - python \ - make \ - g++ \ + python3 \ + jq \ && rm -rf /var/cache/apk/* \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ @@ -65,9 +42,10 @@ 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 build-back \ + && yarn install --production --network-timeout 100000 \ + && yarn cache clean ENTRYPOINT ["entrypoint"] \ No newline at end of file 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; diff --git a/package.json b/package.json index 29ae1c8a..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,16 +34,22 @@ "jsonwebtoken": "^8.5.1", "nedb": "^1.8.0", "node-fetch": "^2.6.1", - "qrcode.react": "^1.0.1", - "react-codemirror2": "^7.2.1", - "react-diff-viewer": "^3.1.1", "reflect-metadata": "^0.1.13", "typedi": "^0.8.0", - "umi": "^3.3.9", - "umi-request": "^1.3.5", "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", + "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/api.sh b/shell/api.sh index 6c8d1254..d118a0ac 100644 --- a/shell/api.sh +++ b/shell/api.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash get_token() { - local auth_info=$(cat $file_auth_user) - token=$(get_json_value "$auth_info" "token") + token=$(cat $file_auth_user | jq -r .token) } get_json_value() { @@ -32,42 +31,40 @@ add_cron_api() { local name=$3 fi - local api=$(curl "http://localhost:5600/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 + local api=$(curl "http://localhost:5600/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) + code=$(echo $api | jq -r .code) + if [[ $code == 200 ]]; then + echo -e "$name 添加成功" + else + echo -e "$name 添加失败" + fi } del_cron_api() { - local id=$1 - local currentTimeStamp=$(date +%s) - local api=$(curl "http://localhost:5600/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 + local id=$1 + local currentTimeStamp=$(date +%s) + local api=$(curl "http://localhost:5600/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") + code=$(echo $api | jq -r .code) + if [[ $code == 200 ]]; then + echo -e "$name 删除成功" + else + echo -e "$name 删除失败" + fi } diff --git a/shell/rebuild.sh b/shell/rebuild.sh index d277ede6..24c1c233 100644 --- a/shell/rebuild.sh +++ b/shell/rebuild.sh @@ -10,6 +10,7 @@ git pull echo -e "更新更新qinglong完成...\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" 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..8ace424c 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,120 +212,57 @@ 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 addCookie = () => { - setEditedCookie(null as any); - setIsModalVisible(true); - }; - - const editCookie = (record: any, index: number) => { - setEditedCookie(record.cookie); - setIsModalVisible(true); - }; - - const deleteCookie = (record: any, index: number) => { + const enabledOrDisabledCron = (record: any, index: number) => { Modal.confirm({ - title: '确认删除', + title: `确认${record.status === Status.已禁用 ? '启用' : '禁用'}`, content: ( <> - 确认删除Cookie {record.cookie} 吗 + 确认{record.status === Status.已禁用 ? '启用' : '禁用'} + Cookie{' '} + + {record.value} + {' '} + 吗 ), onOk() { request - .delete(`${config.apiPrefix}cookie`, { - data: { cookie: record.cookie }, - }) + .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: '删除成功', + message: `${ + record.status === Status.已禁用 ? '启用' : '禁用' + }成功`, }); - getCookies(); + const newStatus = + record.status === Status.已禁用 ? Status.未获取 : Status.已禁用; + const result = [...value]; + result.splice(index, 1, { + ...record, + status: newStatus, + }); + setValue(result); } else { notification.error({ message: data, @@ -237,13 +276,103 @@ const Config = () => { }); }; - const handleCancel = (needUpdate?: boolean) => { + const addCookie = () => { + setEditedCookie(null as any); + setIsModalVisible(true); + }; + + const editCookie = (record: any, index: number) => { + setEditedCookie(record); + setIsModalVisible(true); + }; + + const deleteCookie = (record: any, index: number) => { + Modal.confirm({ + title: '确认删除', + content: ( + <> + 确认删除Cookie{' '} + + {record.value} + {' '} + 吗 + + ), + onOk() { + request + .delete(`${config.apiPrefix}cookies/${record._id}`) + .then((data: any) => { + if (data.code === 200) { + notification.success({ + message: '删除成功', + }); + const result = [...value]; + result.splice(index, 1); + setValue(result); + } else { + notification.error({ + message: data, + }); + } + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + const handleCancel = (cookie: any) => { setIsModalVisible(false); - if (needUpdate) { - getCookies(); + if (cookie) { + handleCookies(cookie); } }; + 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 = { + body: { + row: DragableBodyRow, + }, + }; + + const moveRow = useCallback( + (dragIndex, hoverIndex) => { + if (dragIndex === hoverIndex) { + return; + } + 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 +412,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) { @@ -45,15 +43,11 @@ const CookieModal = ({ message: data, }); } - handleCancel(true); + handleCancel(data); }; useEffect(() => { - if (cookie) { - form.setFieldsValue({ cookie }); - } else { - form.resetFields(); - } + form.resetFields(); }, [cookie]); return ( @@ -74,9 +68,15 @@ const CookieModal = ({ onCancel={() => handleCancel()} destroyOnClose > -
+ { { - setLogCron(record); - setIsLogModalVisible(true); + setLogCron({ ...record, timestamp: Date.now() }); }} > @@ -169,7 +168,11 @@ const Crontab = () => { title: '确认删除', content: ( <> - 确认删除定时任务 {record.name} 吗 + 确认删除定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -201,7 +204,11 @@ const Crontab = () => { title: '确认运行', content: ( <> - 确认运行定时任务 {record.name} 吗 + 确认运行定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -236,7 +243,11 @@ const Crontab = () => { content: ( <> 确认{record.status === CrontabStatus.disabled ? '启用' : '禁用'} - 定时任务 {record.name} 吗 + 定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -335,10 +346,10 @@ const Crontab = () => { } }; - const handleCancel = (needUpdate?: boolean) => { + const handleCancel = (cron?: any) => { setIsModalVisible(false); - if (needUpdate) { - getCronDetail(editedCron); + if (cron) { + handleCrons(cron); } }; @@ -346,6 +357,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}`) @@ -361,6 +385,12 @@ const Crontab = () => { .finally(() => setLoading(false)); }; + useEffect(() => { + if (logCron) { + setIsLogModalVisible(true); + } + }, [logCron]); + useEffect(() => { getCrons(); }, [searchText]); @@ -415,7 +445,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..17fbc423 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,28 +18,38 @@ const CronLogModal = ({ visible: boolean; handleCancel: () => void; }) => { - const [value, setValue] = useState('运行中...'); - const [logTimer, setLogTimer] = useState(); + const [value, setValue] = 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 && !log.includes('执行结束')) { + setTimeout(() => { + getCronLog(); + }, 2000); + } + }) + .finally(() => { + if (isFirst) { + setLoading(false); + } }); }; const cancel = () => { - clearInterval(logTimer); handleCancel(); }; useEffect(() => { if (cron) { - const timer = setInterval(() => { - getCronLog(); - }, 2000); - setLogTimer(timer); + getCronLog(true); } }, [cron]); @@ -44,10 +60,9 @@ const CronLogModal = ({ forceRender onOk={() => cancel()} onCancel={() => cancel()} - destroyOnClose >
-        {value}
+        {!loading && value}
       
); 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(() => { 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 }); }