mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 10:40:52 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddee69d291 | ||
|
|
28960c9fab | ||
|
|
449301e519 | ||
|
|
28dab3dc2f | ||
|
|
7995d98db5 | ||
|
|
1f319f3533 | ||
|
|
7d2dbaa62d | ||
|
|
ed5999526b | ||
|
|
2409f75491 | ||
|
|
0c840095d1 | ||
|
|
3eaf0b4856 | ||
|
|
394735a5d9 | ||
|
|
5bfa10bfe7 | ||
|
|
3f12d9cbd5 | ||
|
|
54d89754df | ||
|
|
9470524615 | ||
|
|
b5faa6eaaa | ||
|
|
eab476cfe8 | ||
|
|
b773def012 | ||
|
|
a9f8b23a7a | ||
|
|
61bb6535fb | ||
|
|
eb0bdffe06 | ||
|
|
e129485285 | ||
|
|
aac7de172e | ||
|
|
e19d2412fc | ||
|
|
7cba2cf76c | ||
|
|
55d7a49dbc | ||
|
|
f695864fd8 | ||
|
|
ae4883fbe7 | ||
|
|
836dfd2861 | ||
|
|
6e0523c6d7 | ||
|
|
cd9ba084ae | ||
|
|
c8a0c0de67 |
@@ -105,7 +105,9 @@ jobs:
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
build-args: MAINTAINER=${{ github.repository_owner }}, QL_BRANCH=${{ github.ref_name }}
|
||||
build-args: |
|
||||
MAINTAINER=${{ github.repository_owner }}
|
||||
QL_BRANCH=${{ github.ref_name }}
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x
|
||||
context: docker/
|
||||
push: true
|
||||
|
||||
@@ -39,7 +39,7 @@ export default defineConfig({
|
||||
scripts: [
|
||||
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
||||
'https://gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/darkreader@4/darkreader.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/darkreader@4.9.40/darkreader.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/codemirror@5/lib/codemirror.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/shell/shell.js',
|
||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/python/python.js',
|
||||
|
||||
+4
-4
@@ -8,10 +8,10 @@ import { celebrate, Joi } from 'celebrate';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/', route);
|
||||
app.use('/configs', route);
|
||||
|
||||
route.get(
|
||||
'/configs/files',
|
||||
'/files',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -32,7 +32,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/configs/:file',
|
||||
'/:file',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -58,7 +58,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.post(
|
||||
'/configs/save',
|
||||
'/save',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
|
||||
+26
-30
@@ -7,26 +7,22 @@ import cron_parser from 'cron-parser';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/', route);
|
||||
route.get(
|
||||
'/crons',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.crontabs(
|
||||
req.query.searchValue as string,
|
||||
);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
app.use('/crons', route);
|
||||
|
||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.crontabs(req.query.searchValue as string);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
|
||||
route.post(
|
||||
'/crons',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
command: Joi.string().required(),
|
||||
@@ -52,7 +48,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/run',
|
||||
'/run',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -70,7 +66,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/stop',
|
||||
'/stop',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -88,7 +84,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/disable',
|
||||
'/disable',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -106,7 +102,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/enable',
|
||||
'/enable',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -124,7 +120,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/crons/:id/log',
|
||||
'/:id/log',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
@@ -144,7 +140,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons',
|
||||
'',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
command: Joi.string().optional(),
|
||||
@@ -174,7 +170,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/crons',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -192,7 +188,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/pin',
|
||||
'/pin',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -210,7 +206,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/unpin',
|
||||
'/unpin',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -228,7 +224,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/crons/import',
|
||||
'/import',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -243,7 +239,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/crons/:id',
|
||||
'/:id',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
@@ -263,7 +259,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/status',
|
||||
'/status',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
ids: Joi.array().items(Joi.string().required()),
|
||||
|
||||
+19
-21
@@ -6,24 +6,22 @@ import { celebrate, Joi } from 'celebrate';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/', route);
|
||||
route.get(
|
||||
'/dependencies',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const dependenceService = Container.get(DependenceService);
|
||||
const data = await dependenceService.dependencies(req.query as any);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
app.use('/dependencies', route);
|
||||
|
||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const dependenceService = Container.get(DependenceService);
|
||||
const data = await dependenceService.dependencies(req.query as any);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
|
||||
route.post(
|
||||
'/dependencies',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.array().items(
|
||||
Joi.object({
|
||||
@@ -47,7 +45,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/dependencies',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
@@ -70,7 +68,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/dependencies',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -88,7 +86,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/dependencies/force',
|
||||
'/force',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -106,7 +104,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/dependencies/:id',
|
||||
'/:id',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
@@ -126,7 +124,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/dependencies/reinstall',
|
||||
'/reinstall',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
|
||||
+21
-23
@@ -6,24 +6,22 @@ import { celebrate, Joi } from 'celebrate';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/', route);
|
||||
route.get(
|
||||
'/envs',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const envService = Container.get(EnvService);
|
||||
const data = await envService.envs(req.query.searchValue as string);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
app.use('/envs', route);
|
||||
|
||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const envService = Container.get(EnvService);
|
||||
const data = await envService.envs(req.query.searchValue as string);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
|
||||
route.post(
|
||||
'/envs',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.array().items(
|
||||
Joi.object({
|
||||
@@ -47,7 +45,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/envs',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
value: Joi.string().required(),
|
||||
@@ -70,7 +68,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/envs',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -88,7 +86,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/envs/:id/move',
|
||||
'/:id/move',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
@@ -112,7 +110,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/envs/disable',
|
||||
'/disable',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -130,7 +128,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/envs/enable',
|
||||
'/enable',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
@@ -148,7 +146,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/envs/name',
|
||||
'/name',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
ids: Joi.array().items(Joi.string().required()),
|
||||
@@ -169,7 +167,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/envs/:id',
|
||||
'/:id',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
|
||||
@@ -7,6 +7,7 @@ import cron from './cron';
|
||||
import script from './script';
|
||||
import open from './open';
|
||||
import dependence from './dependence';
|
||||
import system from './system';
|
||||
|
||||
export default () => {
|
||||
const app = Router();
|
||||
@@ -18,6 +19,7 @@ export default () => {
|
||||
script(app);
|
||||
open(app);
|
||||
dependence(app);
|
||||
system(app);
|
||||
|
||||
return app;
|
||||
};
|
||||
|
||||
+33
-35
@@ -7,44 +7,42 @@ import { getFileContentByName } from '../config/util';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/', route);
|
||||
route.get(
|
||||
'/logs',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const fileList = fs.readdirSync(config.logPath, 'utf-8');
|
||||
const dirs = [];
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const stat = fs.lstatSync(config.logPath + fileList[i]);
|
||||
if (stat.isDirectory()) {
|
||||
const fileListTmp = fs.readdirSync(
|
||||
`${config.logPath}/${fileList[i]}`,
|
||||
'utf-8',
|
||||
);
|
||||
dirs.push({
|
||||
name: fileList[i],
|
||||
isDir: true,
|
||||
files: fileListTmp.reverse(),
|
||||
});
|
||||
} else {
|
||||
dirs.push({
|
||||
name: fileList[i],
|
||||
isDir: false,
|
||||
files: [],
|
||||
});
|
||||
}
|
||||
app.use('/logs', route);
|
||||
|
||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const fileList = fs.readdirSync(config.logPath, 'utf-8');
|
||||
const dirs = [];
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const stat = fs.lstatSync(config.logPath + fileList[i]);
|
||||
if (stat.isDirectory()) {
|
||||
const fileListTmp = fs.readdirSync(
|
||||
`${config.logPath}/${fileList[i]}`,
|
||||
'utf-8',
|
||||
);
|
||||
dirs.push({
|
||||
name: fileList[i],
|
||||
isDir: true,
|
||||
files: fileListTmp.reverse(),
|
||||
});
|
||||
} else {
|
||||
dirs.push({
|
||||
name: fileList[i],
|
||||
isDir: false,
|
||||
files: [],
|
||||
});
|
||||
}
|
||||
res.send({ code: 200, dirs });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
res.send({ code: 200, dirs });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
|
||||
route.get(
|
||||
'/logs/:dir/:file',
|
||||
'/:dir/:file',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -61,7 +59,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/logs/:file',
|
||||
'/:file',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
|
||||
+46
-18
@@ -9,15 +9,15 @@ import { Logger } from 'winston';
|
||||
import config from '../config';
|
||||
import * as fs from 'fs';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import path from 'path';
|
||||
import path, { join } from 'path';
|
||||
import ScriptService from '../services/script';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/', route);
|
||||
app.use('/scripts', route);
|
||||
|
||||
route.get(
|
||||
'/scripts/files',
|
||||
'/files',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -44,7 +44,7 @@ export default (app: Router) => {
|
||||
children.push({
|
||||
title: childFile,
|
||||
value: childFile,
|
||||
key: `${fileOrDir}-${childFile}`,
|
||||
key: `${fileOrDir}/${childFile}`,
|
||||
mtime: statObj.mtimeMs,
|
||||
parent: fileOrDir,
|
||||
});
|
||||
@@ -80,14 +80,16 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/scripts/:file',
|
||||
'/:file',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const path = req.query.path ? `${req.query.path}/` : '';
|
||||
const content = getFileContentByName(
|
||||
`${config.scriptPath}${path}${req.params.file}`,
|
||||
const filePath = join(
|
||||
config.scriptPath,
|
||||
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);
|
||||
@@ -97,11 +99,11 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.post(
|
||||
'/scripts',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().allow(''),
|
||||
path: Joi.string().optional().allow(''),
|
||||
content: Joi.string().allow(''),
|
||||
originFilename: Joi.string().allow(''),
|
||||
}),
|
||||
@@ -157,11 +159,11 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/scripts',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().allow(''),
|
||||
path: Joi.string().optional().allow(''),
|
||||
content: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
@@ -173,7 +175,7 @@ export default (app: Router) => {
|
||||
content: string;
|
||||
path: string;
|
||||
};
|
||||
const filePath = `${config.scriptPath}${path}/${filename}`;
|
||||
const filePath = join(config.scriptPath, path, filename);
|
||||
fs.writeFileSync(filePath, content);
|
||||
return res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
@@ -184,7 +186,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/scripts',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
@@ -198,7 +200,7 @@ export default (app: Router) => {
|
||||
filename: string;
|
||||
path: string;
|
||||
};
|
||||
const filePath = `${config.scriptPath}${path}/${filename}`;
|
||||
const filePath = join(config.scriptPath, path, filename);
|
||||
fs.unlinkSync(filePath);
|
||||
res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
@@ -209,7 +211,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.post(
|
||||
'/scripts/download',
|
||||
'/download',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
@@ -240,7 +242,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/scripts/run',
|
||||
'/run',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
@@ -254,7 +256,7 @@ export default (app: Router) => {
|
||||
filename: string;
|
||||
path: string;
|
||||
};
|
||||
const filePath = `${path}/${filename}`;
|
||||
const filePath = join(path, filename);
|
||||
const scriptService = Container.get(ScriptService);
|
||||
const result = await scriptService.runScript(filePath);
|
||||
res.send(result);
|
||||
@@ -264,4 +266,30 @@ export default (app: Router) => {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/stop',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().optional().allow(''),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
let { filename, path } = req.body as {
|
||||
filename: string;
|
||||
path: string;
|
||||
};
|
||||
const filePath = join(path, filename);
|
||||
const scriptService = Container.get(ScriptService);
|
||||
const result = await scriptService.stopScript(filePath);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import { Logger } from 'winston';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import SystemService from '../services/system';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import { getFileContentByName } from '../config/util';
|
||||
import UserService from '../services/user';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/system', route);
|
||||
|
||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const authInfo = await userService.getUserInfo();
|
||||
const envDbContent = getFileContentByName(config.envDbFile);
|
||||
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
||||
|
||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||
const version = currentVersionFile.match(versionRegx)![1];
|
||||
|
||||
let isInitialized = true;
|
||||
if (
|
||||
Object.keys(authInfo).length === 2 &&
|
||||
authInfo.username === 'admin' &&
|
||||
authInfo.password === 'admin' &&
|
||||
envDbContent.length === 0
|
||||
) {
|
||||
isInitialized = false;
|
||||
}
|
||||
res.send({
|
||||
code: 200,
|
||||
data: {
|
||||
isInitialized,
|
||||
version,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
|
||||
route.get(
|
||||
'/log/remove',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const systemService = Container.get(SystemService);
|
||||
const data = await systemService.getLogRemoveFrequency();
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/log/remove',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
frequency: Joi.number().required(),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const systemService = Container.get(SystemService);
|
||||
const result = await systemService.updateLogRemoveFrequency(
|
||||
req.body.frequency,
|
||||
);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/update-check',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const systemService = Container.get(SystemService);
|
||||
const result = await systemService.checkUpdate();
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/update',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const systemService = Container.get(SystemService);
|
||||
const result = await systemService.updateSystem();
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
+29
-130
@@ -9,7 +9,8 @@ import { getFileContentByName } from '../config/util';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/', route);
|
||||
app.use('/user', route);
|
||||
|
||||
route.post(
|
||||
'/login',
|
||||
celebrate({
|
||||
@@ -47,7 +48,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/user',
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
@@ -67,29 +68,26 @@ export default (app: Router) => {
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/user',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const authInfo = await userService.getUserInfo();
|
||||
res.send({
|
||||
code: 200,
|
||||
data: {
|
||||
username: authInfo.username,
|
||||
twoFactorActivated: authInfo.twoFactorActivated,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const authInfo = await userService.getUserInfo();
|
||||
res.send({
|
||||
code: 200,
|
||||
data: {
|
||||
username: authInfo.username,
|
||||
twoFactorActivated: authInfo.twoFactorActivated,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
|
||||
route.get(
|
||||
'/user/two-factor/init',
|
||||
'/two-factor/init',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -104,7 +102,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/user/two-factor/active',
|
||||
'/two-factor/active',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
code: Joi.string().required(),
|
||||
@@ -124,7 +122,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/user/two-factor/deactive',
|
||||
'/two-factor/deactive',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -139,7 +137,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/user/two-factor/login',
|
||||
'/two-factor/login',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
code: Joi.string().required(),
|
||||
@@ -161,7 +159,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/user/login-log',
|
||||
'/login-log',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -176,7 +174,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/user/notification',
|
||||
'/notification',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -191,7 +189,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/user/notification',
|
||||
'/notification',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -205,40 +203,8 @@ export default (app: Router) => {
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/system',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const authInfo = await userService.getUserInfo();
|
||||
const envDbContent = getFileContentByName(config.envDbFile);
|
||||
|
||||
let isInitialized = true;
|
||||
if (
|
||||
Object.keys(authInfo).length === 2 &&
|
||||
authInfo.username === 'admin' &&
|
||||
authInfo.password === 'admin' &&
|
||||
envDbContent.length === 0
|
||||
) {
|
||||
isInitialized = false;
|
||||
}
|
||||
res.send({
|
||||
code: 200,
|
||||
data: {
|
||||
isInitialized,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 初始化api
|
||||
route.put(
|
||||
'/init/user',
|
||||
'/init',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
@@ -259,7 +225,7 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/init/notification',
|
||||
'/notification/init',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
@@ -272,71 +238,4 @@ export default (app: Router) => {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/system/log/remove',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.getLogRemoveFrequency();
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/system/log/remove',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
frequency: Joi.number().required(),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const result = await userService.updateLogRemoveFrequency(
|
||||
req.body.frequency,
|
||||
);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/system/update-check',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const result = await userService.checkUpdate();
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/system/update',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const result = await userService.updateSystem();
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createRandomString } from './util';
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
const lastVersionFile =
|
||||
'https://ghproxy.com/https://raw.githubusercontent.com/whyour/qinglong/master/src/version.ts';
|
||||
'https://raw.githubusercontent.com/whyour/qinglong/master/src/version.ts';
|
||||
|
||||
const envFound = dotenv.config();
|
||||
const rootPath = process.cwd();
|
||||
@@ -77,12 +77,12 @@ export default {
|
||||
writePathList: [configPath, scriptPath],
|
||||
bakPath,
|
||||
apiWhiteList: [
|
||||
'/api/login',
|
||||
'/api/user/login',
|
||||
'/open/auth/token',
|
||||
'/api/user/two-factor/login',
|
||||
'/api/system',
|
||||
'/api/init/user',
|
||||
'/api/init/notification',
|
||||
'/api/user/init',
|
||||
'/api/user/notification/init',
|
||||
],
|
||||
versionFile,
|
||||
lastVersionFile,
|
||||
|
||||
@@ -231,3 +231,40 @@ export async function fileExist(file: any) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function concurrentRun(
|
||||
fnList: Array<() => Promise<any>> = [],
|
||||
max = 5,
|
||||
) {
|
||||
if (!fnList.length) return;
|
||||
|
||||
const replyList: any[] = []; // 收集任务执行结果
|
||||
const startTime = new Date().getTime(); // 记录任务执行开始时间
|
||||
|
||||
// 任务执行程序
|
||||
const schedule = async (index: number) => {
|
||||
return new Promise(async (resolve) => {
|
||||
const fn = fnList[index];
|
||||
if (!fn) return resolve(null);
|
||||
|
||||
// 执行当前异步任务
|
||||
const reply = await fn();
|
||||
replyList[index] = reply;
|
||||
|
||||
// 执行完当前任务后,继续执行任务池的剩余任务
|
||||
await schedule(index + max);
|
||||
resolve(null);
|
||||
});
|
||||
};
|
||||
|
||||
// 任务池执行程序
|
||||
const scheduleList = new Array(max)
|
||||
.fill(0)
|
||||
.map((_, index) => schedule(index));
|
||||
|
||||
// 使用 Promise.all 批量执行
|
||||
const r = await Promise.all(scheduleList);
|
||||
const cost = (new Date().getTime() - startTime) / 1000;
|
||||
|
||||
return replyList;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import dependencyInjectorLoader from './dependencyInjector';
|
||||
import Logger from './logger';
|
||||
import initData from './initData';
|
||||
import { Application } from 'express';
|
||||
import linkDeps from './deps';
|
||||
|
||||
export default async ({ expressApp }: { expressApp: Application }) => {
|
||||
await dependencyInjectorLoader({
|
||||
@@ -15,4 +16,7 @@ export default async ({ expressApp }: { expressApp: Application }) => {
|
||||
|
||||
await initData();
|
||||
Logger.info('✌️ init data loaded');
|
||||
|
||||
await linkDeps();
|
||||
Logger.info('✌️ link deps');
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import chokidar from 'chokidar';
|
||||
import config from '../config/index';
|
||||
|
||||
function linkToNodeModule(src: string, dst?: string) {
|
||||
const target = path.join(config.rootPath, 'node_modules', dst || src);
|
||||
const source = path.join(config.rootPath, src);
|
||||
|
||||
fs.lstat(target, (err, stat) => {
|
||||
if (!stat) {
|
||||
fs.symlink(source, target, 'dir', (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default async (src: string = 'deps') => {
|
||||
linkToNodeModule(src);
|
||||
|
||||
const source = path.join(config.rootPath, src);
|
||||
const watcher = chokidar.watch(source, {
|
||||
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
||||
persistent: true,
|
||||
});
|
||||
|
||||
watcher
|
||||
.on('add', (path) => linkToNodeModule(src))
|
||||
.on('change', (path) => linkToNodeModule(src));
|
||||
};
|
||||
@@ -98,7 +98,7 @@ export default ({ app }: { app: Application }) => {
|
||||
});
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
if (!['/api/init/user', '/api/init/notification'].includes(req.path)) {
|
||||
if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) {
|
||||
return next();
|
||||
}
|
||||
const userService = Container.get(UserService);
|
||||
|
||||
@@ -22,14 +22,14 @@ export default async () => {
|
||||
);
|
||||
|
||||
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
||||
dependenceDb.find({ status: { $in: [0, 1, 2] } }).exec((err, docs) => {
|
||||
dependenceDb.find({ status: { $in: [0, 1, 2] } }).exec(async (err, 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) {
|
||||
dependenceService.reInstall([dep]);
|
||||
await dependenceService.reInstall([dep]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import Logger from './logger';
|
||||
|
||||
export default ({ expressApp }: { expressApp: Application }) => {
|
||||
Sentry.init({
|
||||
dsn: 'https://e14681bce55f4849b11024a7d424b711@o1051273.ingest.sentry.io/6047906',
|
||||
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
||||
integrations: [
|
||||
new Sentry.Integrations.Http({ tracing: true }),
|
||||
new Tracing.Integrations.Express({ app: expressApp }),
|
||||
|
||||
+33
-23
@@ -6,7 +6,7 @@ import { Crontab, CrontabStatus } from '../data/cron';
|
||||
import { exec, execSync, spawn } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import cron_parser from 'cron-parser';
|
||||
import { getFileContentByName } from '../config/util';
|
||||
import { getFileContentByName, concurrentRun } from '../config/util';
|
||||
import PQueue from 'p-queue';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
@@ -93,17 +93,21 @@ export default class CronService {
|
||||
last_running_time: number;
|
||||
last_execution_time: number;
|
||||
}) {
|
||||
const options: any = {
|
||||
status,
|
||||
pid,
|
||||
log_path,
|
||||
last_execution_time,
|
||||
};
|
||||
if (last_running_time > 0) {
|
||||
options.last_running_time = last_running_time;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.cronDb.update(
|
||||
{ _id: { $in: ids } },
|
||||
{
|
||||
$set: {
|
||||
status,
|
||||
pid,
|
||||
log_path,
|
||||
last_running_time,
|
||||
last_execution_time,
|
||||
},
|
||||
$set: options,
|
||||
},
|
||||
{ multi: true, returnUpdatedDocs: true },
|
||||
(err) => {
|
||||
@@ -155,7 +159,9 @@ export default class CronService {
|
||||
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
||||
let query = {};
|
||||
if (searchText) {
|
||||
const reg = new RegExp(searchText, 'i');
|
||||
const encodeText = encodeURIComponent(searchText);
|
||||
const reg = new RegExp(`${searchText}|${encodeText}`, 'i');
|
||||
|
||||
query = {
|
||||
$or: [
|
||||
{
|
||||
@@ -194,10 +200,10 @@ export default class CronService {
|
||||
{ $set: { status: CrontabStatus.queued } },
|
||||
{ multi: true },
|
||||
);
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
this.queue.add(() => this.runSingle(id));
|
||||
}
|
||||
concurrentRun(
|
||||
ids.map((id) => () => this.runSingle(id)),
|
||||
10,
|
||||
);
|
||||
}
|
||||
|
||||
public async stop(ids: string[]) {
|
||||
@@ -235,7 +241,7 @@ export default class CronService {
|
||||
});
|
||||
}
|
||||
|
||||
private async killTask(name: string) {
|
||||
public async killTask(name: string) {
|
||||
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
|
||||
const execAsync = promisify(exec);
|
||||
try {
|
||||
@@ -245,16 +251,20 @@ export default class CronService {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
const pids = pid.match(/\(\d+/g);
|
||||
let pids = pid.match(/\(\d+/g);
|
||||
const killLogs = [];
|
||||
for (const id of pids) {
|
||||
const c = `kill -9 ${id.slice(1)}`;
|
||||
const { stdout, stderr } = await execAsync(c);
|
||||
if (stderr) {
|
||||
killLogs.push(stderr);
|
||||
}
|
||||
if (stdout) {
|
||||
killLogs.push(stdout);
|
||||
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)}`;
|
||||
const { stdout, stderr } = await execAsync(c);
|
||||
if (stderr) {
|
||||
killLogs.push(stderr);
|
||||
}
|
||||
if (stdout) {
|
||||
killLogs.push(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
|
||||
|
||||
+95
-89
@@ -131,7 +131,7 @@ export default class DependenceService {
|
||||
{ $set: { status: DependenceStatus.installing, log: [] } },
|
||||
{ multi: true, returnUpdatedDocs: true },
|
||||
async (err, num, docs: Dependence[]) => {
|
||||
this.installOrUninstallDependencies(docs);
|
||||
await this.installOrUninstallDependencies(docs);
|
||||
resolve(docs);
|
||||
},
|
||||
);
|
||||
@@ -178,103 +178,109 @@ export default class DependenceService {
|
||||
dependencies: Dependence[],
|
||||
isInstall: boolean = true,
|
||||
) {
|
||||
if (dependencies.length === 0) {
|
||||
return;
|
||||
}
|
||||
const depNames = dependencies.map((x) => x.name).join(' ');
|
||||
const depRunCommand = (
|
||||
isInstall
|
||||
? InstallDependenceCommandTypes
|
||||
: unInstallDependenceCommandTypes
|
||||
)[dependencies[0].type as any];
|
||||
const actionText = isInstall ? '安装' : '删除';
|
||||
const depIds = dependencies.map((x) => x._id) as string[];
|
||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||
const startTime = Date.now();
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}`,
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(
|
||||
depIds,
|
||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}\n`,
|
||||
);
|
||||
cp.stdout.on('data', (data) => {
|
||||
return new Promise((resolve) => {
|
||||
if (dependencies.length === 0) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const depNames = dependencies.map((x) => x.name).join(' ');
|
||||
const depRunCommand = (
|
||||
isInstall
|
||||
? InstallDependenceCommandTypes
|
||||
: unInstallDependenceCommandTypes
|
||||
)[dependencies[0].type as any];
|
||||
const actionText = isInstall ? '安装' : '删除';
|
||||
const depIds = dependencies.map((x) => x._id) as string[];
|
||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||
const startTime = Date.now();
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: data.toString(),
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(depIds, data.toString());
|
||||
});
|
||||
|
||||
cp.stderr.on('data', (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: data.toString(),
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(depIds, data.toString());
|
||||
});
|
||||
|
||||
cp.on('error', (err) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: JSON.stringify(err),
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(depIds, JSON.stringify(err));
|
||||
});
|
||||
|
||||
cp.on('close', (code) => {
|
||||
const endTime = Date.now();
|
||||
const isSucceed = code === 0;
|
||||
const resultText = isSucceed ? '成功' : '失败';
|
||||
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: `依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}`,
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(
|
||||
depIds,
|
||||
`依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}\n`,
|
||||
);
|
||||
cp.stdout.on('data', (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: data.toString(),
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(depIds, data.toString());
|
||||
});
|
||||
|
||||
let status = null;
|
||||
if (isSucceed) {
|
||||
status = isInstall
|
||||
? DependenceStatus.installed
|
||||
: DependenceStatus.removed;
|
||||
} else {
|
||||
status = isInstall
|
||||
? DependenceStatus.installFailed
|
||||
: DependenceStatus.removeFailed;
|
||||
}
|
||||
this.dependenceDb.update(
|
||||
{ _id: { $in: depIds } },
|
||||
{
|
||||
$set: { status },
|
||||
$unset: { pid: true },
|
||||
},
|
||||
{ multi: true },
|
||||
);
|
||||
cp.stderr.on('data', (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: data.toString(),
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(depIds, data.toString());
|
||||
});
|
||||
|
||||
// 如果删除依赖成功,3秒后删除数据库记录
|
||||
if (isSucceed && !isInstall) {
|
||||
setTimeout(() => {
|
||||
this.removeDb(depIds);
|
||||
}, 5000);
|
||||
}
|
||||
cp.on('error', (err) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: JSON.stringify(err),
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(depIds, JSON.stringify(err));
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
cp.on('close', (code) => {
|
||||
const endTime = Date.now();
|
||||
const isSucceed = code === 0;
|
||||
const resultText = isSucceed ? '成功' : '失败';
|
||||
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: `依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
references: depIds,
|
||||
});
|
||||
this.updateLog(
|
||||
depIds,
|
||||
`依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
);
|
||||
|
||||
let status = null;
|
||||
if (isSucceed) {
|
||||
status = isInstall
|
||||
? DependenceStatus.installed
|
||||
: DependenceStatus.removed;
|
||||
} else {
|
||||
status = isInstall
|
||||
? DependenceStatus.installFailed
|
||||
: DependenceStatus.removeFailed;
|
||||
}
|
||||
this.dependenceDb.update(
|
||||
{ _id: { $in: depIds } },
|
||||
{
|
||||
$set: { status },
|
||||
$unset: { pid: true },
|
||||
},
|
||||
{ multi: true },
|
||||
);
|
||||
|
||||
// 如果删除依赖成功,3秒后删除数据库记录
|
||||
if (isSucceed && !isInstall) {
|
||||
setTimeout(() => {
|
||||
this.removeDb(depIds);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -113,7 +113,9 @@ export default class EnvService {
|
||||
): Promise<Env[]> {
|
||||
let condition = { ...query };
|
||||
if (searchText) {
|
||||
const reg = new RegExp(searchText);
|
||||
const encodeText = encodeURIComponent(searchText);
|
||||
const reg = new RegExp(`${searchText}|${encodeText}`, 'i');
|
||||
|
||||
condition = {
|
||||
$or: [
|
||||
{
|
||||
@@ -219,11 +221,17 @@ export default class EnvService {
|
||||
|
||||
// 忽略不符合bash要求的环境变量名称
|
||||
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) {
|
||||
env_string += `export ${key}="${_(group)
|
||||
let value = _(group)
|
||||
.filter((x) => x.status !== EnvStatus.disabled)
|
||||
.map('value')
|
||||
.join('&')
|
||||
.replace(/ /g, '')}"\n`;
|
||||
.replace(/ /g, '');
|
||||
if (/"/.test(value)) {
|
||||
value = `'${value}'`;
|
||||
} else {
|
||||
value = `"${value}"`;
|
||||
}
|
||||
env_string += `export ${key}=${value}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,9 @@ export default class OpenService {
|
||||
): Promise<App[]> {
|
||||
let condition = { ...query };
|
||||
if (searchText) {
|
||||
const reg = new RegExp(searchText);
|
||||
const encodeText = encodeURIComponent(searchText);
|
||||
const reg = new RegExp(`${searchText}|${encodeText}`, 'i');
|
||||
|
||||
condition = {
|
||||
$or: [
|
||||
{
|
||||
|
||||
+22
-4
@@ -2,21 +2,19 @@ import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import CronService from './cron';
|
||||
|
||||
@Service()
|
||||
export default class ScriptService {
|
||||
constructor(
|
||||
@Inject('logger') private logger: winston.Logger,
|
||||
private sockService: SockService,
|
||||
private cronService: CronService,
|
||||
) {}
|
||||
|
||||
public async runScript(path: string) {
|
||||
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
|
||||
|
||||
this.sockService.sendMessage({
|
||||
type: 'manuallyRunScript',
|
||||
message: `开始执行脚本`,
|
||||
});
|
||||
cp.stdout.on('data', (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'manuallyRunScript',
|
||||
@@ -38,6 +36,26 @@ export default class ScriptService {
|
||||
});
|
||||
});
|
||||
|
||||
cp.on('close', (err) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'manuallyRunScript',
|
||||
message: `执行结束`,
|
||||
});
|
||||
});
|
||||
|
||||
return { code: 200 };
|
||||
}
|
||||
|
||||
public async stopScript(path: string) {
|
||||
const err = await this.cronService.killTask(`task -l ${path} now`);
|
||||
const str = err ? `\n${err}` : '';
|
||||
this.sockService.sendMessage({
|
||||
type: 'manuallyRunScript',
|
||||
message: `${str}\n## 执行结束... ${new Date()
|
||||
.toLocaleString('zh', { hour12: false })
|
||||
.replace(' 24:', ' 00:')} `,
|
||||
});
|
||||
|
||||
return { code: 200 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import config from '../config';
|
||||
import * as fs from 'fs';
|
||||
import _ from 'lodash';
|
||||
import { AuthDataType, AuthInfo, LoginStatus } from '../data/auth';
|
||||
import { NotificationInfo } from '../data/notify';
|
||||
import NotificationService from './notify';
|
||||
import ScheduleService from './schedule';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import got from 'got';
|
||||
import { dbs } from '../loaders/db';
|
||||
|
||||
@Service()
|
||||
export default class SystemService {
|
||||
@Inject((type) => NotificationService)
|
||||
private notificationService!: NotificationService;
|
||||
private authDb = dbs.authDb;
|
||||
|
||||
constructor(
|
||||
@Inject('logger') private logger: winston.Logger,
|
||||
private scheduleService: ScheduleService,
|
||||
private sockService: SockService,
|
||||
) {}
|
||||
|
||||
public async getLogRemoveFrequency() {
|
||||
return new Promise((resolve) => {
|
||||
this.authDb
|
||||
.find({ type: AuthDataType.removeLogFrequency })
|
||||
.exec((err, docs) => {
|
||||
if (err || docs.length === 0) {
|
||||
resolve({});
|
||||
} else {
|
||||
resolve(docs[0].info);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async updateAuthDb(payload: AuthInfo): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
this.authDb.update(
|
||||
{ type: payload.type },
|
||||
{ ...payload },
|
||||
{ upsert: true, returnUpdatedDocs: true },
|
||||
(err, num, doc: any) => {
|
||||
if (err) {
|
||||
resolve({} as NotificationInfo);
|
||||
} else {
|
||||
resolve({ ...doc.info, _id: doc._id });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public async updateNotificationMode(notificationInfo: NotificationInfo) {
|
||||
const code = Math.random().toString().slice(-6);
|
||||
const isSuccess = await this.notificationService.testNotify(
|
||||
notificationInfo,
|
||||
'青龙',
|
||||
`【蛟龙】测试通知 https://t.me/jiao_long`,
|
||||
);
|
||||
if (isSuccess) {
|
||||
const result = await this.updateAuthDb({
|
||||
type: AuthDataType.notification,
|
||||
info: { ...notificationInfo },
|
||||
});
|
||||
return { code: 200, data: { ...result, code } };
|
||||
} else {
|
||||
return { code: 400, data: '通知发送失败,请检查参数' };
|
||||
}
|
||||
}
|
||||
|
||||
public async updateLogRemoveFrequency(frequency: number) {
|
||||
const result = await this.updateAuthDb({
|
||||
type: AuthDataType.removeLogFrequency,
|
||||
info: { frequency },
|
||||
});
|
||||
const cron = {
|
||||
_id: result._id,
|
||||
name: '删除日志',
|
||||
command: `ql rmlog ${frequency}`,
|
||||
schedule: `5 23 */${frequency} * *`,
|
||||
};
|
||||
await this.scheduleService.cancelSchedule(cron);
|
||||
if (frequency > 0) {
|
||||
await this.scheduleService.generateSchedule(cron);
|
||||
}
|
||||
return { code: 200, data: { ...cron } };
|
||||
}
|
||||
|
||||
public async checkUpdate() {
|
||||
try {
|
||||
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
||||
const logRegx = /.*export const changeLog = \`((.*\n.*)+)\`;/;
|
||||
|
||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
||||
|
||||
let lastVersion = '';
|
||||
let lastLog = '';
|
||||
try {
|
||||
const result = await Promise.race([
|
||||
got.get(config.lastVersionFile, { timeout: 1000, retry: 0 }),
|
||||
got.get(`https://ghproxy.com/${config.lastVersionFile}`, {
|
||||
timeout: 5000,
|
||||
retry: 0,
|
||||
}),
|
||||
]);
|
||||
const lastVersionFileContent = result.body;
|
||||
lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
||||
lastLog = lastVersionFileContent.match(logRegx)
|
||||
? lastVersionFileContent.match(logRegx)![1]
|
||||
: '';
|
||||
} catch (error) {}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: {
|
||||
hasNewVersion: this.checkHasNewVersion(currentVersion, lastVersion),
|
||||
lastVersion,
|
||||
lastLog,
|
||||
},
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
code: 400,
|
||||
data: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private checkHasNewVersion(curVersion: string, lastVersion: string) {
|
||||
const curArr = curVersion.split('.').map((x) => parseInt(x, 10));
|
||||
const lastArr = lastVersion.split('.').map((x) => parseInt(x, 10));
|
||||
if (curArr[0] < lastArr[0]) {
|
||||
return true;
|
||||
}
|
||||
if (curArr[0] === lastArr[0] && curArr[1] < lastArr[1]) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
curArr[0] === lastArr[0] &&
|
||||
curArr[1] === lastArr[1] &&
|
||||
curArr[2] < lastArr[2]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async updateSystem() {
|
||||
const cp = spawn('ql -l update', { shell: '/bin/bash' });
|
||||
|
||||
this.sockService.sendMessage({
|
||||
type: 'updateSystemVersion',
|
||||
message: `开始更新系统`,
|
||||
});
|
||||
cp.stdout.on('data', (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'updateSystemVersion',
|
||||
message: data.toString(),
|
||||
});
|
||||
});
|
||||
|
||||
cp.stderr.on('data', (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'updateSystemVersion',
|
||||
message: data.toString(),
|
||||
});
|
||||
});
|
||||
|
||||
cp.on('error', (err) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'updateSystemVersion',
|
||||
message: JSON.stringify(err),
|
||||
});
|
||||
});
|
||||
|
||||
return { code: 200 };
|
||||
}
|
||||
}
|
||||
+16
-7
@@ -403,13 +403,22 @@ export default class UserService {
|
||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
||||
|
||||
const lastVersionFileContent = await (
|
||||
await got.get(config.lastVersionFile)
|
||||
).body;
|
||||
const lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
||||
const lastLog = lastVersionFileContent.match(logRegx)
|
||||
? lastVersionFileContent.match(logRegx)![1]
|
||||
: '';
|
||||
let lastVersion = '';
|
||||
let lastLog = '';
|
||||
try {
|
||||
const result = await Promise.race([
|
||||
got.get(config.lastVersionFile, { timeout: 1000, retry: 0 }),
|
||||
got.get(`https://ghproxy.com/${config.lastVersionFile}`, {
|
||||
timeout: 5000,
|
||||
retry: 0,
|
||||
}),
|
||||
]);
|
||||
const lastVersionFileContent = result.body;
|
||||
lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
||||
lastLog = lastVersionFileContent.match(logRegx)
|
||||
? lastVersionFileContent.match(logRegx)![1]
|
||||
: '';
|
||||
} catch (error) {}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { CommandModule } from 'yargs';
|
||||
export const updateCommand: CommandModule = {
|
||||
command: 'update',
|
||||
describe: 'Update and restart qinglong',
|
||||
builder: (yargs) => {
|
||||
return yargs.option('repositority', {
|
||||
type: 'string',
|
||||
alias: 'r',
|
||||
describe: `Specify the release warehouse address of the package`,
|
||||
});
|
||||
},
|
||||
handler: async (argv) => {},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as yargs from 'yargs';
|
||||
import { green, red } from 'chalk';
|
||||
import { updateCommand } from './commands/update';
|
||||
|
||||
yargs
|
||||
.usage('Usage: ql [command] <options>')
|
||||
.command(updateCommand)
|
||||
.fail((err) => {
|
||||
console.error(`${red(err)}`);
|
||||
})
|
||||
.alias('h', 'help')
|
||||
.showHelp()
|
||||
.recommendCommands().argv;
|
||||
@@ -6,6 +6,7 @@ link_shell
|
||||
|
||||
echo -e "======================1. 检测配置文件========================\n"
|
||||
make_dir /etc/nginx/conf.d
|
||||
make_dir /run/nginx
|
||||
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||
pm2 l &>/dev/null
|
||||
|
||||
+3
-1
@@ -29,6 +29,7 @@
|
||||
"@sentry/tracing": "^6.14.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"celebrate": "^13.0.3",
|
||||
"chokidar": "^3.5.2",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^3.5.0",
|
||||
"dotenv": "^8.2.0",
|
||||
@@ -50,7 +51,8 @@
|
||||
"sockjs": "^0.3.21",
|
||||
"typedi": "^0.8.0",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.3.3"
|
||||
"winston": "^3.3.3",
|
||||
"yargs": "^17.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
|
||||
@@ -285,6 +285,7 @@ def pushplus_bot(title: str, content: str) -> None:
|
||||
else:
|
||||
|
||||
url_old = "http://pushplus.hxtrip.com/send"
|
||||
headers["Accept"] = "application/json"
|
||||
response = requests.post(url=url_old, data=body, headers=headers).json()
|
||||
|
||||
if response["code"] == 200:
|
||||
|
||||
@@ -35,6 +35,8 @@ file_notify_js=$dir_scripts/sendNotify.js
|
||||
task_error_log_path=$dir_log/task_error.log
|
||||
nginx_app_conf=$dir_root/docker/front.conf
|
||||
nginx_conf=$dir_root/docker/nginx.conf
|
||||
dep_notify_py=$dir_dep/notify.py
|
||||
dep_notify_js=$dir_dep/sendNotify.js
|
||||
|
||||
## 清单文件
|
||||
list_crontab_user=$dir_config/crontab.list
|
||||
@@ -210,6 +212,19 @@ fix_config() {
|
||||
cat /dev/null > /etc/nginx/conf.d/default.conf
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -s $dep_notify_js ]]; then
|
||||
echo -e "复制一份 $file_notify_js_sample 为 $dep_notify_js\n"
|
||||
cp -fv $file_notify_js_sample $dep_notify_js
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -s $dep_notify_py ]]; then
|
||||
echo -e "复制一份 $file_notify_py_sample 为 $dep_notify_py\n"
|
||||
cp -fv $file_notify_py_sample $dep_notify_py
|
||||
echo
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
npm_install_sub() {
|
||||
|
||||
+20
-7
@@ -264,13 +264,18 @@ run_designated() {
|
||||
|
||||
## 运行其他命令
|
||||
run_else() {
|
||||
local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||
local log_dir_tmp="${1##*/}"
|
||||
local log_dir="$dir_log/${log_dir_tmp%%.*}"
|
||||
local file_param="$1"
|
||||
define_program "$file_param"
|
||||
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
log_dir_tmp_path="${file_param%%/*}"
|
||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="$dir_log/${log_dir_tmp%%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd="&>> $log_path"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$log_dir"
|
||||
make_dir $log_dir
|
||||
|
||||
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local begin_timestamp=$(date "+%s" -d "$begin_time")
|
||||
@@ -278,13 +283,21 @@ run_else() {
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $first_param" | perl -pe "s|.*ID=(.*) $cmd_task $first_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $@" | perl -pe "s|.*ID=(.*) $cmd_task $@\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||
eval . $file_task_before "$@" $cmd
|
||||
|
||||
eval timeout -k 10s $command_timeout_time "$@" $cmd
|
||||
cd $dir_scripts
|
||||
local relative_path="${file_param%/*}"
|
||||
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||
cd ${relative_path}
|
||||
file_param=${file_param/$relative_path\//}
|
||||
fi
|
||||
|
||||
eval . $file_task_after "$@" $cmd
|
||||
shift
|
||||
eval timeout -k 10s $command_timeout_time $which_program "$file_param" "$@" $cmd
|
||||
|
||||
eval . $file_task_after "$file_param" "$@" $cmd
|
||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local end_timestamp=$(date "+%s" -d "$end_time")
|
||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
||||
|
||||
@@ -79,6 +79,5 @@ export default {
|
||||
fixSiderbar: true,
|
||||
contentWidth: 'Fixed',
|
||||
splitMenus: false,
|
||||
logo: 'https://z3.ax1x.com/2021/11/18/I7MpAe.png',
|
||||
siderWidth: 180,
|
||||
} as any;
|
||||
|
||||
+20
-2
@@ -152,8 +152,8 @@
|
||||
|
||||
.ant-layout-content.ant-pro-basicLayout-content.ant-pro-basicLayout-has-header {
|
||||
margin-bottom: 0 !important;
|
||||
min-height: calc(100vh - 66px);
|
||||
min-height: calc(100vh - var(--vh-offset, 0px) - 66px);
|
||||
min-height: calc(100vh - 72px);
|
||||
min-height: calc(100vh - var(--vh-offset, 0px) - 72px);
|
||||
}
|
||||
|
||||
.Resizer {
|
||||
@@ -293,3 +293,21 @@
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-sider-logo {
|
||||
padding: 16px 8px !important;
|
||||
h1 {
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
img {
|
||||
width: 32px !important;
|
||||
border-radius: 52% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-global-header-logo {
|
||||
a img {
|
||||
// 移动端logo被拉伸
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-11
@@ -20,14 +20,14 @@ import './index.less';
|
||||
import vhCheck from 'vh-check';
|
||||
import { version, changeLogLink, changeLog } from '../version';
|
||||
import { useCtx, useTheme } from '@/utils/hooks';
|
||||
import { message, Badge, Modal, Avatar, Dropdown, Menu, Popover } from 'antd';
|
||||
import { message, Badge, Modal, Avatar, Dropdown, Menu, Image } from 'antd';
|
||||
// @ts-ignore
|
||||
import SockJS from 'sockjs-client';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Integrations } from '@sentry/tracing';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://ea2fede373244db99c536210b910d9da@o1051273.ingest.sentry.io/6047851',
|
||||
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
release: version,
|
||||
tracesSampleRate: 1.0,
|
||||
@@ -44,7 +44,7 @@ export default function (props: any) {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const logout = () => {
|
||||
request.post(`${config.apiPrefix}logout`).then(() => {
|
||||
request.post(`${config.apiPrefix}user/logout`).then(() => {
|
||||
localStorage.removeItem(config.authKey);
|
||||
history.push('/login');
|
||||
});
|
||||
@@ -232,6 +232,12 @@ export default function (props: any) {
|
||||
selectedKeys={[props.location.pathname]}
|
||||
loading={loading}
|
||||
ErrorBoundary={Sentry.ErrorBoundary}
|
||||
logo={
|
||||
<Image
|
||||
preview={false}
|
||||
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<>
|
||||
<span style={{ fontSize: 16 }}>控制面板</span>
|
||||
@@ -275,14 +281,16 @@ export default function (props: any) {
|
||||
}}
|
||||
onCollapse={setCollapsed}
|
||||
collapsed={collapsed}
|
||||
rightContentRender={() => (
|
||||
<Dropdown overlay={menu} trigger={['click']}>
|
||||
<span className="side-menu-user-wrapper">
|
||||
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
||||
<span style={{ marginLeft: 5 }}>admin</span>
|
||||
</span>
|
||||
</Dropdown>
|
||||
)}
|
||||
rightContentRender={() =>
|
||||
ctx.isPhone && (
|
||||
<Dropdown overlay={menu} trigger={['click']}>
|
||||
<span className="side-menu-user-wrapper">
|
||||
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
||||
<span style={{ marginLeft: 5 }}>admin</span>
|
||||
</span>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
collapsedButtonRender={(collapsed) => (
|
||||
<span
|
||||
className="side-menu-container"
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
.ant-table-pagination.ant-pagination {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
tr {
|
||||
td:nth-child(2) {
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
||||
width: 150,
|
||||
align: 'center' as const,
|
||||
render: (text: string, record: any) => (
|
||||
<span
|
||||
<a
|
||||
onClick={() => {
|
||||
goToScriptManager(record);
|
||||
}}
|
||||
@@ -90,7 +90,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
</a>
|
||||
),
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.name.localeCompare(b.name),
|
||||
@@ -335,8 +335,12 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||
|
||||
const goToScriptManager = (record: any) => {
|
||||
const cmd = record.command.split(' ');
|
||||
const cmd = record.command.split(' ') as string[];
|
||||
if (cmd[0] === 'task') {
|
||||
if (cmd[1].startsWith('/ql/scripts')) {
|
||||
cmd[1] = cmd[1].replace('/ql/scripts/', '');
|
||||
}
|
||||
|
||||
let [p, s] = cmd[1].split('/');
|
||||
if (!s) {
|
||||
s = p;
|
||||
@@ -799,7 +803,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
||||
onSearch={onSearch}
|
||||
/>,
|
||||
<Button key="2" type="primary" onClick={() => addCron()}>
|
||||
添加任务
|
||||
新建任务
|
||||
</Button>,
|
||||
]}
|
||||
header={{
|
||||
|
||||
@@ -27,7 +27,7 @@ const CronModal = ({
|
||||
data: payload,
|
||||
});
|
||||
if (code === 200) {
|
||||
message.success(cron ? '更新Cron成功' : '添加Cron成功');
|
||||
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
onSearch={onSearch}
|
||||
/>,
|
||||
<Button key="2" type="primary" onClick={() => addDependence()}>
|
||||
添加依赖
|
||||
新建依赖
|
||||
</Button>,
|
||||
]}
|
||||
header={{
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
}
|
||||
|
||||
.diff-switch-file {
|
||||
min-width: 768px;
|
||||
.ant-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -485,7 +485,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
onSearch={onSearch}
|
||||
/>,
|
||||
<Button key="2" type="primary" onClick={() => addEnv()}>
|
||||
添加变量
|
||||
新建变量
|
||||
</Button>,
|
||||
]}
|
||||
header={{
|
||||
|
||||
Vendored
+2
-2
@@ -40,7 +40,7 @@ const EnvModal = ({
|
||||
data: payload,
|
||||
});
|
||||
if (code === 200) {
|
||||
message.success(env ? '更新变量成功' : '添加变量成功');
|
||||
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ const EnvModal = ({
|
||||
rules={[
|
||||
{ required: true, message: '请输入环境变量名称', whitespace: true },
|
||||
{
|
||||
pattern: /^[a-zA-Z_][0-9a-zA-Z_]+$/,
|
||||
pattern: /^[a-zA-Z_][0-9a-zA-Z_]*$/,
|
||||
message: '只能输入字母数字下划线,且不能以数字开头',
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -35,7 +35,7 @@ const Initialization = () => {
|
||||
const submitAccountSetting = (values: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.put(`${config.apiPrefix}init/user`, {
|
||||
.put(`${config.apiPrefix}user/init`, {
|
||||
data: {
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
@@ -52,8 +52,9 @@ const Initialization = () => {
|
||||
};
|
||||
|
||||
const submitNotification = (values: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.put(`${config.apiPrefix}init/notification`, {
|
||||
.put(`${config.apiPrefix}user/notification/init`, {
|
||||
data: {
|
||||
...values,
|
||||
},
|
||||
@@ -73,6 +74,10 @@ const Initialization = () => {
|
||||
setFields(_fields || []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.removeItem(config.authKey);
|
||||
}, []);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: '欢迎使用',
|
||||
@@ -228,7 +233,7 @@ const Initialization = () => {
|
||||
<img
|
||||
alt="logo"
|
||||
className={styles.logo}
|
||||
src="https://z3.ax1x.com/2021/11/18/I7MpAe.png"
|
||||
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||
/>
|
||||
<span className={styles.title}>初始化配置</span>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const Login = () => {
|
||||
setTwoFactor(false);
|
||||
setWaitTime(null);
|
||||
request
|
||||
.post(`${config.apiPrefix}login`, {
|
||||
.post(`${config.apiPrefix}user/login`, {
|
||||
data: {
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
@@ -113,6 +113,14 @@ const Login = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const codeInputChange = (e: React.ChangeEvent) => {
|
||||
const { value } = e.target as any;
|
||||
const regx = /^[0-9]{6}$/;
|
||||
if (regx.test(value)) {
|
||||
completeTowFactor({ code: value });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const isAuth = localStorage.getItem(config.authKey);
|
||||
if (isAuth) {
|
||||
@@ -127,7 +135,7 @@ const Login = () => {
|
||||
<img
|
||||
alt="logo"
|
||||
className={styles.logo}
|
||||
src="https://z3.ax1x.com/2021/11/18/I7MpAe.png"
|
||||
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||
/>
|
||||
<span className={styles.title}>
|
||||
{twoFactor ? '两步验证' : config.siteName}
|
||||
@@ -144,12 +152,15 @@ const Login = () => {
|
||||
{
|
||||
pattern: /^[0-9]{6}$/,
|
||||
message: '验证码为6位数字',
|
||||
validateTrigger: 'onBlur',
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input placeholder="6位数字" autoFocus autoComplete="off" />
|
||||
<Input
|
||||
placeholder="6位数字"
|
||||
onChange={codeInputChange}
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormItem>
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -24,22 +24,22 @@ const prefixMap: any = {
|
||||
|
||||
const EditModal = ({
|
||||
treeData,
|
||||
currentFile,
|
||||
currentNode,
|
||||
content,
|
||||
handleCancel,
|
||||
visible,
|
||||
socketMessage,
|
||||
}: {
|
||||
treeData?: any;
|
||||
currentFile?: string;
|
||||
content?: string;
|
||||
visible: boolean;
|
||||
socketMessage: any;
|
||||
currentNode: any;
|
||||
handleCancel: () => void;
|
||||
}) => {
|
||||
const [value, setValue] = useState('');
|
||||
const [language, setLanguage] = useState<string>('javascript');
|
||||
const [fileName, setFileName] = useState<string>('');
|
||||
const [cNode, setCNode] = useState<any>();
|
||||
const [selectedKey, setSelectedKey] = useState<string>('');
|
||||
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
||||
const [settingModalVisible, setSettingModalVisible] =
|
||||
@@ -47,37 +47,56 @@ const EditModal = ({
|
||||
const [log, setLog] = useState<string>('');
|
||||
const { theme } = useTheme();
|
||||
const editorRef = useRef<any>(null);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
const cancel = () => {
|
||||
handleCancel();
|
||||
};
|
||||
|
||||
const onSelect = (value: any, node: any) => {
|
||||
if (node.value === fileName || !value) {
|
||||
if (node.key === selectedKey || !value) {
|
||||
return;
|
||||
}
|
||||
const newMode = LangMap[value.slice(-3)] || '';
|
||||
setFileName(value);
|
||||
setCNode(node);
|
||||
setLanguage(newMode);
|
||||
getDetail(node);
|
||||
setSelectedKey(node.key);
|
||||
};
|
||||
|
||||
const getDetail = (node: any) => {
|
||||
request.get(`${config.apiPrefix}scripts/${node.value}`).then((data) => {
|
||||
setValue(data.data);
|
||||
});
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
});
|
||||
};
|
||||
|
||||
const run = () => {
|
||||
setLog('');
|
||||
request
|
||||
.put(`${config.apiPrefix}scripts/run`, {
|
||||
data: {
|
||||
filename: fileName,
|
||||
path: '',
|
||||
filename: cNode.value,
|
||||
path: cNode.parent || '',
|
||||
},
|
||||
})
|
||||
.then((data) => {});
|
||||
.then((data) => {
|
||||
setIsRunning(true);
|
||||
});
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
request
|
||||
.put(`${config.apiPrefix}scripts/stop`, {
|
||||
data: {
|
||||
filename: cNode.value,
|
||||
path: cNode.parent || '',
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
setIsRunning(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -91,6 +110,13 @@ const EditModal = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (_message === '执行结束') {
|
||||
_message = '';
|
||||
setTimeout(() => {
|
||||
setIsRunning(false);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
if (log) {
|
||||
_message = `\n${_message}`;
|
||||
}
|
||||
@@ -98,21 +124,21 @@ const EditModal = ({
|
||||
}, [socketMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentFile) {
|
||||
setFileName(currentFile);
|
||||
if (currentNode) {
|
||||
setCNode(currentNode);
|
||||
setValue(content as string);
|
||||
setSelectedKey(currentFile);
|
||||
setSelectedKey(currentNode.key);
|
||||
}
|
||||
}, [currentFile, content]);
|
||||
}, [content, currentNode]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
className="edit-modal"
|
||||
closable={false}
|
||||
title={
|
||||
<>
|
||||
<span style={{ marginRight: 8 }}>{fileName}</span>
|
||||
<TreeSelect
|
||||
style={{ marginRight: 8, width: 120 }}
|
||||
style={{ marginRight: 8, width: 150 }}
|
||||
value={selectedKey}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={treeData}
|
||||
@@ -122,7 +148,7 @@ const EditModal = ({
|
||||
/>
|
||||
<Select
|
||||
value={language}
|
||||
style={{ width: 120, marginRight: 8 }}
|
||||
style={{ width: 110, marginRight: 8 }}
|
||||
onChange={(e) => {
|
||||
setLanguage(e);
|
||||
}}
|
||||
@@ -132,8 +158,12 @@ const EditModal = ({
|
||||
<Option value="shell">shell</Option>
|
||||
<Option value="python">python</Option>
|
||||
</Select>
|
||||
<Button type="primary" style={{ marginRight: 8 }} onClick={run}>
|
||||
运行
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={isRunning ? stop : run}
|
||||
>
|
||||
{isRunning ? '停止' : '运行'}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -162,6 +192,16 @@ const EditModal = ({
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => {
|
||||
stop();
|
||||
handleCancel();
|
||||
}}
|
||||
>
|
||||
退出
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
width={'100%'}
|
||||
@@ -169,7 +209,12 @@ const EditModal = ({
|
||||
onClose={cancel}
|
||||
visible={visible}
|
||||
>
|
||||
<SplitPane split="vertical" minSize={200} defaultSize="50%">
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
minSize={200}
|
||||
defaultSize="50%"
|
||||
style={{ height: 'calc(100vh - 55px)' }}
|
||||
>
|
||||
<Editor
|
||||
language={language}
|
||||
value={value}
|
||||
@@ -184,9 +229,7 @@ const EditModal = ({
|
||||
editorRef.current = editor;
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<pre>{log}</pre>
|
||||
</div>
|
||||
<pre style={{ height: '100%', whiteSpace: 'break-spaces' }}>{log}</pre>
|
||||
</SplitPane>
|
||||
<SaveModal
|
||||
visible={saveModalVisible}
|
||||
@@ -197,7 +240,7 @@ const EditModal = ({
|
||||
content:
|
||||
editorRef.current &&
|
||||
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
||||
filename: fileName,
|
||||
filename: cNode?.value,
|
||||
}}
|
||||
/>
|
||||
<SettingModal
|
||||
|
||||
@@ -116,12 +116,12 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
node: {
|
||||
title: s,
|
||||
value: s,
|
||||
key: p ? `${p}-${s}` : s,
|
||||
key: p ? `${p}/${s}` : s,
|
||||
parent: p,
|
||||
},
|
||||
};
|
||||
setExpandedKeys([p]);
|
||||
onTreeSelect([`${p}-${s}`], obj);
|
||||
onTreeSelect([`${p}/${s}`], obj);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -377,7 +377,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
) : (
|
||||
<Menu>
|
||||
<Menu.Item key="add" icon={<PlusOutlined />} onClick={addFile}>
|
||||
添加
|
||||
新建
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="edit"
|
||||
@@ -525,7 +525,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
<EditModal
|
||||
visible={isLogModalVisible}
|
||||
treeData={data}
|
||||
currentFile={select}
|
||||
currentNode={currentNode}
|
||||
content={value}
|
||||
socketMessage={socketMessage}
|
||||
handleCancel={() => {
|
||||
|
||||
@@ -26,7 +26,7 @@ const AppModal = ({
|
||||
data: payload,
|
||||
});
|
||||
if (code === 200) {
|
||||
message.success(app ? '更新应用成功' : '添加应用成功');
|
||||
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
if (data.hasNewVersion) {
|
||||
showConfirmUpdateModal(data);
|
||||
} else {
|
||||
message.success('已经是最新版了!');
|
||||
showForceUpdateModal();
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
@@ -39,6 +39,32 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const showForceUpdateModal = () => {
|
||||
Modal.confirm({
|
||||
width: 500,
|
||||
title: '更新',
|
||||
content: (
|
||||
<>
|
||||
<div>已经是最新版了!</div>
|
||||
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
|
||||
青龙 {version} 是目前检测到的最新可用版本了。
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
okText: '确认',
|
||||
cancelText: '强制更新',
|
||||
onCancel() {
|
||||
showUpdatingModal();
|
||||
request
|
||||
.put(`${config.apiPrefix}system/update`)
|
||||
.then((_data: any) => {})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const showConfirmUpdateModal = (data: any) => {
|
||||
const { lastVersion, lastLog } = data;
|
||||
Modal.confirm({
|
||||
|
||||
@@ -319,7 +319,7 @@ const Setting = ({
|
||||
tabActiveKey === 'app'
|
||||
? [
|
||||
<Button key="2" type="primary" onClick={() => addApp()}>
|
||||
添加应用
|
||||
新建应用
|
||||
</Button>,
|
||||
]
|
||||
: []
|
||||
|
||||
@@ -55,6 +55,14 @@ export default {
|
||||
name: '任务日志',
|
||||
value: 'logs',
|
||||
},
|
||||
{
|
||||
name: '依赖管理',
|
||||
value: 'dependencies',
|
||||
},
|
||||
{
|
||||
name: '系统信息',
|
||||
value: 'system',
|
||||
},
|
||||
],
|
||||
scopesMap: {
|
||||
crons: '定时任务',
|
||||
|
||||
+3
-3
@@ -34,12 +34,12 @@ const errorHandler = function (error: any) {
|
||||
|
||||
const _request = extend({ timeout: 60000, params: { t: time }, errorHandler });
|
||||
const apiWhiteList = [
|
||||
'/api/login',
|
||||
'/api/user/login',
|
||||
'/open/auth/token',
|
||||
'/api/user/two-factor/login',
|
||||
'/api/system',
|
||||
'/api/init/user',
|
||||
'/api/init/notification',
|
||||
'/api/user/init',
|
||||
'/api/user/notification/init',
|
||||
];
|
||||
|
||||
_request.interceptors.request.use((url, options) => {
|
||||
|
||||
+8
-9
@@ -1,10 +1,9 @@
|
||||
export const version = '2.10.9';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/228';
|
||||
export const changeLog = `2.10.9 版本说明
|
||||
1. 任务管理支持任务名跳转脚本管理页,感谢 https://github.com/kilo5hz PR
|
||||
2. 系统通知支持gotify,感谢 https://github.com/kilo5hz PR
|
||||
3. 定时任务列表pageSize增加200/500/1000,感谢 https://github.com/fzls PR
|
||||
4. 修复deps目录依赖文件拷贝
|
||||
5. 修改alpine基础镜像版本,解决arm32位系统无法启动容器,感谢 https://github.com/lx200916
|
||||
6. 修复调试功能
|
||||
export const version = '2.10.13';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/233';
|
||||
export const changeLog = `2.10.13 版本说明
|
||||
1. task 命令执行脚本支持任意参数,比如 task xxx.js a b c d
|
||||
2. 应用权限增加依赖管理、系统信息
|
||||
3. 调试脚本增加停止操作,优化交互
|
||||
4. 定时任务任务名修改展示效果,感谢https://github.com/tinet-jutt
|
||||
5. 其他bug修复
|
||||
`;
|
||||
|
||||
+1
-7
@@ -27,13 +27,7 @@
|
||||
"allowJs": true,
|
||||
"noEmit": false
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"config/**/*",
|
||||
".umirc.ts",
|
||||
"typings.d.ts",
|
||||
"back/**/*"
|
||||
],
|
||||
"include": ["src/**/*", ".umirc.ts", "typings.d.ts", "back/**/*"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib",
|
||||
|
||||
@@ -2889,7 +2889,7 @@ chokidar@3.5.1:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.1"
|
||||
|
||||
chokidar@^3.2.2:
|
||||
chokidar@^3.2.2, chokidar@^3.5.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
|
||||
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
|
||||
@@ -2995,6 +2995,15 @@ cliui@^6.0.0:
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
cliui@^7.0.2:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
|
||||
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
clone-response@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||
@@ -4446,7 +4455,7 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
|
||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
||||
|
||||
get-caller-file@^2.0.1:
|
||||
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
@@ -10819,6 +10828,11 @@ y18n@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||
|
||||
yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||
@@ -10842,6 +10856,11 @@ yargs-parser@^18.1.2:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^20.2.2:
|
||||
version "20.2.9"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
||||
|
||||
yargs@^15.4.1:
|
||||
version "15.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||
@@ -10859,6 +10878,19 @@ yargs@^15.4.1:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.2"
|
||||
|
||||
yargs@^17.2.1:
|
||||
version "17.2.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
|
||||
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
string-width "^4.2.0"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
|
||||
Reference in New Issue
Block a user