mirror of
				https://github.com/whyour/qinglong.git
				synced 2025-11-04 02:56:07 +08:00 
			
		
		
		
	* 修复了视图无排序时无法再次修改的问题 * 修复在视图管理中编辑、新建视图点击确定后不能关闭页面的问题 * 修复#1611 避免查询条件被覆盖 * 修复视图筛选不能正确处理`不包含`
		
			
				
	
	
		
			611 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			611 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { Service, Inject } from 'typedi';
 | 
						||
import winston from 'winston';
 | 
						||
import config from '../config';
 | 
						||
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
 | 
						||
import { exec, execSync, spawn } from 'child_process';
 | 
						||
import fs from 'fs';
 | 
						||
import cron_parser from 'cron-parser';
 | 
						||
import { getFileContentByName, concurrentRun, fileExist } from '../config/util';
 | 
						||
import { promises, existsSync } from 'fs';
 | 
						||
import { promisify } from 'util';
 | 
						||
import { Op } from 'sequelize';
 | 
						||
import path from 'path';
 | 
						||
import dayjs from 'dayjs';
 | 
						||
 | 
						||
@Service()
 | 
						||
export default class CronService {
 | 
						||
  constructor(@Inject('logger') private logger: winston.Logger) {}
 | 
						||
 | 
						||
  private isSixCron(cron: Crontab) {
 | 
						||
    const { schedule } = cron;
 | 
						||
    if (schedule?.split(/ +/).length === 6) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  public async create(payload: Crontab): Promise<Crontab> {
 | 
						||
    const tab = new Crontab(payload);
 | 
						||
    tab.saved = false;
 | 
						||
    const doc = await this.insert(tab);
 | 
						||
    await this.set_crontab(this.isSixCron(doc));
 | 
						||
    return doc;
 | 
						||
  }
 | 
						||
 | 
						||
  public async insert(payload: Crontab): Promise<Crontab> {
 | 
						||
    return await CrontabModel.create(payload, { returning: true });
 | 
						||
  }
 | 
						||
 | 
						||
  public async update(payload: Crontab): Promise<Crontab> {
 | 
						||
    payload.saved = false;
 | 
						||
    const newDoc = await this.updateDb(payload);
 | 
						||
    await this.set_crontab(this.isSixCron(newDoc));
 | 
						||
    return newDoc;
 | 
						||
  }
 | 
						||
 | 
						||
  public async updateDb(payload: Crontab): Promise<Crontab> {
 | 
						||
    await CrontabModel.update(payload, { where: { id: payload.id } });
 | 
						||
    return await this.getDb({ id: payload.id });
 | 
						||
  }
 | 
						||
 | 
						||
  public async status({
 | 
						||
    ids,
 | 
						||
    status,
 | 
						||
    pid,
 | 
						||
    log_path,
 | 
						||
    last_running_time = 0,
 | 
						||
    last_execution_time = 0,
 | 
						||
  }: {
 | 
						||
    ids: number[];
 | 
						||
    status: CrontabStatus;
 | 
						||
    pid: number;
 | 
						||
    log_path: string;
 | 
						||
    last_running_time: number;
 | 
						||
    last_execution_time: number;
 | 
						||
  }) {
 | 
						||
    const options: any = {
 | 
						||
      status,
 | 
						||
      pid,
 | 
						||
      log_path,
 | 
						||
      last_execution_time,
 | 
						||
    };
 | 
						||
    if (last_running_time > 0) {
 | 
						||
      options.last_running_time = last_running_time;
 | 
						||
    }
 | 
						||
 | 
						||
    return await CrontabModel.update({ ...options }, { where: { id: ids } });
 | 
						||
  }
 | 
						||
 | 
						||
  public async remove(ids: number[]) {
 | 
						||
    await CrontabModel.destroy({ where: { id: ids } });
 | 
						||
    await this.set_crontab(true);
 | 
						||
  }
 | 
						||
 | 
						||
  public async pin(ids: number[]) {
 | 
						||
    await CrontabModel.update({ isPinned: 1 }, { where: { id: ids } });
 | 
						||
  }
 | 
						||
 | 
						||
  public async unPin(ids: number[]) {
 | 
						||
    await CrontabModel.update({ isPinned: 0 }, { where: { id: ids } });
 | 
						||
  }
 | 
						||
 | 
						||
  public async addLabels(ids: string[], labels: string[]) {
 | 
						||
    const docs = await CrontabModel.findAll({ where: { id: ids } });
 | 
						||
    for (const doc of docs) {
 | 
						||
      await CrontabModel.update(
 | 
						||
        {
 | 
						||
          labels: Array.from(new Set((doc.labels || []).concat(labels))),
 | 
						||
        },
 | 
						||
        { where: { id: doc.id } },
 | 
						||
      );
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  public async removeLabels(ids: string[], labels: string[]) {
 | 
						||
    const docs = await CrontabModel.findAll({ where: { id: ids } });
 | 
						||
    for (const doc of docs) {
 | 
						||
      await CrontabModel.update(
 | 
						||
        {
 | 
						||
          labels: (doc.labels || []).filter((label) => !labels.includes(label)),
 | 
						||
        },
 | 
						||
        { where: { id: doc.id } },
 | 
						||
      );
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  private formatViewQuery(query: any, viewQuery: any) {
 | 
						||
    if (viewQuery.filters && viewQuery.filters.length > 0) {
 | 
						||
      if (!query[Op.and]) {
 | 
						||
        query[Op.and] = [];
 | 
						||
      }
 | 
						||
      for (const col of viewQuery.filters) {
 | 
						||
        const { property, value, operation } = col;
 | 
						||
        let q: any = {};
 | 
						||
        let operate2 = null;
 | 
						||
        let operate = null;
 | 
						||
        switch (operation) {
 | 
						||
          case 'Reg':
 | 
						||
            operate = Op.like;
 | 
						||
            operate2 = Op.or;
 | 
						||
            break;
 | 
						||
          case 'NotReg':
 | 
						||
            operate = Op.notLike;
 | 
						||
            operate2 = Op.and;
 | 
						||
            break;
 | 
						||
          case 'In':
 | 
						||
            q[Op.or] = [
 | 
						||
              {
 | 
						||
                [property]: value,
 | 
						||
              },
 | 
						||
              property === 'status' && value.includes(2)
 | 
						||
                ? { isDisabled: 1 }
 | 
						||
                : {},
 | 
						||
            ];
 | 
						||
            break;
 | 
						||
          case 'Nin':
 | 
						||
            q[Op.and] = [
 | 
						||
              {
 | 
						||
                [property]: {
 | 
						||
                  [Op.notIn]: value,
 | 
						||
                },
 | 
						||
              },
 | 
						||
              property === 'status' && value.includes(2)
 | 
						||
                ? { isDisabled: { [Op.ne]: 1 } }
 | 
						||
                : {},
 | 
						||
            ];
 | 
						||
            break;
 | 
						||
          default:
 | 
						||
            break;
 | 
						||
        }
 | 
						||
        if (operate && operate2) {
 | 
						||
          q[property] = {
 | 
						||
            [operate2]: [
 | 
						||
              { [operate]: `%${value}%` },
 | 
						||
              { [operate]: `%${encodeURIComponent(value)}%` },
 | 
						||
            ],
 | 
						||
          };
 | 
						||
        }
 | 
						||
        query[Op.and].push(q);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  private formatSearchText(query: any, searchText: string | undefined) {
 | 
						||
    if (searchText) {
 | 
						||
      if (!query[Op.and]) {
 | 
						||
        query[Op.and] = [];
 | 
						||
      }
 | 
						||
      let q: any = {};
 | 
						||
      const textArray = searchText.split(':');
 | 
						||
      switch (textArray[0]) {
 | 
						||
        case 'name':
 | 
						||
        case 'command':
 | 
						||
        case 'schedule':
 | 
						||
        case 'label':
 | 
						||
          const column = textArray[0] === 'label' ? 'labels' : textArray[0];
 | 
						||
          q[column] = {
 | 
						||
            [Op.or]: [
 | 
						||
              { [Op.like]: `%${textArray[1]}%` },
 | 
						||
              { [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
 | 
						||
            ],
 | 
						||
          };
 | 
						||
          break;
 | 
						||
        default:
 | 
						||
          const reg = {
 | 
						||
            [Op.or]: [
 | 
						||
              { [Op.like]: `%${searchText}%` },
 | 
						||
              { [Op.like]: `%${encodeURIComponent(searchText)}%` },
 | 
						||
            ],
 | 
						||
          };
 | 
						||
          q[Op.or] = [
 | 
						||
            {
 | 
						||
              name: reg,
 | 
						||
            },
 | 
						||
            {
 | 
						||
              command: reg,
 | 
						||
            },
 | 
						||
            {
 | 
						||
              schedule: reg,
 | 
						||
            },
 | 
						||
            {
 | 
						||
              labels: reg,
 | 
						||
            },
 | 
						||
          ];
 | 
						||
          break;
 | 
						||
      }
 | 
						||
      query[Op.and].push(q);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  private formatViewSort(order: string[][], viewQuery: any) {
 | 
						||
    if (viewQuery.sorts && viewQuery.sorts.length > 0) {
 | 
						||
      for (const { property, type } of viewQuery.sorts) {
 | 
						||
        order.unshift([property, type]);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  public async crontabs(params?: {
 | 
						||
    searchValue: string;
 | 
						||
    page: string;
 | 
						||
    size: string;
 | 
						||
    sortField: string;
 | 
						||
    sortType: string;
 | 
						||
    queryString: string;
 | 
						||
  }): Promise<{ data: Crontab[]; total: number }> {
 | 
						||
    const searchText = params?.searchValue;
 | 
						||
    const page = Number(params?.page || '0');
 | 
						||
    const size = Number(params?.size || '0');
 | 
						||
    const sortField = params?.sortField || '';
 | 
						||
    const sortType = params?.sortType || '';
 | 
						||
    const viewQuery = JSON.parse(params?.queryString || '{}');
 | 
						||
 | 
						||
    let query: any = {};
 | 
						||
    let order = [
 | 
						||
      ['isPinned', 'DESC'],
 | 
						||
      ['isDisabled', 'ASC'],
 | 
						||
      ['status', 'ASC'],
 | 
						||
      ['createdAt', 'DESC'],
 | 
						||
    ];
 | 
						||
 | 
						||
    this.formatViewQuery(query, viewQuery);
 | 
						||
    this.formatSearchText(query, searchText);
 | 
						||
    this.formatViewSort(order, viewQuery);
 | 
						||
 | 
						||
    if (sortType && sortField) {
 | 
						||
      order.unshift([sortField, sortType]);
 | 
						||
    }
 | 
						||
    let condition: any = {
 | 
						||
      where: query,
 | 
						||
      order: order,
 | 
						||
    };
 | 
						||
    if (page && size) {
 | 
						||
      condition.offset = (page - 1) * size;
 | 
						||
      condition.limit = size;
 | 
						||
    }
 | 
						||
    try {
 | 
						||
      const result = await CrontabModel.findAll(condition);
 | 
						||
      const count = await CrontabModel.count({ where: query });
 | 
						||
      return { data: result, total: count };
 | 
						||
    } catch (error) {
 | 
						||
      throw error;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  public async getDb(query: any): Promise<Crontab> {
 | 
						||
    const doc: any = await CrontabModel.findOne({ where: { ...query } });
 | 
						||
    return doc && (doc.get({ plain: true }) as Crontab);
 | 
						||
  }
 | 
						||
 | 
						||
  public async run(ids: number[]) {
 | 
						||
    await CrontabModel.update(
 | 
						||
      { status: CrontabStatus.queued },
 | 
						||
      { where: { id: ids } },
 | 
						||
    );
 | 
						||
    concurrentRun(
 | 
						||
      ids.map((id) => async () => await this.runSingle(id)),
 | 
						||
      10,
 | 
						||
    );
 | 
						||
  }
 | 
						||
 | 
						||
  public async stop(ids: number[]) {
 | 
						||
    const docs = await CrontabModel.findAll({ where: { id: ids } });
 | 
						||
    for (const doc of docs) {
 | 
						||
      if (doc.pid) {
 | 
						||
        try {
 | 
						||
          process.kill(-doc.pid);
 | 
						||
        } catch (error) {
 | 
						||
          this.logger.silly(error);
 | 
						||
        }
 | 
						||
      }
 | 
						||
      const err = await this.killTask(doc.command);
 | 
						||
      const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
 | 
						||
      const logFileExist = doc.log_path && (await fileExist(absolutePath));
 | 
						||
 | 
						||
      const endTime = dayjs();
 | 
						||
      const diffTimeStr = doc.last_execution_time
 | 
						||
        ? `,耗时 ${endTime.diff(
 | 
						||
            dayjs(doc.last_execution_time * 1000),
 | 
						||
            'second',
 | 
						||
          )}`
 | 
						||
        : '';
 | 
						||
      if (logFileExist) {
 | 
						||
        const str = err ? `\n${err}` : '';
 | 
						||
        fs.appendFileSync(
 | 
						||
          `${absolutePath}`,
 | 
						||
          `${str}\n## 执行结束... ${endTime.format(
 | 
						||
            'YYYY-MM-DD HH:mm:ss',
 | 
						||
          )}${diffTimeStr}`,
 | 
						||
        );
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    await CrontabModel.update(
 | 
						||
      { status: CrontabStatus.idle, pid: undefined },
 | 
						||
      { where: { id: ids } },
 | 
						||
    );
 | 
						||
  }
 | 
						||
 | 
						||
  public async killTask(name: string) {
 | 
						||
    let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
 | 
						||
    const execAsync = promisify(exec);
 | 
						||
    try {
 | 
						||
      let pid = (await execAsync(taskCommand)).stdout;
 | 
						||
      if (pid) {
 | 
						||
        pid = (await execAsync(`pstree -p ${pid}`)).stdout;
 | 
						||
      } else {
 | 
						||
        return;
 | 
						||
      }
 | 
						||
      let pids = pid.match(/\(\d+/g);
 | 
						||
      const killLogs = [];
 | 
						||
      if (pids && pids.length > 0) {
 | 
						||
        // node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
 | 
						||
        pids = pids.slice(0, 3);
 | 
						||
        for (const id of pids) {
 | 
						||
          const c = `kill -9 ${id.slice(1)}`;
 | 
						||
          try {
 | 
						||
            const { stdout, stderr } = await execAsync(c);
 | 
						||
            if (stderr) {
 | 
						||
              killLogs.push(stderr);
 | 
						||
            }
 | 
						||
            if (stdout) {
 | 
						||
              killLogs.push(stdout);
 | 
						||
            }
 | 
						||
          } catch (error: any) {
 | 
						||
            killLogs.push(error.message);
 | 
						||
          }
 | 
						||
        }
 | 
						||
      }
 | 
						||
      return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
 | 
						||
    } catch (e) {
 | 
						||
      return JSON.stringify(e);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  private async runSingle(cronId: number): Promise<number> {
 | 
						||
    return new Promise(async (resolve: any) => {
 | 
						||
      const cron = await this.getDb({ id: cronId });
 | 
						||
      if (cron.status !== CrontabStatus.queued) {
 | 
						||
        resolve();
 | 
						||
        return;
 | 
						||
      }
 | 
						||
 | 
						||
      let { id, command, log_path } = cron;
 | 
						||
      const absolutePath = path.resolve(config.logPath, `${log_path}`);
 | 
						||
      const logFileExist = log_path && (await fileExist(absolutePath));
 | 
						||
 | 
						||
      this.logger.silly('Running job');
 | 
						||
      this.logger.silly('ID: ' + id);
 | 
						||
      this.logger.silly('Original command: ' + command);
 | 
						||
 | 
						||
      let cmdStr = command;
 | 
						||
      if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
 | 
						||
        cmdStr = `task ${cmdStr}`;
 | 
						||
      }
 | 
						||
      if (
 | 
						||
        cmdStr.endsWith('.js') ||
 | 
						||
        cmdStr.endsWith('.py') ||
 | 
						||
        cmdStr.endsWith('.pyc') ||
 | 
						||
        cmdStr.endsWith('.sh') ||
 | 
						||
        cmdStr.endsWith('.ts')
 | 
						||
      ) {
 | 
						||
        cmdStr = `${cmdStr} now`;
 | 
						||
      }
 | 
						||
 | 
						||
      const cp = spawn(cmdStr, { shell: '/bin/bash' });
 | 
						||
 | 
						||
      await CrontabModel.update(
 | 
						||
        { status: CrontabStatus.running, pid: cp.pid },
 | 
						||
        { where: { id } },
 | 
						||
      );
 | 
						||
      cp.stderr.on('data', (data) => {
 | 
						||
        if (logFileExist) {
 | 
						||
          fs.appendFileSync(`${absolutePath}`, `${data}`);
 | 
						||
        }
 | 
						||
      });
 | 
						||
      cp.on('error', (err) => {
 | 
						||
        if (logFileExist) {
 | 
						||
          fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
 | 
						||
        }
 | 
						||
      });
 | 
						||
 | 
						||
      cp.on('exit', async (code, signal) => {
 | 
						||
        this.logger.info(
 | 
						||
          `任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
 | 
						||
        );
 | 
						||
      });
 | 
						||
      cp.on('close', async (code) => {
 | 
						||
        await CrontabModel.update(
 | 
						||
          { status: CrontabStatus.idle, pid: undefined },
 | 
						||
          { where: { id } },
 | 
						||
        );
 | 
						||
        resolve();
 | 
						||
      });
 | 
						||
    });
 | 
						||
  }
 | 
						||
 | 
						||
  public async disabled(ids: number[]) {
 | 
						||
    await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
 | 
						||
    await this.set_crontab(true);
 | 
						||
  }
 | 
						||
 | 
						||
  public async enabled(ids: number[]) {
 | 
						||
    await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
 | 
						||
    await this.set_crontab(true);
 | 
						||
  }
 | 
						||
 | 
						||
  public async log(id: number) {
 | 
						||
    const doc = await this.getDb({ id });
 | 
						||
    if (!doc) {
 | 
						||
      return '';
 | 
						||
    }
 | 
						||
 | 
						||
    const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
 | 
						||
    const logFileExist = doc.log_path && (await fileExist(absolutePath));
 | 
						||
    if (logFileExist) {
 | 
						||
      return getFileContentByName(`${absolutePath}`);
 | 
						||
    }
 | 
						||
    const [, commandStr, url] = doc.command.split(/ +/);
 | 
						||
    let logPath = this.getKey(commandStr);
 | 
						||
    const isQlCommand = doc.command.startsWith('ql ');
 | 
						||
    const key =
 | 
						||
      (url && ['repo', 'raw'].includes(commandStr) && this.getKey(url)) ||
 | 
						||
      logPath;
 | 
						||
    if (isQlCommand) {
 | 
						||
      logPath = 'update';
 | 
						||
    }
 | 
						||
    let logDir = `${config.logPath}${logPath}`;
 | 
						||
    if (existsSync(logDir)) {
 | 
						||
      let files = await promises.readdir(logDir);
 | 
						||
      if (isQlCommand) {
 | 
						||
        files = files.filter((x) => x.includes(key));
 | 
						||
      }
 | 
						||
      return getFileContentByName(`${logDir}/${files[files.length - 1]}`);
 | 
						||
    } else {
 | 
						||
      return '';
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  public async logs(id: number) {
 | 
						||
    const doc = await this.getDb({ id });
 | 
						||
    if (!doc) {
 | 
						||
      return [];
 | 
						||
    }
 | 
						||
 | 
						||
    if (doc.log_path) {
 | 
						||
      const relativeDir = path.dirname(`${doc.log_path}`);
 | 
						||
      const dir = path.resolve(config.logPath, relativeDir);
 | 
						||
      if (existsSync(dir)) {
 | 
						||
        let files = await promises.readdir(dir);
 | 
						||
        return files
 | 
						||
          .map((x) => ({
 | 
						||
            filename: x,
 | 
						||
            directory: relativeDir.replace(config.logPath, ''),
 | 
						||
            time: fs.statSync(`${dir}/${x}`).mtime.getTime(),
 | 
						||
          }))
 | 
						||
          .sort((a, b) => b.time - a.time);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    const [, commandStr, url] = doc.command.split(/ +/);
 | 
						||
    let logPath = this.getKey(commandStr);
 | 
						||
    const isQlCommand = doc.command.startsWith('ql ');
 | 
						||
    const key =
 | 
						||
      (url && ['repo', 'raw'].includes(commandStr) && this.getKey(url)) ||
 | 
						||
      logPath;
 | 
						||
    if (isQlCommand) {
 | 
						||
      logPath = 'update';
 | 
						||
    }
 | 
						||
    let logDir = `${config.logPath}${logPath}`;
 | 
						||
    if (existsSync(logDir)) {
 | 
						||
      let files = await promises.readdir(logDir);
 | 
						||
      if (isQlCommand) {
 | 
						||
        files = files.filter((x) => x.includes(key));
 | 
						||
      }
 | 
						||
      return files
 | 
						||
        .map((x) => ({
 | 
						||
          filename: x,
 | 
						||
          directory: logPath,
 | 
						||
          time: fs.statSync(`${logDir}/${x}`).mtime.getTime(),
 | 
						||
        }))
 | 
						||
        .sort((a, b) => b.time - a.time);
 | 
						||
    } else {
 | 
						||
      return [];
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  private getKey(command: string): string {
 | 
						||
    const start =
 | 
						||
      command.lastIndexOf('/') !== -1 ? command.lastIndexOf('/') + 1 : 0;
 | 
						||
    const end =
 | 
						||
      command.lastIndexOf('.') !== -1
 | 
						||
        ? command.lastIndexOf('.')
 | 
						||
        : command.length;
 | 
						||
 | 
						||
    const tmpStr = command.substring(0, start - 1);
 | 
						||
    let index = 0;
 | 
						||
    if (tmpStr.lastIndexOf('/') !== -1 && tmpStr.startsWith('http')) {
 | 
						||
      index = tmpStr.lastIndexOf('/');
 | 
						||
    } else if (tmpStr.lastIndexOf(':') !== -1 && tmpStr.startsWith('git@')) {
 | 
						||
      index = tmpStr.lastIndexOf(':');
 | 
						||
    }
 | 
						||
    if (index) {
 | 
						||
      return `${tmpStr.substring(index + 1)}_${command.substring(start, end)}`;
 | 
						||
    } else {
 | 
						||
      return command.substring(start, end);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  private make_command(tab: Crontab) {
 | 
						||
    const crontab_job_string = `ID=${tab.id} ${tab.command}`;
 | 
						||
    return crontab_job_string;
 | 
						||
  }
 | 
						||
 | 
						||
  private async set_crontab(needReloadSchedule: boolean = false) {
 | 
						||
    const tabs = await this.crontabs();
 | 
						||
    var crontab_string = '';
 | 
						||
    tabs.data.forEach((tab) => {
 | 
						||
      const _schedule = tab.schedule && tab.schedule.split(/ +/);
 | 
						||
      if (tab.isDisabled === 1 || _schedule!.length !== 5) {
 | 
						||
        crontab_string += '# ';
 | 
						||
        crontab_string += tab.schedule;
 | 
						||
        crontab_string += ' ';
 | 
						||
        crontab_string += this.make_command(tab);
 | 
						||
        crontab_string += '\n';
 | 
						||
      } else {
 | 
						||
        crontab_string += tab.schedule;
 | 
						||
        crontab_string += ' ';
 | 
						||
        crontab_string += this.make_command(tab);
 | 
						||
        crontab_string += '\n';
 | 
						||
      }
 | 
						||
    });
 | 
						||
 | 
						||
    this.logger.silly(crontab_string);
 | 
						||
    fs.writeFileSync(config.crontabFile, crontab_string);
 | 
						||
 | 
						||
    execSync(`crontab ${config.crontabFile}`);
 | 
						||
    if (needReloadSchedule) {
 | 
						||
      exec(`pm2 reload schedule`);
 | 
						||
    }
 | 
						||
    await CrontabModel.update({ saved: true }, { where: {} });
 | 
						||
  }
 | 
						||
 | 
						||
  public import_crontab() {
 | 
						||
    exec('crontab -l', (error, stdout, stderr) => {
 | 
						||
      const lines = stdout.split('\n');
 | 
						||
      const namePrefix = new Date().getTime();
 | 
						||
 | 
						||
      lines.reverse().forEach(async (line, index) => {
 | 
						||
        line = line.replace(/\t+/g, ' ');
 | 
						||
        const regex =
 | 
						||
          /^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/;
 | 
						||
        const command = line.replace(regex, '').trim();
 | 
						||
        const schedule = line.replace(command, '').trim();
 | 
						||
 | 
						||
        if (
 | 
						||
          command &&
 | 
						||
          schedule &&
 | 
						||
          cron_parser.parseExpression(schedule).hasNext()
 | 
						||
        ) {
 | 
						||
          const name = namePrefix + '_' + index;
 | 
						||
 | 
						||
          const _crontab = await CrontabModel.findOne({
 | 
						||
            where: { command, schedule },
 | 
						||
          });
 | 
						||
          if (!_crontab) {
 | 
						||
            await this.create({ name, command, schedule });
 | 
						||
          } else {
 | 
						||
            _crontab.command = command;
 | 
						||
            _crontab.schedule = schedule;
 | 
						||
            await this.update(_crontab);
 | 
						||
          }
 | 
						||
        }
 | 
						||
      });
 | 
						||
    });
 | 
						||
  }
 | 
						||
 | 
						||
  public autosave_crontab() {
 | 
						||
    return this.set_crontab();
 | 
						||
  }
 | 
						||
}
 |