Compare commits

..

60 Commits

Author SHA1 Message Date
whyour 325482cbb1 更新版本 v2.13.4 2022-07-16 12:31:50 +08:00
whyour 45e02a55c3 修改npm镜像源 2022-07-16 12:25:14 +08:00
whyour 6b8ef75b51 修复获取环境变量详情 2022-07-16 12:16:33 +08:00
whyour f2535ece76 移除内置token判断 2022-07-11 21:21:35 +08:00
whyour b2afa65cb1 任务详情编辑脚本支持全屏 2022-07-04 22:44:51 +08:00
whyour 3a62cc37ff 环境变量增加导出功能 2022-07-04 22:24:50 +08:00
whyour 93c69826ad 脚本管理按文件类型排序 2022-06-26 22:25:21 +08:00
whyour 70727d0e26 修复mac和linux兼容性 2022-06-26 18:59:39 +08:00
whyour 2578aae0ba 修复手动停止任务耗时日志 2022-06-19 14:32:09 +08:00
whyour 616e56ea95 修复系统token验证 2022-06-18 19:24:56 +08:00
whyour 96ae20de68 修复系统token权限 2022-06-18 19:18:14 +08:00
whyour 6975ff4aa1 修复repo命令 2022-06-18 19:03:50 +08:00
whyour 1d8a1e9c4a 修复repo命令 2022-06-18 19:00:22 +08:00
whyour 2719280cdc 修改结束message 2022-06-18 12:45:38 +08:00
whyour 575e41172f 修改日期打印 2022-06-18 12:43:23 +08:00
whyour bd50b1e976 Merge branch 'develop' of github.com:whyour/qinglong into develop 2022-06-18 12:30:11 +08:00
whyour dfb2572b4f 修复node kill进程逻辑 2022-06-18 12:30:02 +08:00
二毛 7746b18023 区分原生termux和termux中安装的Linux发行版 (#1498)
检测termux时只看环境变量,以便于区分原生termux和termux中安装的Linux发行版
测试发现${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT}在termux中安装的Linux发行版中仍旧存在,但是发行版中应该不用设置软连接吧
2022-06-14 22:55:49 +08:00
whyour 05c738c7c6 修改系统内部获取token方式 2022-06-14 22:43:18 +08:00
whyour e561e12356 修改更新定时任务参数验证 2022-06-12 21:12:01 +08:00
whyour 595b28ba62 移除notify软链命令 2022-06-10 21:23:43 +08:00
whyour 366caed74e 修复根目录环境变量 2022-06-09 16:44:58 +08:00
whyour 2e26a8c24e 修复shell本地开发兼容性 2022-06-09 12:10:41 +08:00
whyour c09c375066 修复无法更新脚本内容为空 2022-06-08 23:57:22 +08:00
whyour 4f93905507 修改默认pip源 2022-06-08 22:54:44 +08:00
whyour f152136f0b 更新版本 v2.13.3 2022-06-08 18:39:51 +08:00
whyour c24d3d6e2f 移除console 2022-06-08 18:39:10 +08:00
whyour 33d820acdb 修改npm peer dependence策略 2022-06-08 11:19:54 +08:00
whyour 78ef75bbfe 移除无用日志 2022-06-08 11:12:15 +08:00
whyour 31541fb87e 修复调试脚本结束条件 2022-06-06 21:25:09 +08:00
whyour 0c169e3f6c 修复调试脚本目录 2022-06-05 21:25:09 +08:00
whyour 73c15cb3bc 修复调试脚本运行需要先保存 2022-06-05 19:26:32 +08:00
whyour 4373a2df49 修复脚本管理和日志管理树字段 2022-06-05 18:44:35 +08:00
whyour cb004cfbf5 修复粘贴订阅私有仓库字段 2022-06-05 11:56:48 +08:00
whyour 7f2d6d159c 修复创建脚本选择目录 2022-06-04 01:26:01 +08:00
whyour 4bba024e4a 新建脚本支持文件上传 2022-06-04 00:12:26 +08:00
whyour acb43f3ad8 更新版本 v2.13.2 2022-06-01 00:28:35 +08:00
whyour b160f914ad 修改订阅执行逻辑 2022-06-01 00:25:32 +08:00
whyour 6116aa6d12 修复私钥配置文件匹配 2022-06-01 00:03:58 +08:00
whyour d43cee53c9 修改interval_schedule验证 2022-05-30 10:26:30 +08:00
whyour defea8ff9b 修复订阅添加定时规则 2022-05-30 10:21:15 +08:00
whyour b290b60d46 修复新建订阅定时 2022-05-30 09:58:51 +08:00
whyour 706b5e8108 修复订阅任务创建逻辑 2022-05-29 22:51:36 +08:00
whyour b55c7d5747 修复依赖安装顺序,优先linux依赖安装 2022-05-29 15:01:48 +08:00
whyour 6b8fd94f0c 系统启动安装依赖优先安装linux依赖 2022-05-29 11:35:31 +08:00
whyour e18710a30a 更新dockerfile 2022-05-29 11:13:32 +08:00
whyour c0f0da4e96 修复dockerfile 2022-05-29 10:18:08 +08:00
whyour 49ef5cd61f 修复pnpm环境变量 2022-05-29 10:02:03 +08:00
whyour 99d7eaeff0 更新版本 v2.13.1 2022-05-29 01:50:46 +08:00
whyour c8f5eb7c2e 修复pnpm版本兼容 2022-05-29 01:50:05 +08:00
whyour 109e16e8fb 修复前端列表查询 2022-05-28 21:49:34 +08:00
whyour 555f6e04e8 修复依赖日志查看 2022-05-28 21:10:24 +08:00
whyour bcde0a54bf 修复依赖安装日志 2022-05-28 20:51:25 +08:00
whyour eee7f25bc3 修复订阅日志查看 2022-05-28 20:11:38 +08:00
whyour b10157b09a 修复系统启动执行订阅任务 2022-05-28 19:56:12 +08:00
whyour a09a21fa5d 依赖改为串行安装 2022-05-28 19:41:37 +08:00
whyour ec2918b427 修改订阅日志排版 2022-05-28 19:04:46 +08:00
whyour 57bfc19ecf 修复execSync卡死主进程 2022-05-28 18:55:52 +08:00
whyour aa110d4187 修复任务详情日志访问,修复新建订阅粘贴repo命令 2022-05-28 17:36:41 +08:00
whyour 9e8febfc11 修复七牛sdk参数 2022-05-28 00:11:43 +08:00
54 changed files with 955 additions and 519 deletions
+3 -3
View File
@@ -7,6 +7,6 @@ LOG_LEVEL='debug'
SECRET='whyour'
QINIU_AK = ''
QINIU_SK = ''
QINIU_SCOPE = ''
QINIU_AK=''
QINIU_SK=''
QINIU_SCOPE=''
+2 -1
View File
@@ -1 +1,2 @@
sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli
sentrycli_cdnurl=https://npmmirror.com/mirrors/sentry-cli/
strict-peer-dependencies=false
-4
View File
@@ -9,10 +9,6 @@ export default defineConfig({
type: 'none',
},
fastRefresh: {},
// antd: {
// dark: true,
// },
mfsu: {},
esbuild: {},
webpack5: {},
dynamicImport: {
-3
View File
@@ -25,7 +25,6 @@ export default (app: Router) => {
}),
});
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -51,7 +50,6 @@ export default (app: Router) => {
}
res.send({ code: 200, data: content });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -76,7 +74,6 @@ export default (app: Router) => {
fs.writeFileSync(path, content);
res.send({ code: 200, message: '保存成功' });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+7 -23
View File
@@ -42,7 +42,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -60,7 +59,6 @@ export default (app: Router) => {
const data = await cronService.run(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -78,7 +76,6 @@ export default (app: Router) => {
const data = await cronService.stop(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -102,7 +99,6 @@ export default (app: Router) => {
);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -123,7 +119,6 @@ export default (app: Router) => {
const data = await cronService.addLabels(req.body.ids, req.body.labels);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -141,7 +136,6 @@ export default (app: Router) => {
const data = await cronService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -159,7 +153,6 @@ export default (app: Router) => {
const data = await cronService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -179,7 +172,6 @@ export default (app: Router) => {
const data = await cronService.log(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -190,9 +182,9 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
labels: Joi.array().optional().allow(null),
command: Joi.string().optional(),
schedule: Joi.string().optional(),
name: Joi.string().optional(),
command: Joi.string().required(),
schedule: Joi.string().required(),
name: Joi.string().optional().allow(null),
id: Joi.number().required(),
}),
}),
@@ -210,7 +202,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -228,7 +219,6 @@ export default (app: Router) => {
const data = await cronService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -246,7 +236,6 @@ export default (app: Router) => {
const data = await cronService.pin(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -264,7 +253,6 @@ export default (app: Router) => {
const data = await cronService.unPin(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -279,7 +267,6 @@ export default (app: Router) => {
const data = await cronService.import_crontab();
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -299,7 +286,6 @@ export default (app: Router) => {
const data = await cronService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -311,10 +297,10 @@ export default (app: Router) => {
body: Joi.object({
ids: Joi.array().items(Joi.number().required()),
status: Joi.string().required(),
pid: Joi.string().optional(),
log_path: Joi.string().optional(),
last_running_time: Joi.number().optional(),
last_execution_time: Joi.number().optional(),
pid: Joi.string().optional().allow(null),
log_path: Joi.string().optional().allow(null),
last_running_time: Joi.number().optional().allow(null),
last_execution_time: Joi.number().optional().allow(null),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@@ -328,7 +314,6 @@ export default (app: Router) => {
});
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -348,7 +333,6 @@ export default (app: Router) => {
const data = await cronService.logs(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
-6
View File
@@ -38,7 +38,6 @@ export default (app: Router) => {
const data = await dependenceService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -61,7 +60,6 @@ export default (app: Router) => {
const data = await dependenceService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -79,7 +77,6 @@ export default (app: Router) => {
const data = await dependenceService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -97,7 +94,6 @@ export default (app: Router) => {
const data = await dependenceService.remove(req.body, true);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -117,7 +113,6 @@ export default (app: Router) => {
const data = await dependenceService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -135,7 +130,6 @@ export default (app: Router) => {
const data = await dependenceService.reInstall(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+1 -9
View File
@@ -38,7 +38,6 @@ export default (app: Router) => {
const data = await envService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -61,7 +60,6 @@ export default (app: Router) => {
const data = await envService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -79,7 +77,6 @@ export default (app: Router) => {
const data = await envService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -103,7 +100,6 @@ export default (app: Router) => {
const data = await envService.move(req.params.id, req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -121,7 +117,6 @@ export default (app: Router) => {
const data = await envService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -139,7 +134,6 @@ export default (app: Router) => {
const data = await envService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -160,7 +154,6 @@ export default (app: Router) => {
const data = await envService.updateNames(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -177,10 +170,9 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const envService = Container.get(EnvService);
const data = await envService.getDb(req.params.id);
const data = await envService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+6 -3
View File
@@ -6,6 +6,7 @@ import config from '../config';
import { getFileContentByName, readDirs } from '../config/util';
import { join } from 'path';
const route = Router();
const blacklist = ['.tmp'];
export default (app: Router) => {
app.use('/logs', route);
@@ -13,7 +14,7 @@ export default (app: Router) => {
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const result = readDirs(config.logPath, config.logPath);
const result = readDirs(config.logPath, config.logPath, blacklist);
res.send({
code: 200,
data: result,
@@ -29,15 +30,17 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
if (blacklist.includes(req.path)) {
return res.send({ code: 403, message: '暂无权限' });
}
const filePath = join(
config.logPath,
req.query.path as string,
(req.query.path || '') as string,
req.params.file,
);
const content = getFileContentByName(filePath);
res.send({ code: 200, data: content });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+1 -7
View File
@@ -16,7 +16,6 @@ export default (app: Router) => {
const data = await openService.list();
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -26,7 +25,7 @@ export default (app: Router) => {
'/apps',
celebrate({
body: Joi.object({
name: Joi.string().optional().allow(''),
name: Joi.string().optional().allow('').disallow('system'),
scopes: Joi.array().items(Joi.string().required()),
}),
}),
@@ -37,7 +36,6 @@ export default (app: Router) => {
const data = await openService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -59,7 +57,6 @@ export default (app: Router) => {
const data = await openService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -77,7 +74,6 @@ export default (app: Router) => {
const data = await openService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -97,7 +93,6 @@ export default (app: Router) => {
const data = await openService.resetSecret(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -118,7 +113,6 @@ export default (app: Router) => {
const result = await openService.authToken(req.query as any);
return res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+44 -31
View File
@@ -3,6 +3,7 @@ import {
getFileContentByName,
readDirs,
getLastModifyFilePath,
readDir,
} from '../config/util';
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
@@ -10,17 +11,38 @@ import { Logger } from 'winston';
import config from '../config';
import * as fs from 'fs';
import { celebrate, Joi } from 'celebrate';
import path, { join } from 'path';
import path, { join, parse } from 'path';
import ScriptService from '../services/script';
import multer from 'multer';
const route = Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, config.scriptPath);
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({ storage: storage });
export default (app: Router) => {
app.use('/scripts', route);
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const result = readDirs(config.scriptPath, config.scriptPath);
let result = [];
const blacklist = ['node_modules', '.git'];
if (req.query.path) {
const targetPath = path.join(
config.scriptPath,
req.query.path as string,
);
result = readDir(targetPath, config.scriptPath, blacklist);
} else {
result = readDirs(config.scriptPath, config.scriptPath, blacklist);
}
res.send({
code: 200,
data: result,
@@ -44,7 +66,6 @@ export default (app: Router) => {
const content = getFileContentByName(filePath);
res.send({ code: 200, data: content });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -52,14 +73,7 @@ export default (app: Router) => {
route.post(
'/',
celebrate({
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().optional().allow(''),
content: Joi.string().allow(''),
originFilename: Joi.string().allow(''),
}),
}),
upload.single('file'),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
@@ -69,6 +83,7 @@ export default (app: Router) => {
content: string;
originFilename: string;
};
if (!path) {
path = config.scriptPath;
}
@@ -84,15 +99,18 @@ export default (app: Router) => {
message: '文件路径禁止访问',
});
}
if (req.file) {
fs.renameSync(req.file.path, join(path, req.file.filename));
return res.send({ code: 200 });
}
if (!originFilename) {
originFilename = filename;
}
const originFilePath = `${path}${originFilename.replace(/\//g, '')}`;
const filePath = `${path}${filename.replace(/\//g, '')}`;
if (fs.existsSync(originFilePath)) {
if (!fs.existsSync(config.bakPath)) {
fs.mkdirSync(config.bakPath);
}
fs.copyFileSync(
originFilePath,
`${config.bakPath}${originFilename.replace(/\//g, '')}`,
@@ -104,7 +122,6 @@ export default (app: Router) => {
fs.writeFileSync(filePath, content);
return res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -116,7 +133,7 @@ export default (app: Router) => {
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().optional().allow(''),
content: Joi.string().required(),
content: Joi.string().required().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@@ -131,7 +148,6 @@ export default (app: Router) => {
fs.writeFileSync(filePath, content);
return res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -156,7 +172,6 @@ export default (app: Router) => {
fs.unlinkSync(filePath);
res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -187,7 +202,6 @@ export default (app: Router) => {
return next(err);
});
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -198,22 +212,22 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
filename: Joi.string().required(),
content: Joi.string().optional().allow(''),
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);
let { filename, content, path } = req.body;
const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
fs.writeFileSync(filePath, content || '', { encoding: 'utf8' });
const scriptService = Container.get(ScriptService);
const result = await scriptService.runScript(filePath);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -224,22 +238,21 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
filename: Joi.string().required(),
content: Joi.string().optional().allow(''),
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);
let { filename, content, path } = req.body;
const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
const scriptService = Container.get(ScriptService);
const result = await scriptService.stopScript(filePath);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+7 -12
View File
@@ -29,7 +29,13 @@ export default (app: Router) => {
body: Joi.object({
type: Joi.string().required(),
schedule: Joi.string().optional().allow('').allow(null),
interval_schedule: Joi.object().optional().allow('').allow(null),
interval_schedule: Joi.object({
type: Joi.string().required(),
value: Joi.number().min(1).required(),
})
.optional()
.allow('')
.allow(null),
name: Joi.string().optional().allow('').allow(null),
url: Joi.string().required(),
whitelist: Joi.string().optional().allow('').allow(null),
@@ -59,7 +65,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -77,7 +82,6 @@ export default (app: Router) => {
const data = await subscriptionService.run(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -95,7 +99,6 @@ export default (app: Router) => {
const data = await subscriptionService.stop(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -113,7 +116,6 @@ export default (app: Router) => {
const data = await subscriptionService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -131,7 +133,6 @@ export default (app: Router) => {
const data = await subscriptionService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -151,7 +152,6 @@ export default (app: Router) => {
const data = await subscriptionService.log(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -195,7 +195,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -213,7 +212,6 @@ export default (app: Router) => {
const data = await subscriptionService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -233,7 +231,6 @@ export default (app: Router) => {
const data = await subscriptionService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -260,7 +257,6 @@ export default (app: Router) => {
});
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -280,7 +276,6 @@ export default (app: Router) => {
const data = await subscriptionService.logs(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
-5
View File
@@ -54,7 +54,6 @@ export default (app: Router) => {
const data = await systemService.getLogRemoveFrequency();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -76,7 +75,6 @@ export default (app: Router) => {
);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -91,7 +89,6 @@ export default (app: Router) => {
const result = await systemService.checkUpdate();
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -106,7 +103,6 @@ export default (app: Router) => {
const result = await systemService.updateSystem();
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -127,7 +123,6 @@ export default (app: Router) => {
const result = await systemService.notify(req.body);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
-13
View File
@@ -39,7 +39,6 @@ export default (app: Router) => {
const data = await userService.login({ ...req.body }, req);
return res.send(data);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -54,7 +53,6 @@ export default (app: Router) => {
await userService.logout(req.platform);
res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -75,7 +73,6 @@ export default (app: Router) => {
await userService.updateUsernameAndPassword(req.body);
res.send({ code: 200, message: '更新成功' });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -109,7 +106,6 @@ export default (app: Router) => {
const data = await userService.initTwoFactor();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -129,7 +125,6 @@ export default (app: Router) => {
const data = await userService.activeTwoFactor(req.body.code);
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -144,7 +139,6 @@ export default (app: Router) => {
const data = await userService.deactiveTwoFactor();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -166,7 +160,6 @@ export default (app: Router) => {
const data = await userService.twoFactorLogin(req.body, req);
res.send(data);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -181,7 +174,6 @@ export default (app: Router) => {
const data = await userService.getLoginLog();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -196,7 +188,6 @@ export default (app: Router) => {
const data = await userService.getNotificationMode();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -211,7 +202,6 @@ export default (app: Router) => {
const result = await userService.updateNotificationMode(req.body);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -232,7 +222,6 @@ export default (app: Router) => {
await userService.updateUsernameAndPassword(req.body);
res.send({ code: 200, message: '更新成功' });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -247,7 +236,6 @@ export default (app: Router) => {
const result = await userService.updateNotificationMode(req.body);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -263,7 +251,6 @@ export default (app: Router) => {
const result = await userService.updateAvatar(req.file!.filename);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+2 -2
View File
@@ -9,12 +9,12 @@ import Logger from './loaders/logger';
async function startServer() {
const app = express();
await require('./loaders/db').default();
await require('./loaders/initFile').default();
await require('./loaders/sentry').default({ expressApp: app });
await require('./loaders/db').default();
await require('./loaders/app').default({ expressApp: app });
const server = app
+1
View File
@@ -69,6 +69,7 @@ export default {
'cookie.sh',
'crontab.list',
'env.sh',
'token.json',
],
writePathList: [configPath, scriptPath],
bakPath,
+69 -19
View File
@@ -2,6 +2,7 @@ import * as fs from 'fs';
import * as path from 'path';
import got from 'got';
import iconv from 'iconv-lite';
import { exec } from 'child_process';
export function getFileContentByName(fileName: string) {
if (fs.existsSync(fileName)) {
@@ -277,31 +278,80 @@ export async function concurrentRun(
return replyList;
}
export function readDirs(dir: string, baseDir: string = '') {
enum FileType {
'directory',
'file',
}
export function readDirs(
dir: string,
baseDir: string = '',
blacklist: string[] = [],
) {
const relativePath = path.relative(baseDir, dir);
const files = fs.readdirSync(dir);
const result: any = files.map((file: string) => {
const subPath = path.join(dir, file);
const stats = fs.statSync(subPath);
const key = path.join(relativePath, file);
if (stats.isDirectory()) {
const result: any = files
.filter((x) => !blacklist.includes(x))
.map((file: string) => {
const subPath = path.join(dir, file);
const stats = fs.statSync(subPath);
const key = path.join(relativePath, file);
if (stats.isDirectory()) {
return {
title: file,
key,
type: 'directory',
disabled: true,
parent: relativePath,
children: readDirs(subPath, baseDir).sort(
(a: any, b: any) =>
(FileType as any)[a.type] - (FileType as any)[b.type],
),
};
}
return {
title: file,
value: file,
type: 'file',
key,
type: 'directory',
disabled: true,
parent: relativePath,
children: readDirs(subPath, baseDir),
};
}
return {
title: file,
value: file,
type: 'file',
key,
parent: relativePath,
};
});
});
return result.sort(
(a: any, b: any) => (FileType as any)[a.type] - (FileType as any)[b.type],
);
}
export function readDir(
dir: string,
baseDir: string = '',
blacklist: string[] = [],
) {
const relativePath = path.relative(baseDir, dir);
const files = fs.readdirSync(dir);
const result: any = files
.filter((x) => !blacklist.includes(x))
.map((file: string) => {
const subPath = path.join(dir, file);
const stats = fs.statSync(subPath);
const key = path.join(relativePath, file);
return {
title: file,
type: stats.isDirectory() ? 'directory' : 'file',
key,
parent: relativePath,
};
});
return result;
}
export function promiseExec(command: string): Promise<string> {
return new Promise((resolve, reject) => {
exec(
command,
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
(err, stdout, stderr) => {
resolve(stdout || stderr || JSON.stringify(err));
},
);
});
}
+1 -3
View File
@@ -18,9 +18,7 @@ export default async () => {
await AppModel.sync();
await AuthModel.sync();
await EnvModel.sync();
await SubscriptionModel.sync({ alter: true });
await sequelize.sync();
await SubscriptionModel.sync();
// try {
// const queryInterface = sequelize.getQueryInterface();
+26
View File
@@ -2,6 +2,7 @@ import path from 'path';
import fs from 'fs';
import chokidar from 'chokidar';
import config from '../config/index';
import { promiseExec } from '../config/util';
function linkToNodeModule(src: string, dst?: string) {
const target = path.join(config.rootPath, 'node_modules', dst || src);
@@ -16,7 +17,32 @@ function linkToNodeModule(src: string, dst?: string) {
});
}
async function linkCommand() {
const commandPath = await promiseExec('which node');
const commandDir = path.dirname(commandPath);
const linkShell = [
{
src: 'update.sh',
dest: 'ql',
},
{
src: 'task.sh',
dest: 'task',
},
];
for (const link of linkShell) {
const source = path.join(config.rootPath, 'shell', link.src);
const target = path.join(commandDir, link.dest);
if (fs.existsSync(target)) {
fs.unlinkSync(target);
}
fs.symlink(source, target, (err) => {});
}
}
export default async (src: string = 'deps') => {
await linkCommand();
linkToNodeModule(src);
const source = path.join(config.rootPath, src);
+23 -8
View File
@@ -80,13 +80,6 @@ export default ({ app }: { app: Application }) => {
) {
return next();
}
const remoteAddress = req.socket.remoteAddress;
if (
remoteAddress === '::ffff:127.0.0.1' &&
originPath === '/api/crons/status'
) {
return next();
}
const data = fs.readFileSync(config.authConfigFile, 'utf8');
if (data) {
@@ -136,7 +129,6 @@ export default ({ app }: { app: Application }) => {
});
app.use(errors());
app.use(Sentry.Handlers.errorHandler());
app.use(
(
@@ -155,6 +147,29 @@ export default ({ app }: { app: Application }) => {
},
);
app.use(
(
err: Error & { errors: any[] },
req: Request,
res: Response,
next: NextFunction,
) => {
if (err.name.includes('Sequelize')) {
return res
.status(500)
.send({
code: 400,
message: `${err.name} ${err.message}`,
validation: err.errors,
})
.end();
}
return next(err);
},
);
app.use(Sentry.Handlers.errorHandler());
app.use(
(
err: Error & { status: number },
+10 -11
View File
@@ -21,18 +21,17 @@ export default async () => {
);
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
DependenceModel.findAll({ where: {}, raw: true }).then(async (docs) => {
DependenceModel.findAll({
where: {},
order: [['type', 'DESC']],
raw: true,
}).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);
for (const dep of depIds) {
if (dep) {
await dependenceService.reInstall([dep]);
}
}
}
const keys = Object.keys(groups).sort((a, b) => parseInt(b) - parseInt(a));
for (const key of keys) {
const group = groups[key];
const depIds = group.map((x) => x.id);
await dependenceService.reInstall(depIds as number[]);
}
});
+14
View File
@@ -11,6 +11,7 @@ const configPath = path.join(dataPath, 'config/');
const scriptPath = path.join(dataPath, 'scripts/');
const logPath = path.join(dataPath, 'log/');
const uploadPath = path.join(dataPath, 'upload/');
const bakPath = path.join(dataPath, 'bak/');
const samplePath = path.join(rootPath, 'sample/');
const confFile = path.join(configPath, 'config.sh');
const authConfigFile = path.join(configPath, 'auth.json');
@@ -27,6 +28,7 @@ export default async () => {
const configDirExist = await fileExist(configPath);
const uploadDirExist = await fileExist(uploadPath);
const sshDirExist = await fileExist(sshPath);
const bakDirExist = await fileExist(bakPath);
if (!configDirExist) {
fs.mkdirSync(configPath);
@@ -56,7 +58,19 @@ export default async () => {
fs.mkdirSync(sshPath);
}
if (!bakDirExist) {
fs.mkdirSync(bakPath);
}
dotenv.config({ path: confFile });
// 声明QL_DIR环境变量
let qlHomePath = path.join(__dirname, '../../');
// 生产环境
if (qlHomePath.endsWith('/static/')) {
qlHomePath = path.join(qlHomePath, '../');
}
process.env.QL_DIR = qlHomePath;
Logger.info('✌️ Init file down');
};
+6 -1
View File
@@ -26,6 +26,11 @@ export default async () => {
// 运行所有订阅
const subs = await subscriptionService.list();
for (const sub of subs) {
await subscriptionService.handleTask(sub);
await subscriptionService.handleTask(
sub,
!sub.is_disabled,
true,
!sub.is_disabled,
);
}
};
+13 -3
View File
@@ -10,6 +10,7 @@ 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 {
@@ -197,13 +198,21 @@ export default class CronService {
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## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `,
`${str}\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)}${diffTimeStr}`,
);
}
}
@@ -228,6 +237,7 @@ export default class CronService {
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 {
+33 -24
View File
@@ -13,6 +13,8 @@ import _ from 'lodash';
import { spawn } from 'child_process';
import SockService from './sock';
import { Op } from 'sequelize';
import { concurrentRun } from '../config/util';
import dayjs from 'dayjs';
@Service()
export default class DependenceService {
@@ -27,7 +29,7 @@ export default class DependenceService {
return tab;
});
const docs = await this.insert(tabs);
this.installOrUninstallDependencies(docs);
this.installDependenceOneByOne(docs);
return docs;
}
@@ -47,7 +49,7 @@ export default class DependenceService {
status: DependenceStatus.installing,
});
const newDoc = await this.updateDb(tab);
this.installOrUninstallDependencies([newDoc]);
this.installDependenceOneByOne([newDoc]);
return newDoc;
}
@@ -62,7 +64,7 @@ export default class DependenceService {
{ where: { id: ids } },
);
const docs = await DependenceModel.findAll({ where: { id: ids } });
this.installOrUninstallDependencies(docs, false, force);
this.installDependenceOneByOne(docs, false, force);
return docs;
}
@@ -98,6 +100,20 @@ export default class DependenceService {
}
}
private installDependenceOneByOne(
docs: Dependence[],
isInstall: boolean = true,
force: boolean = false,
) {
concurrentRun(
docs.map(
(dep) => async () =>
await this.installOrUninstallDependencies([dep], isInstall, force),
),
1,
);
}
public async reInstall(ids: number[]): Promise<Dependence[]> {
await DependenceModel.update(
{ status: DependenceStatus.installing, log: [] },
@@ -105,7 +121,7 @@ export default class DependenceService {
);
const docs = await DependenceModel.findAll({ where: { id: ids } });
this.installOrUninstallDependencies(docs);
this.installDependenceOneByOne(docs);
return docs;
}
@@ -146,20 +162,17 @@ export default class DependenceService {
)[dependencies[0].type as any];
const actionText = isInstall ? '安装' : '删除';
const depIds = dependencies.map((x) => x.id) as number[];
const startTime = Date.now();
const startTime = dayjs();
const message = `开始${actionText}依赖 ${depNames},开始时间 ${startTime.format(
'YYYY-MM-DD HH:mm:ss',
)}\n\n`;
this.sockService.sendMessage({
type: socketMessageType,
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
startTime,
).toLocaleString()}\n\n`,
message,
references: depIds,
});
await this.updateLog(
depIds,
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
startTime,
).toLocaleString()}\n\n`,
);
await this.updateLog(depIds, message);
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
@@ -192,23 +205,19 @@ export default class DependenceService {
});
cp.on('close', async (code) => {
const endTime = Date.now();
const endTime = dayjs();
const isSucceed = code === 0;
const resultText = isSucceed ? '成功' : '失败';
const message = `\n依赖${actionText}${resultText},结束时间 ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)},耗时 ${endTime.diff(startTime, 'second')}`;
this.sockService.sendMessage({
type: socketMessageType,
message: `\n依赖${actionText}${resultText},结束时间 ${new Date(
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
message,
references: depIds,
});
await this.updateLog(
depIds,
`\n依赖${actionText}${resultText},结束时间 ${new Date(
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
);
await this.updateLog(depIds, message);
let status = null;
if (isSucceed) {
+34 -1
View File
@@ -90,7 +90,9 @@ export default class OpenService {
}
try {
const result = await this.find(condition);
return result.map((x) => ({ ...x, tokens: [] }));
return result
.filter((x) => x.name !== 'system')
.map((x) => ({ ...x, tokens: [] }));
} catch (error) {
throw error;
}
@@ -142,4 +144,35 @@ export default class OpenService {
return { code: 400, message: 'client_id或client_seret有误' };
}
}
public async findSystemToken(): Promise<{
value: string;
expiration: number;
}> {
let systemApp = (await AppModel.findOne({
where: { name: 'system' },
})) as App;
if (!systemApp) {
systemApp = await this.create({
name: 'system',
scopes: ['crons', 'system'],
} as App);
}
const nowTime = Math.round(Date.now() / 1000);
let token;
if (
!systemApp.tokens ||
!systemApp.tokens.length ||
nowTime > [...systemApp.tokens].pop()!.expiration
) {
const authToken = await this.authToken({
client_id: systemApp.client_id,
client_secret: systemApp.client_secret,
});
token = authToken.data;
} else {
token = [...systemApp.tokens].pop();
}
return token;
}
}
+17 -1
View File
@@ -18,6 +18,7 @@ interface ScheduleTaskType {
}
export interface TaskCallbacks {
onBefore?: (startTime: dayjs.Dayjs) => Promise<void>;
onStart?: (
cp: ChildProcessWithoutNullStreams,
startTime: dayjs.Dayjs,
@@ -45,6 +46,8 @@ export default class ScheduleService {
return new Promise(async (resolve, reject) => {
try {
const startTime = dayjs();
await callbacks.onBefore?.(startTime);
const cp = spawn(command, { shell: '/bin/bash' });
// TODO:
@@ -106,6 +109,7 @@ export default class ScheduleService {
async createCronTask(
{ id = 0, command, name, schedule = '' }: ScheduleTaskType,
callbacks?: TaskCallbacks,
runImmediately = false,
) {
const _id = this.formatId(id);
this.logger.info(
@@ -122,6 +126,10 @@ export default class ScheduleService {
await this.runTask(command, callbacks);
}),
);
if (runImmediately) {
await this.runTask(command, callbacks);
}
}
async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
@@ -160,9 +168,17 @@ export default class ScheduleService {
},
);
const job = new LongIntervalJob({ ...schedule, runImmediately }, task, _id);
const job = new LongIntervalJob(
{ ...schedule, runImmediately: false },
task,
_id,
);
this.intervalSchedule.addIntervalJob(job);
if (runImmediately) {
await this.runTask(command, callbacks);
}
}
async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
+34 -32
View File
@@ -1,8 +1,11 @@
import { Service, Inject } from 'typedi';
import winston from 'winston';
import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
import SockService from './sock';
import CronService from './cron';
import ScheduleService, { TaskCallbacks } from './schedule';
import config from '../config';
@Service()
export default class ScriptService {
@@ -10,44 +13,43 @@ export default class ScriptService {
@Inject('logger') private logger: winston.Logger,
private sockService: SockService,
private cronService: CronService,
private scheduleService: ScheduleService,
) {}
public async runScript(path: string) {
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
private taskCallbacks(filePath: string): TaskCallbacks {
return {
onEnd: async (cp, endTime, diff) => {
try {
fs.unlinkSync(filePath);
} catch (error) {}
},
onError: async (message: string) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message,
});
},
onLog: async (message: string) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message,
});
},
};
}
cp.stdout.on('data', (data) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: data.toString(),
});
});
cp.stderr.on('data', (data) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: data.toString(),
});
});
cp.on('error', (err) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: JSON.stringify(err),
});
});
cp.on('close', (err) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: `执行结束`,
});
});
public async runScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const command = `task -l ${relativePath} now`;
this.scheduleService.runTask(command, this.taskCallbacks(filePath));
return { code: 200 };
}
public async stopScript(path: string) {
const err = await this.cronService.killTask(`task -l ${path} now`);
public async stopScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const err = await this.cronService.killTask(`task -l ${relativePath} now`);
const str = err ? `\n${err}` : '';
this.sockService.sendMessage({
type: 'manuallyRunScript',
+13 -5
View File
@@ -23,6 +23,13 @@ export default class SshKeyService {
}
}
private getConfigRegx(alias: string) {
return new RegExp(
`Host ${alias}\n.*[^StrictHostKeyChecking]*.*[\n]*.*StrictHostKeyChecking no`,
'g',
);
}
private removePrivateKeyFile(alias: string): void {
try {
fs.unlinkSync(`${this.sshPath}/${alias}`);
@@ -47,24 +54,25 @@ export default class SshKeyService {
}
}
private removeSshConfig(config: string) {
private removeSshConfig(alias: string) {
try {
const configRegx = this.getConfigRegx(alias);
const data = fs
.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' })
.replace(config, '')
.replace(/\n\n+/, '\n\n');
.replace(configRegx, '')
.replace(/\n[\n]+/g, '\n');
fs.writeFileSync(this.sshConfigFilePath, data, {
encoding: 'utf8',
});
} catch (error) {
this.logger.error(`删除ssh配置文件${config}失败`, error);
this.logger.error(`删除ssh配置文件${alias}失败`, error);
}
}
public addSSHKey(key: string, alias: string, host: string): void {
this.generatePrivateKeyFile(alias, key);
const config = this.generateSingleSshConfig(alias, host);
this.removeSshConfig(config);
this.removeSshConfig(alias);
this.generateSshConfig([config]);
}
+80 -54
View File
@@ -28,6 +28,7 @@ import ScheduleService, { TaskCallbacks } from './schedule';
import { SimpleIntervalSchedule } from 'toad-scheduler';
import SockService from './sock';
import SshKeyService from './sshKey';
import dayjs from 'dayjs';
@Service()
export default class SubscriptionService {
@@ -124,7 +125,12 @@ export default class SubscriptionService {
return { url, host };
}
public handleTask(doc: Subscription, needCreate = true, needAddKey = true) {
public async handleTask(
doc: Subscription,
needCreate = true,
needAddKey = true,
runImmediately = false,
) {
const { url, host } = this.formatUrl(doc);
if (doc.type === 'private-repo' && doc.pull_type === 'ssh-key') {
if (needAddKey) {
@@ -143,23 +149,36 @@ export default class SubscriptionService {
if (doc.schedule_type === 'crontab') {
this.scheduleService.cancelCronTask(doc as any);
needCreate &&
this.scheduleService.createCronTask(
(await this.scheduleService.createCronTask(
doc as any,
this.taskCallbacks(doc),
);
runImmediately,
));
} else {
this.scheduleService.cancelIntervalTask(doc as any);
const { type, value } = doc.interval_schedule as any;
needCreate &&
this.scheduleService.createIntervalTask(
(await this.scheduleService.createIntervalTask(
doc as any,
{ [type]: value } as SimpleIntervalSchedule,
true,
runImmediately,
this.taskCallbacks(doc),
);
));
}
}
private async promiseExec(command: string): Promise<string> {
return new Promise((resolve, reject) => {
exec(
command,
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
(err, stdout, stderr) => {
resolve(stdout || stderr || JSON.stringify(err));
},
);
});
}
private async handleLogPath(
logPath: string,
data: string = '',
@@ -174,32 +193,39 @@ export default class SubscriptionService {
private taskCallbacks(doc: Subscription): TaskCallbacks {
return {
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
// 执行sub_before
let beforeStr = '';
try {
if (doc.sub_before) {
beforeStr = execSync(doc.sub_before).toString();
}
} catch (error) {
beforeStr = JSON.stringify(error);
}
if (beforeStr) {
beforeStr += '\n';
}
onBefore: async (startTime) => {
const logTime = startTime.format('YYYY-MM-DD-HH-mm-ss');
const logPath = `${doc.alias}/${logTime}.log`;
await this.handleLogPath(
logPath as string,
`${beforeStr}## 开始执行... ${startTime.format(
'YYYY-MM-DD HH:mm:ss',
)}\n`,
);
await SubscriptionModel.update(
{
status: SubscriptionStatus.running,
log_path: logPath,
},
{ where: { id: doc.id } },
);
const absolutePath = await this.handleLogPath(
logPath as string,
`## 开始执行... ${startTime.format('YYYY-MM-DD HH:mm:ss')}\n`,
);
// 执行sub_before
let beforeStr = '';
try {
if (doc.sub_before) {
fs.appendFileSync(absolutePath, `\n## 执行before命令...\n\n`);
beforeStr = await this.promiseExec(doc.sub_before);
}
} catch (error: any) {
beforeStr =
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
}
if (beforeStr) {
fs.appendFileSync(absolutePath, `${beforeStr}\n`);
}
},
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
await SubscriptionModel.update(
{
pid: cp.pid,
},
{ where: { id: doc.id } },
@@ -207,11 +233,23 @@ export default class SubscriptionService {
},
onEnd: async (cp, endTime, diff) => {
const sub = await this.getDb({ id: doc.id });
await SubscriptionModel.update(
{ status: SubscriptionStatus.idle, pid: undefined },
{ where: { id: sub.id } },
);
const absolutePath = await this.handleLogPath(sub.log_path as string);
// 执行 sub_after
let afterStr = '';
try {
if (sub.sub_after) {
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n\n`);
afterStr = await this.promiseExec(sub.sub_after);
}
} catch (error: any) {
afterStr =
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
}
if (afterStr) {
fs.appendFileSync(absolutePath, `${afterStr}\n`);
}
fs.appendFileSync(
absolutePath,
`\n## 执行结束... ${endTime.format(
@@ -219,20 +257,10 @@ export default class SubscriptionService {
)} 耗时 ${diff}`,
);
// 执行 sub_after
let afterStr = '';
try {
if (sub.sub_after) {
afterStr = execSync(sub.sub_after).toString();
}
} catch (error) {
afterStr = JSON.stringify(error);
}
if (afterStr) {
afterStr = `\n\n${afterStr}`;
const absolutePath = await this.handleLogPath(sub.log_path as string);
fs.appendFileSync(absolutePath, afterStr);
}
await SubscriptionModel.update(
{ status: SubscriptionStatus.idle, pid: undefined },
{ where: { id: sub.id } },
);
this.sockService.sendMessage({
type: 'runSubscriptionEnd',
@@ -256,7 +284,7 @@ export default class SubscriptionService {
public async create(payload: Subscription): Promise<Subscription> {
const tab = new Subscription(payload);
const doc = await this.insert(tab);
this.handleTask(doc);
await this.handleTask(doc);
return doc;
}
@@ -266,7 +294,7 @@ export default class SubscriptionService {
public async update(payload: Subscription): Promise<Subscription> {
const newDoc = await this.updateDb(payload);
this.handleTask(newDoc);
await this.handleTask(newDoc, !newDoc.is_disabled);
return newDoc;
}
@@ -309,7 +337,7 @@ export default class SubscriptionService {
public async remove(ids: number[]) {
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
for (const doc of docs) {
this.handleTask(doc, false, false);
await this.handleTask(doc, false, false);
}
await SubscriptionModel.destroy({ where: { id: ids } });
}
@@ -340,16 +368,14 @@ export default class SubscriptionService {
this.logger.silly(error);
}
}
this.handleTask(doc, false);
const command = this.formatCommand(doc);
const err = await this.killTask(command);
const absolutePath = await this.handleLogPath(doc.log_path as string);
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `,
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `,
);
}
@@ -411,7 +437,7 @@ export default class SubscriptionService {
public async disabled(ids: number[]) {
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
for (const doc of docs) {
this.handleTask(doc, false);
await this.handleTask(doc, false);
}
await SubscriptionModel.update({ is_disabled: 1 }, { where: { id: ids } });
}
@@ -419,14 +445,14 @@ export default class SubscriptionService {
public async enabled(ids: number[]) {
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
for (const doc of docs) {
this.handleTask(doc);
await this.handleTask(doc);
}
await SubscriptionModel.update({ is_disabled: 0 }, { where: { id: ids } });
}
public async log(id: number) {
const doc = await this.getDb({ id });
if (!doc) {
if (!doc || !doc.log_path) {
return '';
}
+3 -3
View File
@@ -13,7 +13,7 @@ import { Request } from 'express';
import ScheduleService from './schedule';
import { spawn } from 'child_process';
import SockService from './sock';
import got from 'got';
import dayjs from 'dayjs';
@Service()
export default class UserService {
@@ -111,7 +111,7 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${new Date(timestamp).toLocaleString()}${address} ${
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录成功,ip地址 ${ip}`,
);
@@ -140,7 +140,7 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${new Date(timestamp).toLocaleString()}${address} ${
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录失败,ip地址 ${ip}`,
);
+2 -3
View File
@@ -5,7 +5,8 @@ LABEL maintainer="${QL_MAINTAINER}"
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
ARG QL_BRANCH=master
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules \
ENV PNPM_HOME=/root/.local/share/pnpm \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules:$PNPM_HOME \
LANG=zh_CN.UTF-8 \
SHELL=/bin/bash \
PS1="\u@\h:\w \$ " \
@@ -38,8 +39,6 @@ RUN set -x \
&& git config --global user.email "qinglong@@users.noreply.github.com" \
&& git config --global user.name "qinglong" \
&& npm install -g pnpm \
&& pnpm setup \
&& source ~/.bashrc \
&& pnpm add -g pm2 ts-node typescript tslib \
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
&& cd ${QL_DIR} \
+6 -10
View File
@@ -31,17 +31,13 @@
"allowedVersions": {
"react": "17",
"react-dom": "17"
},
"ignoreMissing": [
"monaco-editor",
"react-router"
]
}
}
},
"dependencies": {
"@otplib/preset-default": "^12.0.1",
"@sentry/node": "^6.18.1",
"@sentry/tracing": "^6.18.1",
"@sentry/node": "^7.0.0",
"@sentry/tracing": "^7.0.0",
"body-parser": "^1.19.2",
"celebrate": "^15.0.1",
"chokidar": "^3.5.3",
@@ -63,10 +59,10 @@
"nodemailer": "^6.7.2",
"p-queue": "7.2.0",
"reflect-metadata": "^0.1.13",
"sequelize": "^6.17.0",
"sequelize": "^6.20.1",
"serve-handler": "^6.1.3",
"sockjs": "^0.3.24",
"sqlite3": "npm:@louislam/sqlite3@^15.0.3",
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
"toad-scheduler": "^1.6.0",
"typedi": "^0.10.0",
"uuid": "^8.3.2",
@@ -77,7 +73,7 @@
"@ant-design/icons": "^4.7.0",
"@ant-design/pro-layout": "^6.33.1",
"@monaco-editor/react": "^4.3.1",
"@sentry/react": "^6.18.1",
"@sentry/react": "^7.0.0",
"@types/body-parser": "^1.19.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
+1 -1
View File
@@ -39,7 +39,7 @@ AutoStartBot=""
BotRepoUrl=""
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple"
PipMirror="https://pypi.doubanio.com/simple/"
## 通知环境变量
## 1. Server酱
+1 -1
View File
@@ -17,7 +17,7 @@ const config = new qiniu.conf.Config({ zone: qiniu.zone.Zone_z1 });
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra(
'',
'',
{},
'text/plain; charset=utf-8',
);
// 文件上传
+8 -26
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
get_token() {
token=$(cat $file_auth_user | jq -r .token)
token=$(ts-node-transpile-only "$dir_shell/token.ts")
}
add_cron_api() {
@@ -17,7 +17,7 @@ add_cron_api() {
fi
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
@@ -52,7 +52,7 @@ update_cron_api() {
fi
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -84,7 +84,7 @@ update_cron_command_api() {
fi
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -109,7 +109,7 @@ del_cron_api() {
local ids=$1
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-X 'DELETE' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -130,24 +130,6 @@ del_cron_api() {
fi
}
get_user_info() {
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/user?t=$currentTimeStamp" \
-H 'Accept: */*' \
-H "Authorization: Bearer $token" \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \
-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' \
--compressed
)
code=$(echo $api | jq -r .code)
if [[ $code != 200 ]]; then
echo -e "请先登录!"
exit 0
fi
}
update_cron() {
local ids="$1"
local status="$2"
@@ -157,7 +139,7 @@ update_cron() {
local runningTime="${6:-0}"
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons/status?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -172,7 +154,7 @@ update_cron() {
code=$(echo $api | jq -r .code)
message=$(echo $api | jq -r .message)
if [[ $code != 200 ]]; then
echo -e "\n## 更新任务状态失败(${message})\n" >> $log_path
echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path
fi
}
@@ -181,7 +163,7 @@ notify_api() {
local content=$2
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/system/notify?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
+1
View File
@@ -88,6 +88,7 @@ check_pm2() {
main() {
echo -e "=====> 开始检测"
npm i -g pnpm
pnpm add -g pm2
copy_dep
check_ql
+39 -7
View File
@@ -14,7 +14,6 @@ dir_log=$dir_data/log
dir_db=$dir_data/db
dir_dep=$dir_data/deps
dir_list_tmp=$dir_log/.tmp
dir_code=$dir_log/code
dir_update_log=$dir_log/update
ql_static_repo=$dir_repo/static
@@ -52,12 +51,10 @@ list_own_drop=$dir_list_tmp/own_drop.list
link_name=(
task
ql
notify
)
original_name=(
task.sh
update.sh
notify.sh
)
init_env() {
@@ -101,7 +98,7 @@ make_dir() {
}
detect_termux() {
if [[ ${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT} ]] || [[ $PATH == *com.termux* ]]; then
if [[ $PATH == *com.termux* ]]; then
is_termux=1
else
is_termux=0
@@ -248,11 +245,11 @@ fix_config() {
npm_install_sub() {
set_proxy
if [ $is_termux -eq 1 ]; then
npm install --production --no-bin-links --registry=https://registry.npm.taobao.org || npm install --production --no-bin-links
npm install --production --no-bin-links --registry=https://registry.npmmirror.com || npm install --production --no-bin-links
elif ! type pnpm &>/dev/null; then
npm install --production --registry=https://registry.npm.taobao.org || npm install --production
npm install --production --registry=https://registry.npmmirror.com || npm install --production
else
pnpm install --loglevel error --production --registry=https://registry.npm.taobao.org || pnpm install --production --loglevel error
pnpm install --loglevel error --production --registry=https://registry.npmmirror.com || pnpm install --production --loglevel error
fi
unset_proxy
}
@@ -380,6 +377,41 @@ reload_pm2() {
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
}
diff_time() {
local format="$1"
local begin_time="$2"
local end_time="$3"
if [[ $is_macos -eq 1 ]]; then
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
else
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
fi
echo "$diff_time"
}
format_time() {
local format="$1"
local time="$2"
if [[ $is_macos -eq 1 ]]; then
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
else
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
fi
}
format_timestamp() {
local format="$1"
local time="$2"
if [[ $is_macos -eq 1 ]]; then
echo $(date -j -f "$format" "$time" "+%s")
else
echo $(date -d "$time" "+%s")
fi
}
init_env
detect_termux
detect_macos
+31 -25
View File
@@ -84,8 +84,8 @@ run_normal() {
fi
fi
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+%Y-%m-%d-%H-%M-%S")
log_time="$time"
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -99,12 +99,12 @@ run_normal() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -118,12 +118,13 @@ run_normal() {
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
eval $timeoutCmd $which_program $file_param $cmd
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local diff_time=$(( $end_timestamp - $begin_timestamp ))
local diff_time=$(expr $end_timestamp - $begin_timestamp)
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
}
@@ -154,8 +155,8 @@ run_concurrent() {
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
define_program "$file_param"
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+%Y-%m-%d-%H-%M-%S")
log_time="$time"
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -169,12 +170,12 @@ run_concurrent() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -196,7 +197,7 @@ run_concurrent() {
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
timeout -k 10s $command_timeout_time $which_program $file_param &>$single_log_path &
eval $timeoutCmd $which_program $file_param &>$single_log_path &
done
wait
@@ -224,8 +225,8 @@ run_designated() {
fi
define_program "$file_param"
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+%Y-%m-%d-%H-%M-%S")
log_time="$time"
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -239,12 +240,12 @@ run_designated() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
@@ -274,7 +275,7 @@ run_designated() {
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
eval $timeoutCmd $which_program $file_param $cmd
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
@@ -288,8 +289,8 @@ run_designated() {
run_else() {
local file_param="$1"
define_program "$file_param"
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+%Y-%m-%d-%H-%M-%S")
log_time="$time"
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -303,12 +304,12 @@ run_else() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -325,7 +326,7 @@ run_else() {
fi
shift
eval timeout -k 10s $command_timeout_time $which_program "$file_param" "$@" $cmd
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
eval . $file_task_after "$file_param" "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
@@ -348,6 +349,11 @@ main() {
done
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
timeoutCmd=""
if type timeout &>/dev/null; then
timeoutCmd="timeout -k 10s $command_timeout_time "
fi
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
case $# in
1)
Executable
+56
View File
@@ -0,0 +1,56 @@
import 'reflect-metadata';
import OpenService from '../back/services/open';
import { Container } from 'typedi';
import LoggerInstance from '../back/loaders/logger';
import fs from 'fs';
import config from '../back/config';
import path from 'path';
const tokenFile = path.join(config.configPath, 'token.json');
async function getToken() {
try {
const data = await readFile();
const nowTime = Math.round(Date.now() / 1000);
if (data.value && data.expiration > nowTime) {
console.log(data.value);
} else {
Container.set('logger', LoggerInstance);
const openService = Container.get(OpenService);
const appToken = await openService.findSystemToken();
console.log(appToken.value);
await writeFile({
value: appToken.value,
expiration: appToken.expiration,
});
}
} catch (error) {
console.log(error);
}
}
async function readFile() {
return new Promise<any>((resolve, reject) => {
fs.readFile(
path.join(config.configPath, 'token.json'),
{ encoding: 'utf8' },
(err, data) => {
if (err) {
resolve({});
} else {
resolve(JSON.parse(data));
}
},
);
});
}
async function writeFile(data: any) {
return new Promise<void>((resolve, reject) => {
fs.writeFile(tokenFile, JSON.stringify(data), { encoding: 'utf8' }, () => {
resolve();
});
});
}
getToken();
+8 -8
View File
@@ -12,14 +12,16 @@ diff_cron() {
local list_task="$2"
local list_add="$3"
local list_drop="$4"
if [[ -s $list_task ]]; then
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
grep -vwf $list_task $list_scripts >$list_add
elif [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
grep -vwf $list_scripts $list_task >$list_drop
fi
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
cp -f $list_scripts $list_add
fi
if [[ -s $list_scripts ]]; then
grep -vwf $list_scripts $list_task >$list_drop
else
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
cp -f $list_task $list_drop
fi
}
@@ -486,7 +488,6 @@ main() {
run_extra_shell >>$log_path
;;
repo)
get_user_info
get_uniq_path "$p2" "$p6"
if [[ -n $p2 ]]; then
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
@@ -496,7 +497,6 @@ main() {
fi
;;
raw)
get_user_info
get_uniq_path "$p2"
if [[ -n $p2 ]]; then
update_raw "$p2"
@@ -538,7 +538,7 @@ main() {
;;
esac
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
local diff_time=$(diff_time "%Y-%m-%d %H:%M:%S" "$begin_time" "$end_time")
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
echo -e "\n## 执行结束... $end_time 耗时 $diff_time" >>$log_path
cat $log_path
+28 -10
View File
@@ -20,6 +20,7 @@ import {
FileOutlined,
PlayCircleOutlined,
PauseCircleOutlined,
FullscreenOutlined,
} from '@ant-design/icons';
import { CrontabStatus } from './index';
import { diffTime } from '@/utils/date';
@@ -106,7 +107,8 @@ const CronDetailModal = ({
glyphMargin: false,
wordWrap: 'on',
}}
onMount={(editor) => {
onMount={(editor, monaco) => {
console.log(monaco);
editorRef.current = editor;
}}
/>
@@ -115,9 +117,13 @@ const CronDetailModal = ({
const onClickItem = (item: LogItem) => {
localStorage.setItem('logCron', currentCron.id);
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
setLogUrl(
`${config.apiPrefix}logs/${item.filename}?path=${item.directory || ''}`,
);
request
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
.get(
`${config.apiPrefix}logs/${item.filename}?path=${item.directory || ''}`,
)
.then((data) => {
setLog(data.data);
setIsLogModalVisible(true);
@@ -348,6 +354,11 @@ const CronDetailModal = ({
});
};
const fullscreen = () => {
const editorElement = editorRef.current._domElement as HTMLElement;
editorElement.parentElement?.requestFullscreen();
};
useEffect(() => {
if (cron && cron.id) {
setCurrentCron(cron);
@@ -525,13 +536,20 @@ const CronDetailModal = ({
}}
tabBarExtraContent={
activeTabKey === 'script' && (
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={saveFile}
>
</Button>
<>
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={saveFile}
>
</Button>
<Button
type="primary"
icon={<FullscreenOutlined />}
onClick={fullscreen}
/>
</>
)
}
>
+40 -28
View File
@@ -438,8 +438,10 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
message.success('删除成功');
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1);
setValue(result);
if (i !== -1) {
result.splice(i, 1);
setValue(result);
}
} else {
message.error(data);
}
@@ -470,11 +472,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
if (data.code === 200) {
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1, {
...record,
status: CrontabStatus.running,
});
setValue(result);
if (i !== -1) {
result.splice(i, 1, {
...record,
status: CrontabStatus.running,
});
setValue(result);
}
} else {
message.error(data);
}
@@ -505,12 +509,14 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
if (data.code === 200) {
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1, {
...record,
pid: null,
status: CrontabStatus.idle,
});
setValue(result);
if (i !== -1) {
result.splice(i, 1, {
...record,
pid: null,
status: CrontabStatus.idle,
});
setValue(result);
}
} else {
message.error(data);
}
@@ -550,11 +556,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const newStatus = record.isDisabled === 1 ? 0 : 1;
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1, {
...record,
isDisabled: newStatus,
});
setValue(result);
if (i !== -1) {
result.splice(i, 1, {
...record,
isDisabled: newStatus,
});
setValue(result);
}
} else {
message.error(data);
}
@@ -594,11 +602,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const newStatus = record.isPinned === 1 ? 0 : 1;
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1, {
...record,
isPinned: newStatus,
});
setValue(result);
if (i !== -1) {
result.splice(i, 1, {
...record,
isPinned: newStatus,
});
setValue(result);
}
} else {
message.error(data);
}
@@ -709,11 +719,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
.parseExpression(data.data.schedule)
.next()
.toDate();
result.splice(index, 1, {
...cron,
...data.data,
});
setValue(result);
if (index !== -1) {
result.splice(index, 1, {
...cron,
...data.data,
});
setValue(result);
}
})
.finally(() => setLoading(false));
};
+25 -15
View File
@@ -271,9 +271,11 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
result.push(...dependence);
} else {
const index = value.findIndex((x) => x.id === dependence.id);
result.splice(index, 1, {
...dependence,
});
if (index !== -1) {
result.splice(index, 1, {
...dependence,
});
}
}
setValue(result);
};
@@ -324,11 +326,13 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
.then((data: any) => {
const index = value.findIndex((x) => x.id === dependence.id);
const result = [...value];
result.splice(index, 1, {
...dependence,
...data.data,
});
setValue(result);
if (index !== -1) {
result.splice(index, 1, {
...dependence,
...data.data,
});
setValue(result);
}
})
.finally(() => setLoading(false));
};
@@ -371,10 +375,12 @@ 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]);
result.splice(index, 1, {
...result[index],
status,
});
if (index !== -1) {
result.splice(index, 1, {
...value[index],
status,
});
}
}
setValue(result);
@@ -383,7 +389,9 @@ 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]);
_result.splice(index, 1);
if (index !== -1) {
_result.splice(index, 1);
}
}
setValue(_result);
}, 5000);
@@ -483,8 +491,10 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
if (needRemove) {
const index = value.findIndex((x) => x.id === logDependence.id);
const result = [...value];
result.splice(index, 1);
setValue(result);
if (index !== -1) {
result.splice(index, 1);
setValue(result);
}
} else if ([...value].map((x) => x.id).includes(logDependence.id)) {
getDependenceDetail(logDependence);
}
+8 -6
View File
@@ -90,17 +90,19 @@ const DependenceLogModal = ({
}, [dependence]);
useEffect(() => {
if (!socketMessage) return;
if (!socketMessage || !dependence) return;
const { type, message, references } = socketMessage;
if (
type === 'installDependence' &&
message.includes('结束时间') &&
references.length > 0
references.length > 0 &&
references.includes(dependence.id)
) {
setExecuting(false);
setIsRemoveFailed(message.includes('删除失败'));
if (message.includes('结束时间')) {
setExecuting(false);
setIsRemoveFailed(message.includes('删除失败'));
}
setValue(`${value}${message}`);
}
setValue(`${value}${message}`);
}, [socketMessage]);
useEffect(() => {
+15 -1
View File
@@ -25,7 +25,7 @@ import EditNameModal from './editNameModal';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import './index.less';
import { getTableScroll } from '@/utils/index';
import { exportJson, getTableScroll } from '@/utils/index';
const { Text, Paragraph } = Typography;
const { Search } = Input;
@@ -462,6 +462,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
});
};
const exportEnvs = () => {
const envs = value
.filter((x) => selectedRowIds.includes(x.id))
.map((x) => ({ value: x.value, name: x.name, remarks: x.remarks }));
exportJson('env.json', JSON.stringify(envs));
};
const modifyName = () => {
setIsEditNameModalVisible(true);
};
@@ -516,6 +523,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
>
</Button>
<Button
type="primary"
onClick={() => exportEnvs()}
style={{ marginLeft: 8, marginRight: 8 }}
>
</Button>
<Button
type="primary"
onClick={() => operateEnvs(0)}
+2 -1
View File
@@ -60,7 +60,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
const getLog = (node: any) => {
request
.get(`${config.apiPrefix}logs/${node.value}?path=${node.parent || ''}`)
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
.then((data) => {
setValue(data.data);
});
@@ -113,6 +113,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={data}
placeholder="请选择日志"
fieldNames={{ value: 'key', label: 'title' }}
showSearch
onSelect={onSelect}
/>,
+11 -7
View File
@@ -66,7 +66,7 @@ const EditModal = ({
const getDetail = (node: any) => {
request
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
.then((data) => {
setValue(data.data);
});
@@ -74,11 +74,13 @@ const EditModal = ({
const run = () => {
setLog('');
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
request
.put(`${config.apiPrefix}scripts/run`, {
data: {
filename: cNode.value,
filename: cNode.title,
path: cNode.parent || '',
content,
},
})
.then((data) => {
@@ -87,14 +89,16 @@ const EditModal = ({
};
const stop = () => {
if (!cNode || !cNode.value) {
if (!cNode || !cNode.title) {
return;
}
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
request
.put(`${config.apiPrefix}scripts/stop`, {
data: {
filename: cNode.value,
filename: cNode.title,
path: cNode.parent || '',
content,
},
})
.then((data) => {
@@ -113,8 +117,7 @@ const EditModal = ({
return;
}
if (_message === '执行结束') {
_message = '';
if (_message.includes('执行结束')) {
setTimeout(() => {
setIsRunning(false);
}, 300);
@@ -146,6 +149,7 @@ const EditModal = ({
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={treeData}
placeholder="请选择脚本文件"
fieldNames={{ value: 'key', label: 'title' }}
showSearch
onSelect={onSelect}
/>
@@ -243,7 +247,7 @@ const EditModal = ({
content:
editorRef.current &&
editorRef.current.getValue().replace(/\r\n/g, '\n'),
filename: cNode?.value,
filename: cNode?.title,
}}
/>
<SettingModal
+89 -22
View File
@@ -1,7 +1,17 @@
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Select } from 'antd';
import {
Modal,
message,
Input,
Form,
Select,
Upload,
Radio,
TreeSelect,
} from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import { UploadOutlined } from '@ant-design/icons';
const { Option } = Select;
@@ -21,21 +31,30 @@ const EditScriptNameModal = ({
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [dirs, setDirs] = useState<any[]>([]);
const [file, setFile] = useState<File>();
const [type, setType] = useState<'blank' | 'upload'>('blank');
const handleOk = async (values: any) => {
setLoading(true);
values.path = values.path || '';
const formData = new FormData();
formData.append('file', file as any);
formData.append('filename', values.filename);
formData.append('path', values.path);
formData.append('content', '');
request
.post(`${config.apiPrefix}scripts`, {
data: { filename: values.filename, path: values.path, content: '' },
data: formData,
})
.then(({ code, data }) => {
if (code === 200) {
message.success('保存文件成功');
message.success('新建文件成功');
const key = values.path ? `${values.path}/` : '';
const filename = file ? file.name : values.filename;
handleCancel({
filename: values.filename,
filename,
path: values.path,
key: `${values.path}-${values.filename}`,
key: `${key}${filename}`,
});
} else {
message.error(data);
@@ -45,15 +64,42 @@ const EditScriptNameModal = ({
.finally(() => setLoading(false));
};
const beforeUpload = (file: File) => {
setFile(file);
return false;
};
const typeChange = (e) => {
setType(e.target.value);
};
const getDirs = (data) => {
for (const item of data) {
if (item.children && item.children.length > 0) {
item.children = item.children
.filter((x) => x.type === 'directory')
.map((x) => ({ ...x, disabled: false }));
getDirs(item.children);
}
}
return data;
};
useEffect(() => {
const originDirs = treeData
.filter((x) => x.type === 'directory')
.map((x) => ({ ...x, disabled: false }));
const dirs = getDirs(originDirs);
setDirs(dirs);
}, [treeData]);
useEffect(() => {
form.resetFields();
const originDirs = treeData.filter((x) => x.disabled);
setDirs([{ key: '' }, ...originDirs]);
}, [visible]);
return (
<Modal
title="新建文件"
title="新建脚本"
visible={visible}
forceRender
centered
@@ -73,23 +119,44 @@ const EditScriptNameModal = ({
>
<Form form={form} layout="vertical" name="edit_name_modal">
<Form.Item
name="filename"
label="文件名"
rules={[{ required: true, message: '请输入文件名' }]}
name="type"
label="类型"
rules={[{ required: true }]}
initialValue={'blank'}
>
<Input placeholder="请输入文件名" />
<Radio.Group onChange={typeChange}>
<Radio value="blank"></Radio>
<Radio value="upload"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="父目录"
name="path"
initialValue={dirs && dirs.length > 0 ? dirs[0].key : ''}
>
<Select placeholder="请选择父目录">
{dirs.map((x) => (
<Option value={x.key}>{x.key || '根'}</Option>
))}
</Select>
{type === 'blank' && (
<Form.Item
name="filename"
label="文件名"
rules={[{ required: true, message: '请输入文件名' }]}
>
<Input placeholder="请输入文件名" />
</Form.Item>
)}
<Form.Item label="父目录" name="path">
<TreeSelect
allowClear
treeData={dirs}
fieldNames={{ value: 'key', label: 'title' }}
placeholder="请选择父目录"
treeDefaultExpandAll
/>
</Form.Item>
{type === 'upload' && (
<Form.Item label="文件" name="file">
<Upload.Dragger beforeUpload={beforeUpload} maxCount={1}>
<p className="ant-upload-drag-icon">
<UploadOutlined />
</p>
<p className="ant-upload-text"></p>
</Upload.Dragger>
</Form.Item>
)}
</Form>
</Modal>
);
+25 -18
View File
@@ -104,7 +104,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
const getDetail = (node: any) => {
request
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
.then((data) => {
setValue(data.data);
});
@@ -117,7 +117,6 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
const obj = {
node: {
title: s,
value: s,
key: p ? vkey : s,
parent: p,
},
@@ -209,7 +208,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
<>
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentNode.value}
{currentNode.title}
</Text>{' '}
</>
@@ -222,7 +221,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
request
.put(`${config.apiPrefix}scripts`, {
data: {
filename: currentNode.value,
filename: currentNode.title,
path: currentNode.parent || '',
content,
},
@@ -262,7 +261,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
request
.delete(`${config.apiPrefix}scripts`, {
data: {
filename: currentNode.value,
filename: currentNode.title,
path: currentNode.parent || '',
},
})
@@ -278,13 +277,17 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
const index = parentNode.children.findIndex(
(y) => y.key === currentNode.key,
);
parentNode.children.splice(index, 1);
newData.splice(parentNodeIndex, 1, { ...parentNode });
if (index !== -1 && parentNodeIndex !== -1) {
parentNode.children.splice(index, 1);
newData.splice(parentNodeIndex, 1, { ...parentNode });
}
} else {
const index = newData.findIndex(
(x) => x.key === currentNode.key,
);
newData.splice(index, 1);
if (index !== -1) {
newData.splice(index, 1);
}
}
setData(newData);
} else {
@@ -311,21 +314,24 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
) => {
if (filename) {
const newData = [...data];
const _file = { title: filename, key, value: filename, parent: path };
const _file = { title: filename, key, parent: path };
if (path) {
// TODO: 更新左侧树数据
const parentNodeIndex = newData.findIndex((x) => x.key === path);
const parentNode = newData[parentNodeIndex];
if (parentNode.children && parentNode.children.length > 0) {
parentNode.children.unshift(_file);
} else {
parentNode.children = [_file];
if (parentNodeIndex !== -1) {
const parentNode = newData[parentNodeIndex];
if (parentNode.children && parentNode.children.length > 0) {
parentNode.children.unshift(_file);
} else {
parentNode.children = [_file];
}
newData.splice(parentNodeIndex, 1, { ...parentNode });
}
newData.splice(parentNodeIndex, 1, { ...parentNode });
} else {
newData.unshift(_file);
}
setData(newData);
onSelect(_file.value, _file);
onSelect(_file.title, _file);
setIsEditing(true);
}
setIsAddFileModalVisible(false);
@@ -335,7 +341,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
request
.post(`${config.apiPrefix}scripts/download`, {
data: {
filename: currentNode.value,
filename: currentNode.title,
},
})
.then((_data: any) => {
@@ -343,7 +349,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = currentNode.value;
a.download = currentNode.title;
document.documentElement.appendChild(a);
a.click();
document.documentElement.removeChild(a);
@@ -445,6 +451,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={data}
placeholder="请选择脚本"
fieldNames={{ value: 'key', label: 'title' }}
showSearch
onSelect={onSelect}
/>,
+32 -22
View File
@@ -263,11 +263,13 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
if (data.code === 200) {
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1, {
...record,
status: SubscriptionStatus.running,
});
setValue(result);
if (i !== -1) {
result.splice(i, 1, {
...record,
status: SubscriptionStatus.running,
});
setValue(result);
}
} else {
message.error(data);
}
@@ -298,12 +300,14 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
if (data.code === 200) {
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1, {
...record,
pid: null,
status: SubscriptionStatus.idle,
});
setValue(result);
if (i !== -1) {
result.splice(i, 1, {
...record,
pid: null,
status: SubscriptionStatus.idle,
});
setValue(result);
}
} else {
message.error(data);
}
@@ -356,8 +360,10 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
message.success('删除成功');
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1);
setValue(result);
if (i !== -1) {
result.splice(i, 1);
setValue(result);
}
} else {
message.error(data);
}
@@ -397,11 +403,13 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
const newStatus = record.is_disabled === 1 ? 0 : 1;
const result = [...value];
const i = result.findIndex((x) => x.id === record.id);
result.splice(i, 1, {
...record,
is_disabled: newStatus,
});
setValue(result);
if (i !== -1) {
result.splice(i, 1, {
...record,
is_disabled: newStatus,
});
setValue(result);
}
} else {
message.error(data);
}
@@ -509,10 +517,12 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
const result = [...value];
for (let i = 0; i < references.length; i++) {
const index = value.findIndex((x) => x.id === references[i]);
result.splice(index, 1, {
...result[index],
status: SubscriptionStatus.idle,
});
if (index !== -1) {
result.splice(index, 1, {
...value[index],
status: SubscriptionStatus.idle,
});
}
}
setValue(result);
}
+21 -9
View File
@@ -107,12 +107,18 @@ const SubscriptionModal = ({
const [intervalNumber, setIntervalNumber] = useState<number>();
const intervalTypeChange = (type: string) => {
setIntervalType(type);
onChange?.({ type, value: intervalNumber });
if (intervalNumber && intervalNumber > 0) {
onChange?.({ type, value: intervalNumber });
}
};
const numberChange = (value: number) => {
setIntervalNumber(value);
onChange?.({ type: intervalType, value });
if (!value) {
onChange?.(null);
} else {
onChange?.({ type: intervalType, value });
}
};
useEffect(() => {
@@ -195,21 +201,27 @@ const SubscriptionModal = ({
dependences,
branch,
extensions,
] = text.split(' ').map((x) => x.trim());
] = text
.split(' ')
.map((x) => x.trim().replace(/\"/g, '').replace(/\'/, ''));
const _type =
type === 'raw'
? 'file'
: url.startsWith('http')
? 'public-repo'
: 'private-repo';
form.setFieldsValue({
type:
type === 'raw'
? 'file'
: url.startsWith('http')
? 'public-repo'
: 'private-repo',
type: _type,
url,
whitelist,
blacklist,
dependences,
branch,
extensions,
alias: formatAlias(url, branch),
});
setType(_type);
}
}, []);
+36
View File
@@ -205,3 +205,39 @@ export function getTableScroll({
let height = document.body.clientHeight - mainTop - extraHeight;
return height;
}
// 自动触发点击事件
function automaticClick(elment: HTMLElement) {
const ev = document.createEvent('MouseEvents');
ev.initMouseEvent(
'click',
true,
false,
window,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null,
);
elment.dispatchEvent(ev);
}
// 导出文件
export function exportJson(name: string, data: string) {
const urlObject = window.URL || window.webkitURL || window;
const export_blob = new Blob([data]);
const createA = document.createElementNS(
'http://www.w3.org/1999/xhtml',
'a',
) as any;
createA.href = urlObject.createObjectURL(export_blob);
createA.download = name;
automaticClick(createA);
}
+8 -2
View File
@@ -7,9 +7,15 @@ export function init() {
// sentry监控 init
Sentry.init({
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
integrations: [new Integrations.BrowserTracing()],
integrations: [
new Integrations.BrowserTracing({
shouldCreateSpanForRequest(url) {
return !url.includes('/api/ws') && !url.includes('/api/static');
},
}),
],
release: version,
tracesSampleRate: 1.0,
tracesSampleRate: 0.1,
beforeBreadcrumb(breadcrumb, hint?) {
if (breadcrumb.data && breadcrumb.data.url) {
const url = breadcrumb.data.url.replace(/token=.*/, '');
+12 -10
View File
@@ -1,12 +1,14 @@
export const version = '2.13.0';
export const version = '2.13.4';
export const changeLogLink = 'https://t.me/jiao_long/303';
export const changeLog = `2.13.0 版本说明
1. 新增订阅管理模块,支持公开仓库/私有仓库等
2. 支持修改头像
3. 修复特殊字符环境变量异常
4. 修改定时任务添加标签
5. 修复添加任务默认会展示一个空标签
6. 修复用户名显示
7. 修复依赖安装
8. 其他bug修复
export const changeLog = `2.13.4 版本说明
1. 增加系统token,防止未登录时无法更新仓库和任务状态
2. 脚本管理按照类型排序
3. 任务详情中编辑脚本支持全屏
4. 环境变量增加导出功能
5. 修复无法更新脚本内容为空
6. 修复停止node脚本报错
7. 修改手动停止任务耗时
8. 修复脚本管理、日志管理树
9. 修复获取环境变量详情
10. 区分原生termux和termux中安装的Linux发,感谢 erma0
`;