diff --git a/.github/workflows/build_docker_image.yml b/.github/workflows/build_docker_image.yml index 7e593d8e..bb102199 100644 --- a/.github/workflows/build_docker_image.yml +++ b/.github/workflows/build_docker_image.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: '14' + node-version: '16' - name: build front and back run: | diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..5ca71642 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli \ No newline at end of file diff --git a/back/api/cron.ts b/back/api/cron.ts index 9890d0ce..df804ec3 100644 --- a/back/api/cron.ts +++ b/back/api/cron.ts @@ -51,7 +51,7 @@ export default (app: Router) => { route.put( '/run', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -69,7 +69,7 @@ export default (app: Router) => { route.put( '/stop', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -129,7 +129,7 @@ export default (app: Router) => { route.put( '/disable', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -147,7 +147,7 @@ export default (app: Router) => { route.put( '/enable', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -166,10 +166,10 @@ export default (app: Router) => { '/:id/log', celebrate({ params: Joi.object({ - id: Joi.string().required(), + id: Joi.number().required(), }), }), - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request<{ id: number }>, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); @@ -183,14 +183,14 @@ export default (app: Router) => { ); route.put( - '', + '/', celebrate({ body: Joi.object({ labels: Joi.array().optional(), command: Joi.string().optional(), schedule: Joi.string().optional(), name: Joi.string().optional(), - _id: Joi.string().required(), + id: Joi.string().required(), }), }), async (req: Request, res: Response, next: NextFunction) => { @@ -216,7 +216,7 @@ export default (app: Router) => { route.delete( '/', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -234,7 +234,7 @@ export default (app: Router) => { route.put( '/pin', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -252,7 +252,7 @@ export default (app: Router) => { route.put( '/unpin', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -286,10 +286,10 @@ export default (app: Router) => { '/:id', celebrate({ params: Joi.object({ - id: Joi.string().required(), + id: Joi.number().required(), }), }), - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request<{ id: number }>, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); @@ -306,7 +306,7 @@ export default (app: Router) => { '/status', celebrate({ body: Joi.object({ - ids: Joi.array().items(Joi.string().required()), + ids: Joi.array().items(Joi.number().required()), status: Joi.string().required(), pid: Joi.string().optional(), log_path: Joi.string().optional(), diff --git a/back/api/dependence.ts b/back/api/dependence.ts index 3e805905..22dba7a5 100644 --- a/back/api/dependence.ts +++ b/back/api/dependence.ts @@ -49,7 +49,7 @@ export default (app: Router) => { celebrate({ body: Joi.object({ name: Joi.string().required(), - _id: Joi.string().required(), + id: Joi.string().required(), type: Joi.number().required(), remark: Joi.number().optional().allow(''), }), @@ -70,7 +70,7 @@ export default (app: Router) => { route.delete( '/', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -88,7 +88,7 @@ export default (app: Router) => { route.delete( '/force', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -110,7 +110,7 @@ export default (app: Router) => { id: Joi.string().required(), }), }), - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request<{ id: number }>, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const dependenceService = Container.get(DependenceService); @@ -126,7 +126,7 @@ export default (app: Router) => { route.put( '/reinstall', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); diff --git a/back/api/env.ts b/back/api/env.ts index 9297cf23..12e956d6 100644 --- a/back/api/env.ts +++ b/back/api/env.ts @@ -51,7 +51,7 @@ export default (app: Router) => { value: Joi.string().required(), name: Joi.string().required(), remarks: Joi.string().optional().allow(''), - _id: Joi.string().required(), + id: Joi.string().required(), }), }), async (req: Request, res: Response, next: NextFunction) => { @@ -70,7 +70,7 @@ export default (app: Router) => { route.delete( '/', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -96,7 +96,7 @@ export default (app: Router) => { toIndex: Joi.number().required(), }), }), - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request<{ id: number }>, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const envService = Container.get(EnvService); @@ -112,7 +112,7 @@ export default (app: Router) => { route.put( '/disable', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -130,7 +130,7 @@ export default (app: Router) => { route.put( '/enable', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -149,7 +149,7 @@ export default (app: Router) => { '/name', celebrate({ body: Joi.object({ - ids: Joi.array().items(Joi.string().required()), + ids: Joi.array().items(Joi.number().required()), name: Joi.string().required(), }), }), @@ -173,7 +173,7 @@ export default (app: Router) => { id: Joi.string().required(), }), }), - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request<{ id: number }>, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const envService = Container.get(EnvService); diff --git a/back/api/open.ts b/back/api/open.ts index 69f1bd20..1e09e9d7 100644 --- a/back/api/open.ts +++ b/back/api/open.ts @@ -49,7 +49,7 @@ export default (app: Router) => { body: Joi.object({ name: Joi.string().optional().allow(''), scopes: Joi.array().items(Joi.string()), - _id: Joi.string().required(), + id: Joi.string().required(), }), }), async (req: Request, res: Response, next: NextFunction) => { @@ -68,7 +68,7 @@ export default (app: Router) => { route.delete( '/apps', celebrate({ - body: Joi.array().items(Joi.string().required()), + body: Joi.array().items(Joi.number().required()), }), async (req: Request, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); @@ -90,7 +90,7 @@ export default (app: Router) => { id: Joi.string().required(), }), }), - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request<{ id: number }>, res: Response, next: NextFunction) => { const logger: Logger = Container.get('logger'); try { const openService = Container.get(OpenService); diff --git a/back/api/script.ts b/back/api/script.ts index bd2805bf..619404da 100644 --- a/back/api/script.ts +++ b/back/api/script.ts @@ -266,4 +266,30 @@ export default (app: Router) => { } }, ); + + route.put( + '/stop', + celebrate({ + body: Joi.object({ + filename: Joi.string().required(), + path: Joi.string().optional().allow(''), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + let { filename, path } = req.body as { + filename: string; + path: string; + }; + const filePath = join(path, filename); + const scriptService = Container.get(ScriptService); + const result = await scriptService.stopScript(filePath); + res.send(result); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); }; diff --git a/back/config/util.ts b/back/config/util.ts index 30f04a1e..174ec9ea 100644 --- a/back/config/util.ts +++ b/back/config/util.ts @@ -231,3 +231,40 @@ export async function fileExist(file: any) { } }); } + +export async function concurrentRun( + fnList: Array<() => Promise> = [], + max = 5, +) { + if (!fnList.length) return; + + const replyList: any[] = []; // 收集任务执行结果 + const startTime = new Date().getTime(); // 记录任务执行开始时间 + + // 任务执行程序 + const schedule = async (index: number) => { + return new Promise(async (resolve) => { + const fn = fnList[index]; + if (!fn) return resolve(null); + + // 执行当前异步任务 + const reply = await fn(); + replyList[index] = reply; + + // 执行完当前任务后,继续执行任务池的剩余任务 + await schedule(index + max); + resolve(null); + }); + }; + + // 任务池执行程序 + const scheduleList = new Array(max) + .fill(0) + .map((_, index) => schedule(index)); + + // 使用 Promise.all 批量执行 + const r = await Promise.all(scheduleList); + const cost = (new Date().getTime() - startTime) / 1000; + + return replyList; +} diff --git a/back/data/auth.ts b/back/data/auth.ts index 1b41cb48..e3000f43 100644 --- a/back/data/auth.ts +++ b/back/data/auth.ts @@ -1,14 +1,17 @@ +import { sequelize } from '.'; +import { DataTypes, Model, ModelDefined } from 'sequelize'; + export class AuthInfo { ip?: string; type: AuthDataType; info?: any; - _id?: string; + id?: number; constructor(options: AuthInfo) { this.ip = options.ip; this.info = options.info; this.type = options.type; - this._id = options._id; + this.id = options.id; } } @@ -23,3 +26,13 @@ export enum AuthDataType { 'notification' = 'notification', 'removeLogFrequency' = 'removeLogFrequency', } + +interface AuthInstance extends Model, AuthInfo {} +export const AuthModel = sequelize.define('Auth', { + ip: DataTypes.STRING, + type: DataTypes.STRING, + info: { + type: DataTypes.JSON, + allowNull: true, + }, +}); diff --git a/back/data/cron.ts b/back/data/cron.ts index fa722edc..928e3dfd 100644 --- a/back/data/cron.ts +++ b/back/data/cron.ts @@ -1,11 +1,13 @@ +import { sequelize } from '.'; +import { DataTypes, Model, ModelDefined } from 'sequelize'; + export class Crontab { name?: string; command: string; schedule: string; timestamp?: string; - created?: number; saved?: boolean; - _id?: string; + id?: number; status?: CrontabStatus; isSystem?: 1 | 0; pid?: number; @@ -13,24 +15,28 @@ export class Crontab { log_path?: string; isPinned?: 1 | 0; labels: Array; + last_running_time?: number; + last_execution_time?: number; constructor(options: Crontab) { this.name = options.name; this.command = options.command; this.schedule = options.schedule; this.saved = options.saved; - this._id = options._id; - this.created = options.created; - this.status = CrontabStatus[options.status] - ? options.status - : CrontabStatus.idle; + this.id = options.id; + this.status = + options.status && CrontabStatus[options.status] + ? options.status + : CrontabStatus.idle; this.timestamp = new Date().toString(); this.isSystem = options.isSystem || 0; this.pid = options.pid; this.isDisabled = options.isDisabled || 0; this.log_path = options.log_path || ''; this.isPinned = options.isPinned || 0; - this.labels = options.labels || []; + this.labels = options.labels || ['']; + this.last_running_time = options.last_running_time || 0; + this.last_execution_time = options.last_execution_time || 0; } } @@ -40,3 +46,32 @@ export enum CrontabStatus { 'disabled', 'queued', } + +interface CronInstance extends Model, Crontab {} +export const CrontabModel = sequelize.define('Crontab', { + name: DataTypes.STRING, + command: DataTypes.STRING, + schedule: DataTypes.STRING, + timestamp: DataTypes.STRING, + saved: DataTypes.BOOLEAN, + status: DataTypes.NUMBER, + isSystem: DataTypes.NUMBER, + pid: DataTypes.NUMBER, + isDisabled: DataTypes.NUMBER, + isPinned: DataTypes.NUMBER, + log_path: DataTypes.STRING, + labels: { + type: DataTypes.STRING, + allowNull: false, + get() { + if (this.getDataValue('labels')) { + return this.getDataValue('labels').split(',') + } + }, + set(value) { + this.setDataValue('labels', value.join(',')); + }, + }, + last_running_time: DataTypes.NUMBER, + last_execution_time: DataTypes.NUMBER, +}); diff --git a/back/data/dependence.ts b/back/data/dependence.ts index a3d74444..f254e008 100644 --- a/back/data/dependence.ts +++ b/back/data/dependence.ts @@ -1,7 +1,9 @@ +import { sequelize } from '.'; +import { DataTypes, Model, ModelDefined } from 'sequelize'; + export class Dependence { timestamp?: string; - created?: number; - _id?: string; + id?: number; status?: DependenceStatus; type?: DependenceTypes; name?: number; @@ -9,8 +11,7 @@ export class Dependence { remark?: string; constructor(options: Dependence) { - this._id = options._id; - this.created = options.created || new Date().valueOf(); + this.id = options.id; this.status = options.status || DependenceStatus.installing; this.type = options.type || DependenceTypes.nodejs; this.timestamp = new Date().toString(); @@ -46,3 +47,18 @@ export enum unInstallDependenceCommandTypes { 'pip3 uninstall -y', 'apk del -f', } + +interface DependenceInstance + extends Model, + Dependence {} +export const DependenceModel = sequelize.define( + 'Dependence', + { + name: DataTypes.STRING, + type: DataTypes.STRING, + timestamp: DataTypes.STRING, + status: DataTypes.STRING, + log: DataTypes.JSON, + remark: DataTypes.STRING, + }, +); diff --git a/back/data/env.ts b/back/data/env.ts index 5ddb9c3d..1cc89275 100644 --- a/back/data/env.ts +++ b/back/data/env.ts @@ -1,17 +1,18 @@ +import { sequelize } from '.'; +import { DataTypes, Model, ModelDefined } from 'sequelize'; + export class Env { value?: string; timestamp?: string; - created?: number; - _id?: string; + id?: number; status?: EnvStatus; position?: number; - name?: number; - remarks?: number; + name?: string; + remarks?: string; constructor(options: Env) { this.value = options.value; - this._id = options._id; - this.created = options.created || new Date().valueOf(); + this.id = options.id; this.status = options.status || EnvStatus.normal; this.timestamp = new Date().toString(); this.position = options.position; @@ -26,3 +27,13 @@ export enum EnvStatus { } export const initEnvPosition = 9999999999; + +interface EnvInstance extends Model, Env {} +export const EnvModel = sequelize.define('Env', { + value: DataTypes.STRING, + timestamp: DataTypes.STRING, + status: DataTypes.NUMBER, + position: DataTypes.NUMBER, + name: DataTypes.STRING, + remarks: DataTypes.STRING, +}); diff --git a/back/data/index.ts b/back/data/index.ts new file mode 100644 index 00000000..2e7dd50a --- /dev/null +++ b/back/data/index.ts @@ -0,0 +1,8 @@ +import { Sequelize } from 'sequelize'; +import config from '../config/index'; + +export const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: `${config.dbPath}database.sqlite`, + logging: false, +}); diff --git a/back/data/open.ts b/back/data/open.ts index 858b4234..feaacdcb 100644 --- a/back/data/open.ts +++ b/back/data/open.ts @@ -1,23 +1,26 @@ +import { sequelize } from '.'; +import { DataTypes, Model, ModelDefined } from 'sequelize'; + export class App { name: string; scopes: AppScope[]; client_id: string; client_secret: string; tokens?: AppToken[]; - _id?: string; + id?: number; constructor(options: App) { this.name = options.name; this.scopes = options.scopes; this.client_id = options.client_id; this.client_secret = options.client_secret; - this._id = options._id; + this.id = options.id; } } export interface AppToken { value: string; - type: 'Bearer'; + type?: 'Bearer'; expiration: number; } @@ -29,3 +32,12 @@ export enum CrontabStatus { 'disabled', 'queued', } + +interface AppInstance extends Model, App {} +export const AppModel = sequelize.define('App', { + name: DataTypes.STRING, + scopes: DataTypes.JSON, + client_id: DataTypes.STRING, + client_secret: DataTypes.STRING, + tokens: DataTypes.JSON, +}); diff --git a/back/data/sock.ts b/back/data/sock.ts index 3132f256..749eeddf 100644 --- a/back/data/sock.ts +++ b/back/data/sock.ts @@ -1,7 +1,7 @@ export class SockMessage { message?: string; type?: SockMessageType; - references?: string[]; + references?: number[]; constructor(options: SockMessage) { this.type = options.type; diff --git a/back/loaders/db.ts b/back/loaders/db.ts index 95089f5d..0e9bb82b 100644 --- a/back/loaders/db.ts +++ b/back/loaders/db.ts @@ -3,6 +3,12 @@ import config from '../config'; import Logger from './logger'; import fs from 'fs'; import { fileExist } from '../config/util'; +import { EnvModel } from '../data/env'; +import { CrontabModel } from '../data/cron'; +import { DependenceModel } from '../data/dependence'; +import { AppModel } from '../data/open'; +import { AuthModel } from '../data/auth'; +import { sequelize } from '../data'; interface Dbs { cronDb: DataStore; @@ -53,6 +59,43 @@ export default async () => { db.appDb.persistence.compactDatafile(); db.authDb.persistence.compactDatafile(); + try { + await sequelize.sync(); + } catch (error) { + console.log(error); + } + + // migrate db to sqlite + setTimeout(async () => { + try { + const count = await CrontabModel.count(); + if (count !== 0) { + return; + } + db.cronDb.find({}).exec(async (err, docs) => { + await CrontabModel.bulkCreate(docs); + }); + + db.dependenceDb.find({}).exec(async (err, docs) => { + await DependenceModel.bulkCreate(docs); + }); + + db.envDb.find({}).exec(async (err, docs) => { + await EnvModel.bulkCreate(docs); + }); + + db.appDb.find({}).exec(async (err, docs) => { + await AppModel.bulkCreate(docs); + }); + + db.authDb.find({}).exec(async (err, docs) => { + await AuthModel.bulkCreate(docs); + }); + } catch (error) { + console.log(error); + } + }, 5000); + Logger.info('✌️ DB loaded'); } catch (error) { Logger.info('✌️ DB load failed'); diff --git a/back/loaders/initData.ts b/back/loaders/initData.ts index f0dc0efc..9c7ab534 100644 --- a/back/loaders/initData.ts +++ b/back/loaders/initData.ts @@ -1,33 +1,31 @@ import DependenceService from '../services/dependence'; import { exec } from 'child_process'; import { Container } from 'typedi'; -import { Crontab, CrontabStatus } from '../data/cron'; +import { Crontab, CrontabModel, CrontabStatus } from '../data/cron'; import CronService from '../services/cron'; import EnvService from '../services/env'; import _ from 'lodash'; -import { dbs } from '../loaders/db'; +import { DependenceModel } from '../data/dependence'; +import { Op } from 'sequelize'; export default async () => { const cronService = Container.get(CronService); const envService = Container.get(EnvService); const dependenceService = Container.get(DependenceService); - const cronDb = dbs.cronDb; - const dependenceDb = dbs.dependenceDb; // 初始化更新所有任务状态为空闲 - cronDb.update( - { status: { $in: [CrontabStatus.running, CrontabStatus.queued] } }, - { $set: { status: CrontabStatus.idle } }, - { multi: true }, + await CrontabModel.update( + { status: CrontabStatus.idle }, + { where: { status: [CrontabStatus.running, CrontabStatus.queued] } }, ); // 初始化时安装所有处于安装中,安装成功,安装失败的依赖 - dependenceDb.find({ status: { $in: [0, 1, 2] } }).exec(async (err, docs) => { + DependenceModel.findAll({ where: {} }).then(async (docs) => { const groups = _.groupBy(docs, 'type'); for (const key in groups) { if (Object.prototype.hasOwnProperty.call(groups, key)) { const group = groups[key]; - const depIds = group.map((x) => x._id); + const depIds = group.map((x) => x.id); for (const dep of depIds) { await dependenceService.reInstall([dep]); } @@ -36,38 +34,21 @@ export default async () => { }); // 初始化时执行一次所有的ql repo 任务 - cronDb - .find({ - command: /ql (repo|raw)/, - isDisabled: { $ne: 1 }, - }) - .exec((err, docs) => { - for (let i = 0; i < docs.length; i++) { - const doc = docs[i]; - if (doc) { - exec(doc.command); - } + CrontabModel.findAll({ + where: { + isDisabled: { [Op.ne]: 1 }, + command: { + [Op.or]: [{ [Op.like]: `%ql repo%` }, { [Op.like]: `%$ql raw%` }], + }, + }, + }).then((docs) => { + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + if (doc) { + exec(doc.command); } - }); - - // patch 禁用状态字段改变 - cronDb - .find({ - status: CrontabStatus.disabled, - }) - .exec((err, docs) => { - if (docs.length > 0) { - const ids = docs.map((x) => x._id); - cronDb.update( - { _id: { $in: ids } }, - { $set: { status: CrontabStatus.idle, isDisabled: 1 } }, - { multi: true }, - (err) => { - cronService.autosave_crontab(); - }, - ); - } - }); + } + }); // 初始化保存一次ck和定时任务数据 await cronService.autosave_crontab(); diff --git a/back/loaders/sentry.ts b/back/loaders/sentry.ts index aa53e600..6ef75224 100644 --- a/back/loaders/sentry.ts +++ b/back/loaders/sentry.ts @@ -5,7 +5,7 @@ import Logger from './logger'; export default ({ expressApp }: { expressApp: Application }) => { Sentry.init({ - dsn: 'https://e14681bce55f4849b11024a7d424b711@o1051273.ingest.sentry.io/6047906', + dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819', integrations: [ new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app: expressApp }), diff --git a/back/schedule.ts b/back/schedule.ts index 2be45603..78a5f0d6 100644 --- a/back/schedule.ts +++ b/back/schedule.ts @@ -2,24 +2,14 @@ import schedule from 'node-schedule'; import express from 'express'; import { exec } from 'child_process'; import Logger from './loaders/logger'; -import { CrontabStatus } from './data/cron'; +import { CrontabModel, CrontabStatus } from './data/cron'; import config from './config'; -import { dbs } from './loaders/db'; const app = express(); const run = async () => { - const cronDb = dbs.cronDb; - - cronDb - .find({}) - .sort({ created: 1 }) - .exec((err, docs) => { - if (err) { - Logger.error(err); - process.exit(1); - } - + CrontabModel.findAll({ where: {} }) + .then((docs) => { if (docs && docs.length > 0) { for (let i = 0; i < docs.length; i++) { const task = docs[i]; @@ -40,6 +30,10 @@ const run = async () => { } } } + }) + .catch((err) => { + Logger.error(err); + process.exit(1); }); }; diff --git a/back/services/cron.ts b/back/services/cron.ts index 53ccefc0..216c0a3a 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -1,25 +1,17 @@ import { Service, Inject } from 'typedi'; import winston from 'winston'; -import DataStore from 'nedb'; import config from '../config'; -import { Crontab, CrontabStatus } from '../data/cron'; +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 } from '../config/util'; -import PQueue from 'p-queue'; +import { getFileContentByName, concurrentRun } from '../config/util'; import { promises, existsSync } from 'fs'; import { promisify } from 'util'; -import { dbs } from '../loaders/db'; +import { Op } from 'sequelize'; @Service() export default class CronService { - private cronDb = dbs.cronDb; - - private queue = new PQueue({ - concurrency: parseInt(process.env.MaxConcurrentNum as string) || 5, - }); - constructor(@Inject('logger') private logger: winston.Logger) {} private isSixCron(cron: Crontab) { @@ -32,7 +24,6 @@ export default class CronService { public async create(payload: Crontab): Promise { const tab = new Crontab(payload); - tab.created = new Date().valueOf(); tab.saved = false; const doc = await this.insert(tab); await this.set_crontab(this.isSixCron(doc)); @@ -40,20 +31,12 @@ export default class CronService { } 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); - } - }); - }); + return await CrontabModel.create(payload); } public async update(payload: Crontab): Promise { - const { _id, ...other } = payload; - const doc = await this.get(_id); + const { id, ...other } = payload; + const doc = await this.get(id as number); const tab = new Crontab({ ...doc, ...other }); tab.saved = false; const newDoc = await this.updateDb(tab); @@ -62,20 +45,11 @@ export default class CronService { } 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); - } - }, - ); - }); + const result = await CrontabModel.update( + { ...payload }, + { where: { id: payload.id } }, + ); + return result[1][0]; } public async status({ @@ -86,7 +60,7 @@ export default class CronService { last_running_time = 0, last_execution_time = 0, }: { - ids: string[]; + ids: number[]; status: CrontabStatus; pid: number; log_path: string; @@ -103,57 +77,20 @@ export default class CronService { options.last_running_time = last_running_time; } - return new Promise((resolve) => { - this.cronDb.update( - { _id: { $in: ids } }, - { - $set: options, - }, - { multi: true, returnUpdatedDocs: true }, - (err) => { - resolve(null); - }, - ); - }); + return await CrontabModel.update({ ...options }, { where: { id: ids } }); } - public async remove(ids: string[]) { - return new Promise((resolve: any) => { - this.cronDb.remove( - { _id: { $in: ids } }, - { multi: true }, - async (err) => { - await this.set_crontab(true); - resolve(); - }, - ); - }); + public async remove(ids: number[]) { + await CrontabModel.destroy({ where: { id: ids } }); + await this.set_crontab(true); } - public async pin(ids: string[]) { - return new Promise((resolve: any) => { - this.cronDb.update( - { _id: { $in: ids } }, - { $set: { isPinned: 1 } }, - { multi: true }, - async (err) => { - resolve(); - }, - ); - }); + public async pin(ids: number[]) { + await CrontabModel.update({ isPinned: 1 }, { where: { id: ids } }); } - public async unPin(ids: string[]) { - return new Promise((resolve: any) => { - this.cronDb.update( - { _id: { $in: ids } }, - { $set: { isPinned: 0 } }, - { multi: true }, - async (err) => { - resolve(); - }, - ); - }); + public async unPin(ids: number[]) { + await CrontabModel.update({ isPinned: 0 }, { where: { id: ids } }); } public async addLabels(ids: string[],labels: string[]){ @@ -188,21 +125,21 @@ export default class CronService { const textArray = searchText.split(":"); switch (textArray[0]) { case "name": - query = {name:createRegexp(textArray[1])}; + query = {name:{[Op.or]:createRegexp(textArray[1])}}; break; case "command": - query = {command:createRegexp(textArray[1])}; + query = {command:{[Op.or]:createRegexp(textArray[1])}}; break; case "schedule": - query = {schedule:createRegexp(textArray[1])}; + query = {schedule:{[Op.or]:createRegexp(textArray[1])}}; break; case "label": - query = {labels:createRegexp(textArray[1])}; + query = {labels:{[Op.or]:createRegexp(textArray[1])}}; break; default: const reg = createRegexp(searchText); query = { - $or: [ + [Op.or]: [ { name: reg, }, @@ -220,77 +157,67 @@ export default class CronService { break; } } - return new Promise((resolve) => { - this.cronDb - .find(query) - .sort({ created: -1 }) - .exec((err, docs) => { - resolve(docs); - }); - }); - function createRegexp(text:string) :RegExp { - const encodeText = encodeURIComponent(text); - const reg = new RegExp(`${text}|${encodeText}`, 'i'); - return reg; + try { + const result = await CrontabModel.findAll({ where: query }); + return result as any; + } catch (error) { + throw error; + } + function createRegexp(text:string) { + return { + [Op.or]: [ + { [Op.like]: `%${searchText}&` }, + { [Op.like]: `%${encodeURIComponent(text)}%` }, + ], + }; } } - public async get(_id: string): Promise { - return new Promise((resolve) => { - this.cronDb.find({ _id }).exec((err, docs) => { - resolve(docs[0]); - }); - }); + public async get(id: number): Promise { + const result = await CrontabModel.findAll({ where: { id } }); + return result[0] as any; } - public async run(ids: string[]) { - this.cronDb.update( - { _id: { $in: ids } }, - { $set: { status: CrontabStatus.queued } }, - { multi: true }, + public async run(ids: number[]) { + await CrontabModel.update( + { status: CrontabStatus.queued }, + { where: { id: ids } }, ); - for (let i = 0; i < ids.length; i++) { - const id = ids[i]; - this.queue.add(() => this.runSingle(id)); + 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); + if (doc.log_path) { + const str = err ? `\n${err}` : ''; + fs.appendFileSync( + `${doc.log_path}`, + `${str}\n## 执行结束... ${new Date() + .toLocaleString('zh', { hour12: false }) + .replace(' 24:', ' 00:')} `, + ); + } } + + await CrontabModel.update( + { status: CrontabStatus.queued, pid: undefined }, + { where: { id: ids } }, + ); } - public async stop(ids: string[]) { - return new Promise((resolve: any) => { - this.cronDb - .find({ _id: { $in: ids } }) - .exec(async (err, docs: Crontab[]) => { - 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); - if (doc.log_path) { - const str = err ? `\n${err}` : ''; - fs.appendFileSync( - `${doc.log_path}`, - `${str}\n## 执行结束... ${new Date() - .toLocaleString('zh', { hour12: false }) - .replace(' 24:', ' 00:')} `, - ); - } - } - this.cronDb.update( - { _id: { $in: ids } }, - { $set: { status: CrontabStatus.idle }, $unset: { pid: true } }, - { multi: true }, - ); - this.queue.clear(); - resolve(); - }); - }); - } - - private async killTask(name: string) { + public async killTask(name: string) { let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`; const execAsync = promisify(exec); try { @@ -322,18 +249,18 @@ export default class CronService { } } - private async runSingle(id: string): Promise { + private async runSingle(cronId: number): Promise { return new Promise(async (resolve: any) => { - const cron = await this.get(id); + const cron = await this.get(cronId); if (cron.status !== CrontabStatus.queued) { resolve(); return; } - let { _id, command, log_path } = cron; + let { id, command, log_path } = cron; this.logger.silly('Running job'); - this.logger.silly('ID: ' + _id); + this.logger.silly('ID: ' + id); this.logger.silly('Original command: ' + command); let cmdStr = command; @@ -345,9 +272,10 @@ export default class CronService { } const cp = spawn(cmdStr, { shell: '/bin/bash' }); - this.cronDb.update( - { _id }, - { $set: { status: CrontabStatus.running, pid: cp.pid } }, + + await CrontabModel.update( + { status: CrontabStatus.running, pid: cp.pid }, + { where: { id } }, ); cp.stderr.on('data', (data) => { if (log_path) { @@ -360,57 +288,39 @@ export default class CronService { } }); - cp.on('exit', (code, signal) => { + cp.on('exit', async (code, signal) => { this.logger.info( `${command} pid: ${cp.pid} exit ${code} signal ${signal}`, ); - this.cronDb.update( - { _id }, - { $set: { status: CrontabStatus.idle }, $unset: { pid: true } }, + await CrontabModel.update( + { status: CrontabStatus.idle, pid: undefined }, + { where: { id } }, ); resolve(); }); - cp.on('close', (code) => { + cp.on('close', async (code) => { this.logger.info(`${command} pid: ${cp.pid} closed ${code}`); - this.cronDb.update( - { _id }, - { $set: { status: CrontabStatus.idle }, $unset: { pid: true } }, + await CrontabModel.update( + { status: CrontabStatus.idle, pid: undefined }, + { where: { id } }, ); resolve(); }); }); } - public async disabled(ids: string[]) { - return new Promise((resolve: any) => { - this.cronDb.update( - { _id: { $in: ids } }, - { $set: { isDisabled: 1 } }, - { multi: true }, - async (err) => { - await this.set_crontab(true); - resolve(); - }, - ); - }); + public async disabled(ids: number[]) { + await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } }); + await this.set_crontab(true); } - public async enabled(ids: string[]) { - return new Promise((resolve: any) => { - this.cronDb.update( - { _id: { $in: ids } }, - { $set: { isDisabled: 0 } }, - { multi: true }, - async (err) => { - await this.set_crontab(true); - resolve(); - }, - ); - }); + public async enabled(ids: number[]) { + await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } }); + await this.set_crontab(true); } - public async log(_id: string) { - const doc = await this.get(_id); + public async log(id: number) { + const doc = await this.get(id); if (!doc) { return ''; } @@ -450,7 +360,7 @@ export default class CronService { } private make_command(tab: Crontab) { - const crontab_job_string = `ID=${tab._id} ${tab.command}`; + const crontab_job_string = `ID=${tab.id} ${tab.command}`; return crontab_job_string; } @@ -480,7 +390,7 @@ export default class CronService { if (needReloadSchedule) { exec(`pm2 reload schedule`); } - this.cronDb.update({}, { $set: { saved: true } }, { multi: true }); + await CrontabModel.update({ saved: true }, { where: {} }); } public import_crontab() { @@ -488,7 +398,7 @@ export default class CronService { var lines = stdout.split('\n'); var namePrefix = new Date().getTime(); - lines.reverse().forEach((line, index) => { + lines.reverse().forEach(async (line, index) => { line = line.replace(/\t+/g, ' '); var regex = /^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/; @@ -502,18 +412,16 @@ export default class CronService { ) { var name = namePrefix + '_' + index; - this.cronDb.findOne({ command, schedule }, (err, doc) => { - if (err) { - throw err; - } - if (!doc) { - this.create({ name, command, schedule }); - } else { - doc.command = command; - doc.schedule = schedule; - this.update(doc); - } + 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); + } } }); }); diff --git a/back/services/dependence.ts b/back/services/dependence.ts index 5cb95bc3..133e00de 100644 --- a/back/services/dependence.ts +++ b/back/services/dependence.ts @@ -8,16 +8,15 @@ import { DependenceStatus, DependenceTypes, unInstallDependenceCommandTypes, + DependenceModel, } from '../data/dependence'; import _ from 'lodash'; import { spawn } from 'child_process'; import SockService from './sock'; -import { dbs } from '../loaders/db'; +import { Op } from 'sequelize'; @Service() export default class DependenceService { - private dependenceDb = dbs.dependenceDb; - constructor( @Inject('logger') private logger: winston.Logger, private sockService: SockService, @@ -34,22 +33,15 @@ export default class DependenceService { } public async insert(payloads: Dependence[]): Promise { - return new Promise((resolve) => { - this.dependenceDb.insert(payloads, (err, docs) => { - if (err) { - this.logger.error(err); - } else { - resolve(docs); - } - }); - }); + const docs = await DependenceModel.bulkCreate(payloads); + return docs; } public async update( - payload: Dependence & { _id: string }, + payload: Dependence & { id: string }, ): Promise { - const { _id, ...other } = payload; - const doc = await this.get(_id); + const { id, ...other } = payload; + const doc = await this.get(id); const tab = new Dependence({ ...doc, ...other, @@ -61,46 +53,23 @@ export default class DependenceService { } private async updateDb(payload: Dependence): Promise { - return new Promise((resolve) => { - this.dependenceDb.update( - { _id: payload._id }, - payload, - { returnUpdatedDocs: true }, - (err, num, doc) => { - if (err) { - this.logger.error(err); - } else { - resolve(doc as Dependence); - } - }, - ); - }); + const [, docs] = await DependenceModel.update( + { ...payload }, + { where: { id: payload.id } }, + ); + return docs[0]; } - public async remove(ids: string[]) { - return new Promise((resolve: any) => { - this.dependenceDb.update( - { _id: { $in: ids } }, - { $set: { status: DependenceStatus.removing, log: [] } }, - { multi: true, returnUpdatedDocs: true }, - async (err, num, docs: Dependence[]) => { - this.installOrUninstallDependencies(docs, false); - resolve(docs); - }, - ); - }); + public async remove(ids: number[]) { + const [, docs] = await DependenceModel.update( + { status: DependenceStatus.removing, log: [] }, + { where: { id: ids } }, + ); + this.installOrUninstallDependencies(docs, false); } - public async removeDb(ids: string[]) { - return new Promise((resolve: any) => { - this.dependenceDb.remove( - { _id: { $in: ids } }, - { multi: true }, - async (err) => { - resolve(); - }, - ); - }); + public async removeDb(ids: number[]) { + await DependenceModel.destroy({ where: { id: ids } }); } public async dependencies( @@ -110,68 +79,53 @@ export default class DependenceService { ): Promise { let condition = { ...query, type: DependenceTypes[type as any] }; if (searchValue) { - const reg = new RegExp(searchValue); - condition = { - ...condition, - $or: [ - { - name: reg, - }, + const encodeText = encodeURIComponent(searchValue); + const reg = { + [Op.or]: [ + { [Op.like]: `%${encodeText}&` }, + { [Op.like]: `%${encodeText}%` }, ], }; + + condition = { + ...condition, + name: reg, + }; + } + try { + const result = await this.find(condition); + return result as any; + } catch (error) { + throw error; } - const newDocs = await this.find(condition, sort); - return newDocs; } - public async reInstall(ids: string[]): Promise { - return new Promise((resolve: any) => { - this.dependenceDb.update( - { _id: { $in: ids } }, - { $set: { status: DependenceStatus.installing, log: [] } }, - { multi: true, returnUpdatedDocs: true }, - async (err, num, docs: Dependence[]) => { - await this.installOrUninstallDependencies(docs); - resolve(docs); - }, - ); - }); + public async reInstall(ids: number[]): Promise { + const [, docs] = await DependenceModel.update( + { status: DependenceStatus.installing, log: [] }, + { where: { id: ids } }, + ); + await this.installOrUninstallDependencies(docs); + return docs; } - private async find(query: any, sort: any): Promise { - return new Promise((resolve) => { - this.dependenceDb - .find(query) - .sort({ ...sort }) - .exec((err, docs) => { - resolve(docs); - }); - }); + private async find(query: any, sort?: any): Promise { + const docs = await DependenceModel.findAll({ where: { ...query } }); + return docs; } - public async get(_id: string): Promise { - return new Promise((resolve) => { - this.dependenceDb.find({ _id }).exec((err, docs) => { - resolve(docs[0]); - }); - }); + public async get(id: number): Promise { + const docs = await DependenceModel.findAll({ where: { id } }); + return docs[0]; } - private async updateLog(ids: string[], log: string): Promise { - return new Promise((resolve) => { - this.dependenceDb.update( - { _id: { $in: ids } }, - { $push: { log } }, - { multi: true }, - (err, num, doc) => { - if (err) { - this.logger.error(err); - } else { - resolve(); - } - }, - ); - }); + private async updateLog(ids: number[], log: string): Promise { + const doc = await DependenceModel.findOne({ where: { id: ids } }); + const newLog = doc?.log ? [...doc.log, log] : [log]; + const [, docs] = await DependenceModel.update( + { status: DependenceStatus.installing, log: newLog }, + { where: { id: ids } }, + ); } public installOrUninstallDependencies( @@ -190,7 +144,7 @@ export default class DependenceService { : unInstallDependenceCommandTypes )[dependencies[0].type as any]; const actionText = isInstall ? '安装' : '删除'; - const depIds = dependencies.map((x) => x._id) as string[]; + const depIds = dependencies.map((x) => x.id) as number[]; const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' }); const startTime = Date.now(); this.sockService.sendMessage({ @@ -263,14 +217,8 @@ export default class DependenceService { ? DependenceStatus.installFailed : DependenceStatus.removeFailed; } - this.dependenceDb.update( - { _id: { $in: depIds } }, - { - $set: { status }, - $unset: { pid: true }, - }, - { multi: true }, - ); + + DependenceModel.update({ status }, { where: { id: depIds } }); // 如果删除依赖成功,3秒后删除数据库记录 if (isSucceed && !isInstall) { diff --git a/back/services/env.ts b/back/services/env.ts index 44bc48bb..bb68a57b 100644 --- a/back/services/env.ts +++ b/back/services/env.ts @@ -4,14 +4,12 @@ import { getFileContentByName } from '../config/util'; import config from '../config'; import * as fs from 'fs'; import DataStore from 'nedb'; -import { Env, EnvStatus, initEnvPosition } from '../data/env'; +import { Env, EnvModel, EnvStatus, initEnvPosition } from '../data/env'; import _ from 'lodash'; -import { dbs } from '../loaders/db'; +import { Op } from 'sequelize'; @Service() export default class EnvService { - private envDb = dbs.envDb; - constructor(@Inject('logger') private logger: winston.Logger) {} public async create(payloads: Env[]): Promise { @@ -31,20 +29,13 @@ export default class EnvService { } public async insert(payloads: Env[]): Promise { - return new Promise((resolve) => { - this.envDb.insert(payloads, (err, docs) => { - if (err) { - this.logger.error(err); - } else { - resolve(docs); - } - }); - }); + const docs = await EnvModel.bulkCreate(payloads); + return docs; } public async update(payload: Env): Promise { - const { _id, ...other } = payload; - const doc = await this.get(_id); + const { id, ...other } = payload; + const doc = await this.get(id as number); const tab = new Env({ ...doc, ...other }); const newDoc = await this.updateDb(tab); await this.set_envs(); @@ -52,33 +43,19 @@ export default class EnvService { } private async updateDb(payload: Env): Promise { - return new Promise((resolve) => { - this.envDb.update( - { _id: payload._id }, - payload, - { returnUpdatedDocs: true }, - (err, num, doc) => { - if (err) { - this.logger.error(err); - } else { - resolve(doc as Env); - } - }, - ); - }); + const [, docs] = await EnvModel.update( + { ...payload }, + { where: { id: payload.id } }, + ); + return docs[0]; } public async remove(ids: string[]) { - return new Promise((resolve: any) => { - this.envDb.remove({ _id: { $in: ids } }, { multi: true }, async (err) => { - await this.set_envs(); - resolve(); - }); - }); + await EnvModel.destroy({ where: { id: ids } }); } public async move( - _id: string, + id: number, { fromIndex, toIndex, @@ -100,7 +77,7 @@ export default class EnvService { : (envs[toIndex].position + envs[toIndex + 1].position) / 2; } const newDoc = await this.update({ - _id, + id, position: targetPosition, }); return newDoc; @@ -114,104 +91,72 @@ export default class EnvService { let condition = { ...query }; if (searchText) { const encodeText = encodeURIComponent(searchText); - const reg = new RegExp(`${searchText}|${encodeText}`, 'i'); + const reg = { + [Op.or]: [ + { [Op.like]: `%${searchText}&` }, + { [Op.like]: `%${encodeText}%` }, + ], + }; condition = { - $or: [ - { - value: reg, - }, + ...condition, + [Op.or]: [ { name: reg, }, { - remarks: reg, + command: reg, + }, + { + schedule: reg, }, ], }; } - const newDocs = await this.find(condition, sort); - return newDocs; + try { + const result = await this.find(condition); + return result as any; + } catch (error) { + throw error; + } } - private async find(query: any, sort: any): Promise { - return new Promise((resolve) => { - this.envDb - .find(query) - .sort({ ...sort }) - .exec((err, docs) => { - resolve(docs); - }); - }); + private async find(query: any, sort?: any): Promise { + const docs = await EnvModel.findAll({ where: { ...query } }); + return docs; } - public async get(_id: string): Promise { - return new Promise((resolve) => { - this.envDb.find({ _id }).exec((err, docs) => { - resolve(docs[0]); - }); - }); - } - - public async getBySort(sort: any): Promise { - return new Promise((resolve) => { - this.envDb - .find({}) - .sort({ ...sort }) - .limit(1) - .exec((err, docs) => { - resolve(docs[0]); - }); - }); + public async get(id: number): Promise { + const docs = await EnvModel.findAll({ where: { id } }); + return docs[0]; } public async disabled(ids: string[]) { - return new Promise((resolve: any) => { - this.envDb.update( - { _id: { $in: ids } }, - { $set: { status: EnvStatus.disabled } }, - { multi: true }, - async (err) => { - await this.set_envs(); - resolve(); - }, - ); - }); + const [, docs] = await EnvModel.update( + { status: EnvStatus.disabled }, + { where: { id: ids } }, + ); + await this.set_envs(); } public async enabled(ids: string[]) { - return new Promise((resolve: any) => { - this.envDb.update( - { _id: { $in: ids } }, - { $set: { status: EnvStatus.normal } }, - { multi: true }, - async (err, num) => { - await this.set_envs(); - resolve(); - }, - ); - }); + const [, docs] = await EnvModel.update( + { status: EnvStatus.normal }, + { where: { id: ids } }, + ); + await this.set_envs(); } public async updateNames({ ids, name }: { ids: string[]; name: string }) { - return new Promise((resolve: any) => { - this.envDb.update( - { _id: { $in: ids } }, - { $set: { name } }, - { multi: true }, - async (err, num) => { - await this.set_envs(); - resolve(); - }, - ); - }); + const [, docs] = await EnvModel.update({ name }, { where: { id: ids } }); + await this.set_envs(); } public async set_envs() { const envs = await this.envs( '', { position: -1 }, - { name: { $exists: true } }, + { name: { [Op.not]: null } }, ); const groups = _.groupBy(envs, 'name'); let env_string = ''; diff --git a/back/services/open.ts b/back/services/open.ts index 6e69a08f..63595924 100644 --- a/back/services/open.ts +++ b/back/services/open.ts @@ -3,29 +3,23 @@ import winston from 'winston'; import { createRandomString } from '../config/util'; import config from '../config'; import DataStore from 'nedb'; -import { App } from '../data/open'; +import { App, AppModel } from '../data/open'; import { v4 as uuidV4 } from 'uuid'; -import { dbs } from '../loaders/db'; +import sequelize, { Op } from 'sequelize'; @Service() export default class OpenService { - private appDb = dbs.appDb; - constructor(@Inject('logger') private logger: winston.Logger) {} - public async findTokenByValue(token: string): Promise { - return new Promise((resolve) => { - this.appDb.find( - { tokens: { $elemMatch: { value: token } } }, - (err, docs) => { - if (err) { - this.logger.error(err); - } else { - resolve(docs[0]); - } - }, - ); + public async findTokenByValue(token: string): Promise { + const doc = await AppModel.findOne({ + where: sequelize.fn( + 'JSON_CONTAINS', + sequelize.col('tokens'), + JSON.stringify({ value: token }), + ), }); + return doc; } public async create(payload: App): Promise { @@ -37,52 +31,32 @@ export default class OpenService { } public async insert(payloads: App[]): Promise { - return new Promise((resolve) => { - this.appDb.insert(payloads, (err, docs) => { - if (err) { - this.logger.error(err); - } else { - resolve(docs); - } - }); - }); + const docs = await AppModel.bulkCreate(payloads); + return docs; } public async update(payload: App): Promise { - const { _id, client_id, client_secret, tokens, ...other } = payload; - const doc = await this.get(_id); + const { id, client_id, client_secret, tokens, ...other } = payload; + const doc = await this.get(id); const tab = new App({ ...doc, ...other }); const newDoc = await this.updateDb(tab); return { ...newDoc, tokens: [] }; } private async updateDb(payload: App): Promise { - return new Promise((resolve) => { - this.appDb.update( - { _id: payload._id }, - payload, - { returnUpdatedDocs: true }, - (err, num, doc, up) => { - if (err) { - this.logger.error(err); - } else { - resolve(doc); - } - }, - ); - }); + const [, docs] = await AppModel.update( + { ...payload }, + { where: { id: payload.id } }, + ); + return docs[0]; } - public async remove(ids: string[]) { - return new Promise((resolve: any) => { - this.appDb.remove({ _id: { $in: ids } }, { multi: true }, async (err) => { - resolve(); - }); - }); + public async remove(ids: number[]) { + await AppModel.destroy({ where: { id: ids } }); } - public async resetSecret(_id: string): Promise { - const doc = await this.get(_id); + public async resetSecret(id: number): Promise { + const doc = await this.get(id); const tab = new App({ ...doc }); tab.client_secret = createRandomString(24, 24); tab.tokens = []; @@ -98,43 +72,44 @@ export default class OpenService { let condition = { ...query }; if (searchText) { const encodeText = encodeURIComponent(searchText); - const reg = new RegExp(`${searchText}|${encodeText}`, 'i'); + const reg = { + [Op.or]: [ + { [Op.like]: `%${searchText}&` }, + { [Op.like]: `%${encodeText}%` }, + ], + }; condition = { - $or: [ - { - value: reg, - }, + ...condition, + [Op.or]: [ { name: reg, }, { - remarks: reg, + command: reg, + }, + { + schedule: reg, }, ], }; } - const newDocs = await this.find(condition, sort); - return newDocs.map((x) => ({ ...x, tokens: [] })); + try { + const result = await this.find(condition); + return result.map((x) => ({ ...x, tokens: [] })); + } catch (error) { + throw error; + } } - private async find(query: any, sort: any): Promise { - return new Promise((resolve) => { - this.appDb - .find(query) - .sort({ ...sort }) - .exec((err, docs) => { - resolve(docs); - }); - }); + private async find(query: any, sort?: any): Promise { + const docs = await AppModel.findAll({ where: { ...query } }); + return docs; } - public async get(_id: string): Promise { - return new Promise((resolve) => { - this.appDb.find({ _id }).exec((err, docs) => { - resolve(docs[0]); - }); - }); + public async get(id: number): Promise { + const docs = await AppModel.findAll({ where: { id } }); + return docs[0]; } public async authToken({ @@ -146,28 +121,22 @@ export default class OpenService { }): Promise { const token = uuidV4(); const expiration = Math.round(Date.now() / 1000) + 2592000; // 2592000 30天 - return new Promise((resolve) => { - this.appDb.find({ client_id, client_secret }).exec((err, docs) => { - if (docs && docs[0]) { - this.appDb.update( - { client_id, client_secret }, - { $push: { tokens: { value: token, expiration } } }, - {}, - (err, num, doc) => { - resolve({ - code: 200, - data: { - token, - token_type: 'Bearer', - expiration, - }, - }); - }, - ); - } else { - resolve({ code: 400, message: 'client_id或client_seret有误' }); - } - }); - }); + const doc = await AppModel.findOne({ where: { client_id, client_secret } }); + if (doc) { + const [, docs] = await AppModel.update( + { tokens: [...(doc.tokens || []), { value: token, expiration }] }, + { where: { client_id, client_secret } }, + ); + return { + code: 200, + data: { + token, + token_type: 'Bearer', + expiration, + }, + }; + } else { + return { code: 400, message: 'client_id或client_seret有误' }; + } } } diff --git a/back/services/schedule.ts b/back/services/schedule.ts index 7c6bb3ac..419906ea 100644 --- a/back/services/schedule.ts +++ b/back/services/schedule.ts @@ -6,22 +6,22 @@ import { exec } from 'child_process'; @Service() export default class ScheduleService { - private scheduleStacks = new Map(); + private scheduleStacks = new Map(); constructor(@Inject('logger') private logger: winston.Logger) {} - async generateSchedule({ _id = '', command, name, schedule }: Crontab) { + async generateSchedule({ id = 0, command, name, schedule }: Crontab) { this.logger.info( '[创建定时任务],任务ID: %s,cron: %s,任务名: %s,执行命令: %s', - _id, + id, schedule, name, command, ); this.scheduleStacks.set( - _id, - nodeSchedule.scheduleJob(_id, schedule, async () => { + id, + nodeSchedule.scheduleJob(id + '', schedule, async () => { try { exec(command, async (error, stdout, stderr) => { if (error) { @@ -55,8 +55,8 @@ export default class ScheduleService { ); } - async cancelSchedule({ _id = '', name }: Crontab) { + async cancelSchedule({ id = 0, name }: Crontab) { this.logger.info('[取消定时任务],任务名:%s', name); - this.scheduleStacks.has(_id) && this.scheduleStacks.get(_id)?.cancel(); + this.scheduleStacks.has(id) && this.scheduleStacks.get(id)?.cancel(); } } diff --git a/back/services/script.ts b/back/services/script.ts index 5d1f3cfa..c44d7530 100644 --- a/back/services/script.ts +++ b/back/services/script.ts @@ -2,21 +2,19 @@ import { Service, Inject } from 'typedi'; import winston from 'winston'; import { spawn } from 'child_process'; import SockService from './sock'; +import CronService from './cron'; @Service() export default class ScriptService { constructor( @Inject('logger') private logger: winston.Logger, private sockService: SockService, + private cronService: CronService, ) {} public async runScript(path: string) { const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' }); - this.sockService.sendMessage({ - type: 'manuallyRunScript', - message: `开始执行脚本`, - }); cp.stdout.on('data', (data) => { this.sockService.sendMessage({ type: 'manuallyRunScript', @@ -38,6 +36,26 @@ export default class ScriptService { }); }); + cp.on('close', (err) => { + this.sockService.sendMessage({ + type: 'manuallyRunScript', + message: `执行结束`, + }); + }); + + return { code: 200 }; + } + + public async stopScript(path: string) { + const err = await this.cronService.killTask(`task -l ${path} now`); + const str = err ? `\n${err}` : ''; + this.sockService.sendMessage({ + type: 'manuallyRunScript', + message: `${str}\n## 执行结束... ${new Date() + .toLocaleString('zh', { hour12: false }) + .replace(' 24:', ' 00:')} `, + }); + return { code: 200 }; } } diff --git a/back/services/system.ts b/back/services/system.ts index 93a884e9..774a7560 100644 --- a/back/services/system.ts +++ b/back/services/system.ts @@ -3,7 +3,7 @@ import winston from 'winston'; import config from '../config'; import * as fs from 'fs'; import _ from 'lodash'; -import { AuthDataType, AuthInfo, LoginStatus } from '../data/auth'; +import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth'; import { NotificationInfo } from '../data/notify'; import NotificationService from './notify'; import ScheduleService from './schedule'; @@ -16,7 +16,6 @@ import { dbs } from '../loaders/db'; export default class SystemService { @Inject((type) => NotificationService) private notificationService!: NotificationService; - private authDb = dbs.authDb; constructor( @Inject('logger') private logger: winston.Logger, @@ -25,34 +24,16 @@ export default class SystemService { ) {} public async getLogRemoveFrequency() { - return new Promise((resolve) => { - this.authDb - .find({ type: AuthDataType.removeLogFrequency }) - .exec((err, docs) => { - if (err || docs.length === 0) { - resolve({}); - } else { - resolve(docs[0].info); - } - }); + const doc = await AuthModel.findOne({ + where: { type: AuthDataType.removeLogFrequency }, }); + return (doc && doc.info) || {}; } private async updateAuthDb(payload: AuthInfo): Promise { - return new Promise((resolve) => { - this.authDb.update( - { type: payload.type }, - { ...payload }, - { upsert: true, returnUpdatedDocs: true }, - (err, num, doc: any) => { - if (err) { - resolve({} as NotificationInfo); - } else { - resolve({ ...doc.info, _id: doc._id }); - } - }, - ); - }); + await AuthModel.upsert({ ...payload }); + const doc = await AuthModel.findOne({ where: { type: payload.type } }); + return doc; } public async updateNotificationMode(notificationInfo: NotificationInfo) { @@ -79,7 +60,7 @@ export default class SystemService { info: { frequency }, }); const cron = { - _id: result._id, + id: result.id, name: '删除日志', command: `ql rmlog ${frequency}`, schedule: `5 23 */${frequency} * *`, diff --git a/back/services/user.ts b/back/services/user.ts index 31ca8ea2..d07edbeb 100644 --- a/back/services/user.ts +++ b/back/services/user.ts @@ -6,8 +6,7 @@ import * as fs from 'fs'; import _ from 'lodash'; import jwt from 'jsonwebtoken'; import { authenticator } from '@otplib/preset-default'; -import DataStore from 'nedb'; -import { AuthDataType, AuthInfo, LoginStatus } from '../data/auth'; +import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth'; import { NotificationInfo } from '../data/notify'; import NotificationService from './notify'; import { Request } from 'express'; @@ -15,13 +14,11 @@ import ScheduleService from './schedule'; import { spawn } from 'child_process'; import SockService from './sock'; import got from 'got'; -import { dbs } from '../loaders/db'; @Service() export default class UserService { @Inject((type) => NotificationService) private notificationService!: NotificationService; - private authDb = dbs.authDb; constructor( @Inject('logger') private logger: winston.Logger, @@ -174,33 +171,24 @@ export default class UserService { } public async getLoginLog(): Promise { - return new Promise((resolve) => { - this.authDb.find({ type: AuthDataType.loginLog }).exec((err, docs) => { - if (err || docs.length === 0) { - resolve([]); - } else { - const result = docs.sort( - (a, b) => b.info.timestamp - a.info.timestamp, - ); - if (result.length > 100) { - this.authDb.remove({ _id: result[result.length - 1]._id }); - } - resolve(result.map((x) => x.info)); - } - }); + const docs = await AuthModel.findAll({ + where: { type: AuthDataType.loginLog }, }); + if (docs && docs.length > 0) { + const result = docs.sort((a, b) => b.info.timestamp - a.info.timestamp); + if (result.length > 100) { + await AuthModel.destroy({ + where: { id: result[result.length - 1].id }, + }); + } + return result.map((x) => x.info); + } + return []; } private async insertDb(payload: AuthInfo): Promise { - return new Promise((resolve) => { - this.authDb.insert(payload, (err, doc) => { - if (err) { - this.logger.error(err); - } else { - resolve(doc); - } - }); - }); + const doc = await AuthModel.create({ ...payload }, { returning: true }); + return doc; } private initAuthInfo() { @@ -315,48 +303,23 @@ export default class UserService { } public async getNotificationMode(): Promise { - return new Promise((resolve) => { - this.authDb - .find({ type: AuthDataType.notification }) - .exec((err, docs) => { - if (err || docs.length === 0) { - resolve({} as NotificationInfo); - } else { - resolve(docs[0].info); - } - }); + const doc = await AuthModel.findOne({ + where: { type: AuthDataType.notification }, }); + return (doc && doc.info) || {}; } public async getLogRemoveFrequency() { - return new Promise((resolve) => { - this.authDb - .find({ type: AuthDataType.removeLogFrequency }) - .exec((err, docs) => { - if (err || docs.length === 0) { - resolve({}); - } else { - resolve(docs[0].info); - } - }); + const doc = await AuthModel.findOne({ + where: { type: AuthDataType.removeLogFrequency }, }); + return (doc && doc.info) || {}; } private async updateAuthDb(payload: AuthInfo): Promise { - return new Promise((resolve) => { - this.authDb.update( - { type: payload.type }, - { ...payload }, - { upsert: true, returnUpdatedDocs: true }, - (err, num, doc: any) => { - if (err) { - resolve({} as NotificationInfo); - } else { - resolve({ ...doc.info, _id: doc._id }); - } - }, - ); - }); + await AuthModel.upsert({ ...payload }); + const doc = await AuthModel.findOne({ where: { type: payload.type } }); + return doc; } public async updateNotificationMode(notificationInfo: NotificationInfo) { @@ -383,7 +346,7 @@ export default class UserService { info: { frequency }, }); const cron = { - _id: result._id, + id: result.id, name: '删除日志', command: `ql rmlog ${frequency}`, schedule: `5 23 */${frequency} * *`, diff --git a/docker/Dockerfile b/docker/Dockerfile index 156a88da..2b5ae72f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN set -x \ && pnpm install -g pm2 \ && pnpm install -g ts-node typescript tslib \ && rm -rf /root/.npm \ - && pnpm install --prod \ + && yarn install --prod \ && rm -rf /root/.pnpm-store \ && git clone -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \ && cp -rf /static/* ${QL_DIR} \ diff --git a/docker/front.conf b/docker/front.conf index 49388f38..7456e7fa 100644 --- a/docker/front.conf +++ b/docker/front.conf @@ -42,4 +42,8 @@ server { index index.html index.htm; try_files $uri $uri/ /index.html; } + + location ~ .*\.(html)$ { + add_header Cache-Control no-cache; + } } diff --git a/package.json b/package.json index 1b2a31f6..ba936839 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ }, "dependencies": { "@otplib/preset-default": "^12.0.1", - "@sentry/node": "^6.14.0", - "@sentry/tracing": "^6.14.0", + "@sentry/node": "^6.16.1", + "@sentry/tracing": "^6.16.1", "body-parser": "^1.19.0", "celebrate": "^13.0.3", "chokidar": "^3.5.2", @@ -47,8 +47,10 @@ "nodemailer": "^6.7.0", "p-queue": "6.6.2", "reflect-metadata": "^0.1.13", + "sequelize": "^7.0.0-alpha.3", "serve-handler": "^6.1.3", "sockjs": "^0.3.21", + "sqlite3": "^5.0.2", "typedi": "^0.8.0", "uuid": "^8.3.2", "winston": "^3.3.3", @@ -58,7 +60,7 @@ "@ant-design/icons": "^4.6.2", "@ant-design/pro-layout": "^6.26.0", "@monaco-editor/react": "^4.3.1", - "@sentry/react": "^6.14.0", + "@sentry/react": "^6.16.1", "@sentry/webpack-plugin": "^1.18.3", "@types/cors": "^2.8.10", "@types/express": "^4.17.8", @@ -76,6 +78,7 @@ "@types/serve-handler": "^6.1.1", "@types/sockjs": "^0.3.33", "@types/sockjs-client": "^1.5.1", + "@types/uuid": "^8.3.3", "@umijs/plugin-antd": "^0.11.0", "@umijs/test": "^3.3.9", "antd": "^4.17.0-alpha.6", diff --git a/sample/config.sample.sh b/sample/config.sample.sh index 4c1133a8..7d6f3131 100644 --- a/sample/config.sample.sh +++ b/sample/config.sample.sh @@ -35,6 +35,9 @@ EnableExtraShell="true" ## 是否自动启动bot,默认不启动,设置为true时自动启动,目前需要自行克隆bot仓库所需代码,存到ql/repo目录下,文件夹命名为dockerbot AutoStartBot="" +## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot +BotRepoUrl="" + ## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空 PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple" @@ -52,7 +55,7 @@ export BARK_SOUND="" ## 下方填写推送消息分组,默认为"QingLong" export BARK_GROUP="QingLong" -## 3. Telegram +## 3. Telegram ## 下方填写自己申请@BotFather的Token,如10xxx4:AAFcqxxxxgER5uw export TG_BOT_TOKEN="" ## 下方填写 @getuseridbot 中获取到的纯数字ID @@ -73,7 +76,7 @@ export TG_PROXY_AUTH="" ## 如需使用,请赋值代理地址链接,并自行解除下一行的注释 export TG_API_HOST="" -## 4. 钉钉 +## 4. 钉钉 ## 官方文档:https://developers.dingtalk.com/document/app/custom-robot-access ## 下方填写token后面的内容,只需 https://oapi.dingtalk.com/robot/send?access_token=XXX 等于=符号后面的XXX即可 export DD_BOT_TOKEN="" @@ -104,9 +107,9 @@ export PUSH_PLUS_TOKEN="" export PUSH_PLUS_USER="" ## 9. go-cqhttp -## gobot_url 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg +## gobot_url 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg ## gobot_token 填写在go-cqhttp文件设置的访问密钥 -## gobot_qq 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群 +## gobot_qq 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群 ## go-cqhttp相关API https://docs.go-cqhttp.org/api export GOBOT_URL="" export GOBOT_TOKEN="" diff --git a/shell/api.sh b/shell/api.sh index c133bb3e..034329f0 100755 --- a/shell/api.sh +++ b/shell/api.sh @@ -61,7 +61,7 @@ update_cron_api() { -H "Origin: http://0.0.0.0:5700" \ -H "Referer: http://0.0.0.0: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\",\"_id\":\"$id\"}" \ + --data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \ --compressed ) code=$(echo $api | jq -r .code) @@ -93,7 +93,7 @@ update_cron_command_api() { -H "Origin: http://0.0.0.0:5700" \ -H "Referer: http://0.0.0.0:5700/crontab" \ -H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \ - --data-raw "{\"command\":\"$command\",\"_id\":\"$id\"}" \ + --data-raw "{\"command\":\"$command\",\"id\":\"$id\"}" \ --compressed ) code=$(echo $api | jq -r .code) diff --git a/shell/bot.sh b/shell/bot.sh index e455aa40..53076af7 100644 --- a/shell/bot.sh +++ b/shell/bot.sh @@ -3,8 +3,14 @@ ## 导入通用变量与函数 dir_shell=/ql/shell . $dir_shell/share.sh -url="${github_proxy_url}https://github.com/SuMaiKaDe/bot.git" -repo_path="${dir_repo}/dockerbot" + +if [[ -z ${BotRepoUrl} ]]; then + url="${github_proxy_url}https://github.com/SuMaiKaDe/bot.git" + repo_path="${dir_repo}/dockerbot" +else + url=${BotRepoUrl} + repo_path="${dir_repo}/diybot" +fi echo -e "\n1、安装bot依赖...\n" apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 8eae7a63..8c68d0af 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -27,7 +27,7 @@ import * as Sentry from '@sentry/react'; import { Integrations } from '@sentry/tracing'; Sentry.init({ - dsn: 'https://ea2fede373244db99c536210b910d9da@o1051273.ingest.sentry.io/6047851', + dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818', integrations: [new Integrations.BrowserTracing()], release: version, tracesSampleRate: 1.0, diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx index 9919215a..3db26990 100644 --- a/src/pages/crontab/index.tsx +++ b/src/pages/crontab/index.tsx @@ -429,12 +429,12 @@ const Crontab = ({ headerStyle, isPhone }: any) => { ), onOk() { request - .delete(`${config.apiPrefix}crons`, { data: [record._id] }) + .delete(`${config.apiPrefix}crons`, { data: [record.id] }) .then((data: any) => { if (data.code === 200) { message.success('删除成功'); const result = [...value]; - const i = result.findIndex((x) => x._id === record._id); + const i = result.findIndex((x) => x.id === record.id); result.splice(i, 1); setValue(result); } else { @@ -462,11 +462,11 @@ const Crontab = ({ headerStyle, isPhone }: any) => { ), onOk() { request - .put(`${config.apiPrefix}crons/run`, { data: [record._id] }) + .put(`${config.apiPrefix}crons/run`, { data: [record.id] }) .then((data: any) => { if (data.code === 200) { const result = [...value]; - const i = result.findIndex((x) => x._id === record._id); + const i = result.findIndex((x) => x.id === record.id); result.splice(i, 1, { ...record, status: CrontabStatus.running, @@ -497,11 +497,11 @@ const Crontab = ({ headerStyle, isPhone }: any) => { ), onOk() { request - .put(`${config.apiPrefix}crons/stop`, { data: [record._id] }) + .put(`${config.apiPrefix}crons/stop`, { data: [record.id] }) .then((data: any) => { if (data.code === 200) { const result = [...value]; - const i = result.findIndex((x) => x._id === record._id); + const i = result.findIndex((x) => x.id === record.id); result.splice(i, 1, { ...record, pid: null, @@ -539,14 +539,14 @@ const Crontab = ({ headerStyle, isPhone }: any) => { record.isDisabled === 1 ? 'enable' : 'disable' }`, { - data: [record._id], + data: [record.id], }, ) .then((data: any) => { if (data.code === 200) { const newStatus = record.isDisabled === 1 ? 0 : 1; const result = [...value]; - const i = result.findIndex((x) => x._id === record._id); + const i = result.findIndex((x) => x.id === record.id); result.splice(i, 1, { ...record, isDisabled: newStatus, @@ -583,14 +583,14 @@ const Crontab = ({ headerStyle, isPhone }: any) => { record.isPinned === 1 ? 'unpin' : 'pin' }`, { - data: [record._id], + data: [record.id], }, ) .then((data: any) => { if (data.code === 200) { const newStatus = record.isPinned === 1 ? 0 : 1; const result = [...value]; - const i = result.findIndex((x) => x._id === record._id); + const i = result.findIndex((x) => x.id === record.id); result.splice(i, 1, { ...record, isPinned: newStatus, @@ -682,7 +682,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => { }; const handleCrons = (cron: any) => { - const index = value.findIndex((x) => x._id === cron._id); + const index = value.findIndex((x) => x.id === cron.id); const result = [...value]; cron.nextRunTime = cron_parser .parseExpression(cron.schedule) @@ -700,9 +700,9 @@ const Crontab = ({ headerStyle, isPhone }: any) => { const getCronDetail = (cron: any) => { request - .get(`${config.apiPrefix}crons/${cron._id}`) + .get(`${config.apiPrefix}crons/${cron.id}`) .then((data: any) => { - const index = value.findIndex((x) => x._id === cron._id); + const index = value.findIndex((x) => x.id === cron.id); const result = [...value]; data.data.nextRunTime = cron_parser .parseExpression(data.data.schedule) @@ -795,7 +795,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => { useEffect(() => { if (logCron) { - localStorage.setItem('logCron', logCron._id); + localStorage.setItem('logCron', logCron.id); setIsLogModalVisible(true); } }, [logCron]); @@ -901,7 +901,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => { pageSizeOptions: [10, 20, 50, 100, 200, 500, 1000], }} dataSource={value} - rowKey="_id" + rowKey="id" size="middle" scroll={{ x: 1000, y: tableScrollHeight }} loading={loading} diff --git a/src/pages/crontab/logModal.tsx b/src/pages/crontab/logModal.tsx index 9889c36e..cb6385b3 100644 --- a/src/pages/crontab/logModal.tsx +++ b/src/pages/crontab/logModal.tsx @@ -36,9 +36,9 @@ const CronLogModal = ({ setLoading(true); } request - .get(`${config.apiPrefix}crons/${cron._id}/log`) + .get(`${config.apiPrefix}crons/${cron.id}/log`) .then((data: any) => { - if (localStorage.getItem('logCron') === cron._id) { + if (localStorage.getItem('logCron') === cron.id) { const log = data.data as string; setValue(log || '暂无日志'); setExecuting( diff --git a/src/pages/crontab/modal.tsx b/src/pages/crontab/modal.tsx index e96f7424..bcdad3d8 100644 --- a/src/pages/crontab/modal.tsx +++ b/src/pages/crontab/modal.tsx @@ -21,7 +21,7 @@ const CronModal = ({ const method = cron ? 'put' : 'post'; const payload = { ...values }; if (cron) { - payload._id = cron._id; + payload.id = cron.id; } const { code, data } = await request[method](`${config.apiPrefix}crons`, { data: payload, diff --git a/src/pages/dependence/index.tsx b/src/pages/dependence/index.tsx index 78686b0a..97504c28 100644 --- a/src/pages/dependence/index.tsx +++ b/src/pages/dependence/index.tsx @@ -82,6 +82,12 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { ); }, }, + { + title: '备注', + dataIndex: 'remark', + key: 'remark', + align: 'center' as const, + }, { title: '创建时间', key: 'created', @@ -175,7 +181,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { ), onOk() { request - .delete(`${config.apiPrefix}dependencies`, { data: [record._id] }) + .delete(`${config.apiPrefix}dependencies`, { data: [record.id] }) .then((data: any) => { if (data.code === 200) { handleDependence(data.data[0]); @@ -205,7 +211,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { onOk() { request .put(`${config.apiPrefix}dependencies/reinstall`, { - data: [record._id], + data: [record.id], }) .then((data: any) => { if (data.code === 200) { @@ -231,7 +237,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { if (Array.isArray(dependence)) { result.push(...dependence); } else { - const index = value.findIndex((x) => x._id === dependence._id); + const index = value.findIndex((x) => x.id === dependence.id); result.splice(index, 1, { ...dependence, }); @@ -278,9 +284,9 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { const getDependenceDetail = (dependence: any) => { request - .get(`${config.apiPrefix}dependencies/${dependence._id}`) + .get(`${config.apiPrefix}dependencies/${dependence.id}`) .then((data: any) => { - const index = value.findIndex((x) => x._id === dependence._id); + const index = value.findIndex((x) => x.id === dependence.id); const result = [...value]; result.splice(index, 1, { ...dependence, @@ -307,7 +313,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { useEffect(() => { if (logDependence) { - localStorage.setItem('logDependence', logDependence._id); + localStorage.setItem('logDependence', logDependence.id); setIsLogModalVisible(true); } }, [logDependence]); @@ -328,7 +334,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { } const result = [...value]; for (let i = 0; i < references.length; i++) { - const index = value.findIndex((x) => x._id === references[i]); + const index = value.findIndex((x) => x.id === references[i]); result.splice(index, 1, { ...result[index], status, @@ -340,7 +346,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { setTimeout(() => { const _result = [...value]; for (let i = 0; i < references.length; i++) { - const index = value.findIndex((x) => x._id === references[i]); + const index = value.findIndex((x) => x.id === references[i]); _result.splice(index, 1); } setValue(_result); @@ -372,7 +378,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { rowSelection={rowSelection} pagination={false} dataSource={value} - rowKey="_id" + rowKey="id" size="middle" scroll={{ x: 768, y: tableScrollHeight }} loading={loading} @@ -432,11 +438,11 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { handleCancel={(needRemove?: boolean) => { setIsLogModalVisible(false); if (needRemove) { - const index = value.findIndex((x) => x._id === logDependence._id); + const index = value.findIndex((x) => x.id === logDependence.id); const result = [...value]; result.splice(index, 1); setValue(result); - } else if ([...value].map((x) => x._id).includes(logDependence._id)) { + } else if ([...value].map((x) => x.id).includes(logDependence.id)) { getDependenceDetail(logDependence); } }} diff --git a/src/pages/dependence/logModal.tsx b/src/pages/dependence/logModal.tsx index 0b131394..81b14d90 100644 --- a/src/pages/dependence/logModal.tsx +++ b/src/pages/dependence/logModal.tsx @@ -46,9 +46,9 @@ const DependenceLogModal = ({ const getDependenceLog = () => { setLoading(true); request - .get(`${config.apiPrefix}dependencies/${dependence._id}`) + .get(`${config.apiPrefix}dependencies/${dependence.id}`) .then((data: any) => { - if (localStorage.getItem('logDependence') === dependence._id) { + if (localStorage.getItem('logDependence') === dependence.id) { const log = (data.data.log || []).join('\n') as string; setValue(log); setExecuting(!log.includes('结束时间')); @@ -64,7 +64,7 @@ const DependenceLogModal = ({ setRemoveLoading(true); request .delete(`${config.apiPrefix}dependencies/force`, { - data: [dependence._id], + data: [dependence.id], }) .then((data: any) => { cancel(true); diff --git a/src/pages/dependence/modal.tsx b/src/pages/dependence/modal.tsx index dd7bc484..6c46acbc 100644 --- a/src/pages/dependence/modal.tsx +++ b/src/pages/dependence/modal.tsx @@ -42,7 +42,7 @@ const DependenceModal = ({ payload = [{ name, type }]; } } else { - payload = { ...values, _id: dependence._id }; + payload = { ...values, id: dependence.id }; } try { const { code, data } = await request[method]( diff --git a/src/pages/env/index.tsx b/src/pages/env/index.tsx index 911b1249..cb67e734 100644 --- a/src/pages/env/index.tsx +++ b/src/pages/env/index.tsx @@ -101,7 +101,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => { { title: '序号', align: 'center' as const, - width: 50, + width: 60, render: (text: string, record: any, index: number) => { return {index + 1} ; }, @@ -197,7 +197,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => { { title: '操作', key: 'action', - width: 100, + width: 120, align: 'center' as const, render: (text: string, record: any, index: number) => { const isPc = !isPhone; @@ -270,7 +270,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => { record.status === Status.已禁用 ? 'enable' : 'disable' }`, { - data: [record._id], + data: [record.id], }, ) .then((data: any) => { @@ -321,7 +321,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => { ), onOk() { request - .delete(`${config.apiPrefix}envs`, { data: [record._id] }) + .delete(`${config.apiPrefix}envs`, { data: [record.id] }) .then((data: any) => { if (data.code === 200) { message.success('删除成功'); @@ -351,7 +351,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => { const handleEnv = (env: any) => { const result = [...value]; - const index = value.findIndex((x) => x._id === env._id); + const index = value.findIndex((x) => x.id === env.id); if (index === -1) { env = Array.isArray(env) ? env : [env]; result.push(...env); @@ -376,7 +376,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => { } const dragRow = value[dragIndex]; request - .put(`${config.apiPrefix}envs/${dragRow._id}/move`, { + .put(`${config.apiPrefix}envs/${dragRow.id}/move`, { data: { fromIndex: dragIndex, toIndex: hoverIndex }, }) .then((data: any) => { @@ -534,7 +534,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => { rowSelection={rowSelection} pagination={false} dataSource={value} - rowKey="_id" + rowKey="id" size="middle" scroll={{ x: 1000, y: tableScrollHeight }} components={components} diff --git a/src/pages/env/modal.tsx b/src/pages/env/modal.tsx index 3e1fbd5a..164de6de 100644 --- a/src/pages/env/modal.tsx +++ b/src/pages/env/modal.tsx @@ -34,7 +34,7 @@ const EnvModal = ({ payload = [{ value, name, remarks }]; } } else { - payload = { ...values, _id: env._id }; + payload = { ...values, id: env.id }; } const { code, data } = await request[method](`${config.apiPrefix}envs`, { data: payload, diff --git a/src/pages/log/index.tsx b/src/pages/log/index.tsx index 260db798..f5865ddb 100644 --- a/src/pages/log/index.tsx +++ b/src/pages/log/index.tsx @@ -1,12 +1,11 @@ import { useState, useEffect, useCallback, Key, useRef } from 'react'; -import { TreeSelect, Tree, Input } from 'antd'; +import { TreeSelect, Tree, Input, Empty } from 'antd'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; import Editor from '@monaco-editor/react'; import { request } from '@/utils/http'; import styles from './index.module.less'; import { Controlled as CodeMirror } from 'react-codemirror2'; -import { useCtx, useTheme } from '@/utils/hooks'; import SplitPane from 'react-split-pane'; function getFilterData(keyword: string, data: any) { @@ -143,21 +142,39 @@ const Log = ({ headerStyle, isPhone, theme }: any) => { {!isPhone && (
- -
- -
+ {data.length > 0 ? ( + <> + +
+ +
+ + ) : ( +
+ +
+ )}
{ } }; + const codeInputChange = (e: React.ChangeEvent) => { + const { value } = e.target as any; + const regx = /^[0-9]{6}$/; + if (regx.test(value)) { + completeTowFactor({ code: value }); + } + }; + useEffect(() => { const isAuth = localStorage.getItem(config.authKey); if (isAuth) { @@ -144,12 +152,15 @@ const Login = () => { { pattern: /^[0-9]{6}$/, message: '验证码为6位数字', - validateTrigger: 'onBlur', }, ]} - hasFeedback > - +