mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 11:22:58 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f152136f0b | ||
|
|
c24d3d6e2f | ||
|
|
33d820acdb | ||
|
|
78ef75bbfe | ||
|
|
31541fb87e | ||
|
|
0c169e3f6c | ||
|
|
73c15cb3bc | ||
|
|
4373a2df49 | ||
|
|
cb004cfbf5 | ||
|
|
7f2d6d159c | ||
|
|
4bba024e4a | ||
|
|
acb43f3ad8 | ||
|
|
b160f914ad | ||
|
|
6116aa6d12 | ||
|
|
d43cee53c9 | ||
|
|
defea8ff9b | ||
|
|
b290b60d46 | ||
|
|
706b5e8108 | ||
|
|
b55c7d5747 | ||
|
|
6b8fd94f0c | ||
|
|
e18710a30a | ||
|
|
c0f0da4e96 | ||
|
|
49ef5cd61f | ||
|
|
99d7eaeff0 | ||
|
|
c8f5eb7c2e | ||
|
|
109e16e8fb | ||
|
|
555f6e04e8 | ||
|
|
bcde0a54bf | ||
|
|
eee7f25bc3 | ||
|
|
b10157b09a | ||
|
|
a09a21fa5d | ||
|
|
ec2918b427 | ||
|
|
57bfc19ecf | ||
|
|
aa110d4187 | ||
|
|
9e8febfc11 |
@@ -1 +1,2 @@
|
||||
sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli
|
||||
sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli
|
||||
strict-peer-dependencies=false
|
||||
@@ -9,10 +9,6 @@ export default defineConfig({
|
||||
type: 'none',
|
||||
},
|
||||
fastRefresh: {},
|
||||
// antd: {
|
||||
// dark: true,
|
||||
// },
|
||||
mfsu: {},
|
||||
esbuild: {},
|
||||
webpack5: {},
|
||||
dynamicImport: {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
@@ -180,7 +173,6 @@ export default (app: Router) => {
|
||||
const data = await envService.getDb(req.params.id);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
|
||||
+1
-2
@@ -31,13 +31,12 @@ export default (app: Router) => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
+43
-30
@@ -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);
|
||||
}
|
||||
},
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
+46
-19
@@ -277,31 +277,58 @@ export async function concurrentRun(
|
||||
return replyList;
|
||||
}
|
||||
|
||||
export function readDirs(dir: string, baseDir: string = '') {
|
||||
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),
|
||||
};
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+1
-3
@@ -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();
|
||||
|
||||
+23
-1
@@ -136,7 +136,6 @@ export default ({ app }: { app: Application }) => {
|
||||
});
|
||||
|
||||
app.use(errors());
|
||||
app.use(Sentry.Handlers.errorHandler());
|
||||
|
||||
app.use(
|
||||
(
|
||||
@@ -155,6 +154,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
@@ -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[]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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,6 +58,10 @@ export default async () => {
|
||||
fs.mkdirSync(sshPath);
|
||||
}
|
||||
|
||||
if (!bakDirExist) {
|
||||
fs.mkdirSync(bakPath);
|
||||
}
|
||||
|
||||
dotenv.config({ path: confFile });
|
||||
|
||||
Logger.info('✌️ Init file down');
|
||||
|
||||
@@ -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,6 +13,7 @@ import _ from 'lodash';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import { Op } from 'sequelize';
|
||||
import { concurrentRun } from '../config/util';
|
||||
|
||||
@Service()
|
||||
export default class DependenceService {
|
||||
@@ -27,7 +28,7 @@ export default class DependenceService {
|
||||
return tab;
|
||||
});
|
||||
const docs = await this.insert(tabs);
|
||||
this.installOrUninstallDependencies(docs);
|
||||
this.installDependenceOneByOne(docs);
|
||||
return docs;
|
||||
}
|
||||
|
||||
@@ -47,7 +48,7 @@ export default class DependenceService {
|
||||
status: DependenceStatus.installing,
|
||||
});
|
||||
const newDoc = await this.updateDb(tab);
|
||||
this.installOrUninstallDependencies([newDoc]);
|
||||
this.installDependenceOneByOne([newDoc]);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -62,7 +63,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 +99,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 +120,7 @@ export default class DependenceService {
|
||||
);
|
||||
|
||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||
this.installOrUninstallDependencies(docs);
|
||||
this.installDependenceOneByOne(docs);
|
||||
return docs;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,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 +148,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 +192,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 +232,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 +256,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 +283,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 +293,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 +336,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,7 +367,6 @@ 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);
|
||||
@@ -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 '';
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -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
@@ -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
@@ -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',
|
||||
);
|
||||
// 文件上传
|
||||
|
||||
@@ -88,6 +88,7 @@ check_pm2() {
|
||||
|
||||
main() {
|
||||
echo -e "=====> 开始检测"
|
||||
npm i -g pnpm
|
||||
pnpm add -g pm2
|
||||
copy_dep
|
||||
check_ql
|
||||
|
||||
@@ -115,9 +115,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);
|
||||
|
||||
+40
-28
@@ -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));
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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}
|
||||
/>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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('保存文件成功');
|
||||
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
@@ -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}
|
||||
/>,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
+8
-2
@@ -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=.*/, '');
|
||||
|
||||
+7
-10
@@ -1,12 +1,9 @@
|
||||
export const version = '2.13.0';
|
||||
export const version = '2.13.3';
|
||||
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.3 版本说明
|
||||
1. 新建脚本支持文件上传
|
||||
2. 修复调试脚本运行,不需要每次保存
|
||||
3. 修复新建脚本选择嵌套目录
|
||||
4. 修复粘贴订阅,私有仓库字段不更新
|
||||
5. 修复脚本管理、日志管理树
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user