diff --git a/back/api/subscription.ts b/back/api/subscription.ts index 4a5dc00a..60996d09 100644 --- a/back/api/subscription.ts +++ b/back/api/subscription.ts @@ -28,7 +28,8 @@ export default (app: Router) => { celebrate({ body: Joi.object({ type: Joi.string().required(), - schedule: Joi.string().required(), + schedule: Joi.string().optional(), + intervalSchedule: Joi.object().optional(), name: Joi.string().optional(), url: Joi.string().required(), whitelist: Joi.string().optional(), @@ -38,8 +39,8 @@ export default (app: Router) => { status: Joi.number().optional(), pull_type: Joi.string().optional(), pull_option: Joi.object().optional(), - schedule_type: Joi.number().optional(), - alias: Joi.number().required(), + schedule_type: Joi.string().required(), + alias: Joi.string().required(), }), }), async (req: Request, res: Response, next: NextFunction) => { @@ -156,7 +157,8 @@ export default (app: Router) => { celebrate({ body: Joi.object({ type: Joi.string().required(), - schedule: Joi.string().required(), + schedule: Joi.string().optional(), + intervalSchedule: Joi.object().optional(), name: Joi.string().optional(), url: Joi.string().required(), whitelist: Joi.string().optional(), @@ -166,8 +168,8 @@ export default (app: Router) => { status: Joi.number().optional(), pull_type: Joi.string().optional(), pull_option: Joi.object().optional(), - schedule_type: Joi.number().optional(), - alias: Joi.number().required(), + schedule_type: Joi.string().optional(), + alias: Joi.string().required(), id: Joi.number().required(), }), }), @@ -176,6 +178,7 @@ export default (app: Router) => { try { if ( !req.body.schedule || + typeof req.body.schedule === 'object' || cron_parser.parseExpression(req.body.schedule).hasNext() ) { const subscriptionService = Container.get(SubscriptionService); diff --git a/back/data/subscription.ts b/back/data/subscription.ts index 230ece4f..20a9ac8a 100644 --- a/back/data/subscription.ts +++ b/back/data/subscription.ts @@ -7,7 +7,8 @@ export class Subscription { name?: string; type?: 'public-repo' | 'private-repo' | 'file'; schedule_type?: 'crontab' | 'interval'; - schedule?: string | SimpleIntervalSchedule; + schedule?: string; + intervalSchedule?: SimpleIntervalSchedule; url?: string; whitelist?: string; blacklist?: string; @@ -22,6 +23,7 @@ export class Subscription { isDisabled?: 1 | 0; log_path?: string; alias: string; + command?: string; constructor(options: Subscription) { this.id = options.id; @@ -41,6 +43,7 @@ export class Subscription { this.log_path = options.log_path; this.schedule_type = options.schedule_type; this.alias = options.alias; + this.intervalSchedule = options.intervalSchedule; } } @@ -69,6 +72,11 @@ export const SubscriptionModel = sequelize.define( unique: 'compositeIndex', type: DataTypes.STRING, }, + intervalSchedule: { + unique: 'compositeIndex', + type: DataTypes.JSON, + }, + type: DataTypes.STRING, whitelist: DataTypes.STRING, blacklist: DataTypes.STRING, status: DataTypes.NUMBER, diff --git a/back/services/schedule.ts b/back/services/schedule.ts index 96253b2f..c01a6aca 100644 --- a/back/services/schedule.ts +++ b/back/services/schedule.ts @@ -1,7 +1,6 @@ import { Service, Inject } from 'typedi'; import winston from 'winston'; import nodeSchedule from 'node-schedule'; -import { Crontab } from '../data/cron'; import { exec } from 'child_process'; import { ToadScheduler, @@ -10,6 +9,13 @@ import { SimpleIntervalSchedule, } from 'toad-scheduler'; +interface ScheduleTaskType { + id: number; + command: string; + name?: string; + schedule?: string; +} + @Service() export default class ScheduleService { private scheduleStacks = new Map(); @@ -20,7 +26,12 @@ export default class ScheduleService { constructor(@Inject('logger') private logger: winston.Logger) {} - async createCronTask({ id = 0, command, name, schedule = '' }: Crontab) { + async createCronTask({ + id = 0, + command, + name, + schedule = '', + }: ScheduleTaskType) { const _id = this.formatId(id); this.logger.info( '[创建cron任务],任务ID: %s,cron: %s,任务名: %s,执行命令: %s', @@ -32,7 +43,7 @@ export default class ScheduleService { this.scheduleStacks.set( _id, - nodeSchedule.scheduleJob(id + '', schedule, async () => { + nodeSchedule.scheduleJob(_id, schedule, async () => { try { exec( command, @@ -70,15 +81,16 @@ export default class ScheduleService { ); } - async cancelCronTask({ id = 0, name }: Crontab) { + async cancelCronTask({ id = 0, name }: ScheduleTaskType) { const _id = this.formatId(id); this.logger.info('[取消定时任务],任务名:%s', name); this.scheduleStacks.has(_id) && this.scheduleStacks.get(_id)?.cancel(); } async createIntervalTask( - { id = 0, command, name = '' }: Crontab, + { id = 0, command, name = '' }: ScheduleTaskType, schedule: SimpleIntervalSchedule, + runImmediately = true, ) { const _id = this.formatId(id); this.logger.info( @@ -131,12 +143,12 @@ export default class ScheduleService { }, ); - const job = new LongIntervalJob({ ...schedule }, task, _id); + const job = new LongIntervalJob({ ...schedule, runImmediately }, task, _id); this.intervalSchedule.addIntervalJob(job); } - async cancelIntervalTask({ id = 0, name }: Crontab) { + async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) { const _id = this.formatId(id); this.logger.info('[取消interval任务],任务ID: %s,任务名:%s', _id, name); this.intervalSchedule.removeById(_id); diff --git a/back/services/subscription.ts b/back/services/subscription.ts index 58062ff8..b32f5b43 100644 --- a/back/services/subscription.ts +++ b/back/services/subscription.ts @@ -14,10 +14,15 @@ import { promises, existsSync } from 'fs'; import { promisify } from 'util'; import { Op } from 'sequelize'; import path from 'path'; +import ScheduleService from './schedule'; +import { SimpleIntervalSchedule } from 'toad-scheduler'; @Service() export default class SubscriptionService { - constructor(@Inject('logger') private logger: winston.Logger) {} + constructor( + @Inject('logger') private logger: winston.Logger, + private scheduleService: ScheduleService, + ) {} public async list(searchText?: string): Promise { let query = {}; @@ -75,9 +80,38 @@ export default class SubscriptionService { } } + private formatCommand(doc: Subscription) { + let command = 'ql '; + const { type, url, whitelist, blacklist, dependences, branch } = doc; + if (type === 'file') { + command += `raw ${url}`; + } else { + command += `repo ${url} ${whitelist || ''} ${blacklist || ''} ${ + dependences || '' + } ${branch || ''}`; + } + return command; + } + + private handleTask(doc: Subscription, needCreate = true) { + doc.command = this.formatCommand(doc); + if (doc.schedule_type === 'crontab') { + this.scheduleService.cancelCronTask(doc as any); + needCreate && this.scheduleService.createCronTask(doc as any); + } else { + this.scheduleService.cancelIntervalTask(doc as any); + needCreate && + this.scheduleService.createIntervalTask( + doc as any, + doc.intervalSchedule as SimpleIntervalSchedule, + ); + } + } + public async create(payload: Subscription): Promise { const tab = new Subscription(payload); const doc = await this.insert(tab); + this.handleTask(doc); return doc; } @@ -87,6 +121,7 @@ export default class SubscriptionService { public async update(payload: Subscription): Promise { const newDoc = await this.updateDb(payload); + this.handleTask(newDoc); return newDoc; } @@ -156,7 +191,9 @@ export default class SubscriptionService { this.logger.silly(error); } } - const err = await this.killTask(''); + this.handleTask(doc, false); + const command = this.formatCommand(doc); + const err = await this.killTask(command); const absolutePath = path.resolve(config.logPath, `${doc.log_path}`); const logFileExist = doc.log_path && (await fileExist(absolutePath)); if (logFileExist) { @@ -219,17 +256,16 @@ export default class SubscriptionService { return; } - let { id, log_path } = cron; + let { id, log_path, name } = cron; + const command = this.formatCommand(cron); const absolutePath = path.resolve(config.logPath, `${log_path}`); const logFileExist = log_path && (await fileExist(absolutePath)); - this.logger.silly('Running job'); + this.logger.silly('Running job' + name); this.logger.silly('ID: ' + id); - this.logger.silly('Original command: '); + this.logger.silly('Original command: ' + command); - let cmdStr = ''; - - const cp = spawn(cmdStr, { shell: '/bin/bash' }); + const cp = spawn(command, { shell: '/bin/bash' }); await SubscriptionModel.update( { status: SubscriptionStatus.running, pid: cp.pid }, @@ -266,10 +302,18 @@ export default class SubscriptionService { } public async disabled(ids: number[]) { + const docs = await SubscriptionModel.findAll({ where: { id: ids } }); + for (const doc of docs) { + this.handleTask(doc, false); + } await SubscriptionModel.update({ isDisabled: 1 }, { where: { id: ids } }); } public async enabled(ids: number[]) { + const docs = await SubscriptionModel.findAll({ where: { id: ids } }); + for (const doc of docs) { + this.handleTask(doc); + } await SubscriptionModel.update({ isDisabled: 0 }, { where: { id: ids } }); } diff --git a/src/pages/subscription/modal.tsx b/src/pages/subscription/modal.tsx index 563395d2..cab726cf 100644 --- a/src/pages/subscription/modal.tsx +++ b/src/pages/subscription/modal.tsx @@ -281,7 +281,7 @@ const SubscriptionModal = ({