mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 19:05:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
201f578bbc | ||
|
|
ce9aa369d2 | ||
|
|
c5c41c0859 | ||
|
|
d3009f82dd | ||
|
|
0eb9a1fdb0 | ||
|
|
83f6d9926a | ||
|
|
14134842b0 | ||
|
|
4c094c4710 | ||
|
|
3ac41075fd | ||
|
|
fdc97773d1 | ||
|
|
917a739595 | ||
|
|
21475f81ae | ||
|
|
a2a9a0560c | ||
|
|
b879846cdb | ||
|
|
a24011cd72 | ||
|
|
410dd2ecdc | ||
|
|
96b111b894 | ||
|
|
630572c8f9 | ||
|
|
6a381a6366 | ||
|
|
fd04f12677 | ||
|
|
86b8cd17a4 | ||
|
|
bb8ee01693 | ||
|
|
5fb0bca3af | ||
|
|
1e4d1f373d | ||
|
|
87c78d0fcc | ||
|
|
041d2b7947 | ||
|
|
5e7b90d995 | ||
|
|
fadc3dcb55 | ||
|
|
4eea091944 | ||
|
|
9f8d46f395 | ||
|
|
2c54a057c9 | ||
|
|
e1e5cacc6d | ||
|
|
edf9066967 | ||
|
|
7097d12b87 | ||
|
|
08aaeb6372 | ||
|
|
4310f80ec0 | ||
|
|
01577f6494 | ||
|
|
9aa570b2c6 | ||
|
|
d611d7d698 | ||
|
|
f05549b06c | ||
|
|
8c4db8dd40 | ||
|
|
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 |
@@ -56,6 +56,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: '14'
|
||||||
|
|
||||||
- name: Set time zone
|
- name: Set time zone
|
||||||
uses: szenius/set-timezone@v1.0
|
uses: szenius/set-timezone@v1.0
|
||||||
@@ -105,7 +108,9 @@ jobs:
|
|||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
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
|
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x
|
||||||
context: docker/
|
context: docker/
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ export default defineConfig({
|
|||||||
scripts: [
|
scripts: [
|
||||||
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
'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://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://unpkg.zhimg.com/darkreader@4.9.40/darkreader.js',
|
||||||
'https://cdn.jsdelivr.net/npm/codemirror@5/lib/codemirror.min.js',
|
'https://unpkg.zhimg.com/codemirror@5/lib/codemirror.js',
|
||||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/shell/shell.js',
|
'https://unpkg.zhimg.com/codemirror@5/mode/shell/shell.js',
|
||||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/python/python.js',
|
'https://unpkg.zhimg.com/codemirror@5/mode/python/python.js',
|
||||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/javascript/javascript.js',
|
'https://unpkg.zhimg.com/codemirror@5/mode/javascript/javascript.js',
|
||||||
'https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js',
|
'https://unpkg.zhimg.com/sockjs-client@1/dist/sockjs.min.js',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-4
@@ -8,10 +8,10 @@ import { celebrate, Joi } from 'celebrate';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/configs', route);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/configs/files',
|
'/files',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -32,7 +32,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/configs/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -58,7 +58,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/configs/save',
|
'/save',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
|
|||||||
+88
-45
@@ -7,31 +7,28 @@ import cron_parser from 'cron-parser';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/crons', route);
|
||||||
route.get(
|
|
||||||
'/crons',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const cronService = Container.get(CronService);
|
||||||
const cronService = Container.get(CronService);
|
const data = await cronService.crontabs(req.query.searchValue as string);
|
||||||
const data = await cronService.crontabs(
|
return res.send({ code: 200, data });
|
||||||
req.query.searchValue as string,
|
} catch (e) {
|
||||||
);
|
logger.error('🔥 error: %o', e);
|
||||||
return res.send({ code: 200, data });
|
return next(e);
|
||||||
} catch (e) {
|
}
|
||||||
logger.error('🔥 error: %o', e);
|
});
|
||||||
return next(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/crons',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
command: Joi.string().required(),
|
command: Joi.string().required(),
|
||||||
schedule: Joi.string().required(),
|
schedule: Joi.string().required(),
|
||||||
name: Joi.string().optional(),
|
name: Joi.string().optional(),
|
||||||
|
labels: Joi.array().optional(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -52,9 +49,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/run',
|
'/run',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -70,9 +67,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/stop',
|
'/stop',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -87,10 +84,55 @@ export default (app: Router) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.delete(
|
||||||
'/crons/disable',
|
'/labels',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.object({
|
||||||
|
ids: Joi.array().items(Joi.number().required()),
|
||||||
|
labels: Joi.array().items(Joi.string().required()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const cronService = Container.get(CronService);
|
||||||
|
const data = await cronService.removeLabels(
|
||||||
|
req.body.ids,
|
||||||
|
req.body.labels,
|
||||||
|
);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
'/labels',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
ids: Joi.array().items(Joi.number().required()),
|
||||||
|
labels: Joi.array().items(Joi.string().required()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const cronService = Container.get(CronService);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/disable',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -106,9 +148,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/enable',
|
'/enable',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -124,13 +166,13 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/crons/:id/log',
|
'/:id/log',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const cronService = Container.get(CronService);
|
const cronService = Container.get(CronService);
|
||||||
@@ -144,13 +186,14 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
|
labels: Joi.array().optional().allow(null),
|
||||||
command: Joi.string().optional(),
|
command: Joi.string().optional(),
|
||||||
schedule: Joi.string().optional(),
|
schedule: Joi.string().optional(),
|
||||||
name: Joi.string().optional(),
|
name: Joi.string().optional(),
|
||||||
_id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -174,9 +217,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/crons',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -192,9 +235,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/pin',
|
'/pin',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -210,9 +253,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/unpin',
|
'/unpin',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -228,7 +271,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/crons/import',
|
'/import',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -243,17 +286,17 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/crons/:id',
|
'/:id',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const cronService = Container.get(CronService);
|
const cronService = Container.get(CronService);
|
||||||
const data = await cronService.get(req.params.id);
|
const data = await cronService.getDb({ id: req.params.id });
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
logger.error('🔥 error: %o', e);
|
||||||
@@ -263,10 +306,10 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/status',
|
'/status',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
ids: Joi.array().items(Joi.string().required()),
|
ids: Joi.array().items(Joi.number().required()),
|
||||||
status: Joi.string().required(),
|
status: Joi.string().required(),
|
||||||
pid: Joi.string().optional(),
|
pid: Joi.string().optional(),
|
||||||
log_path: Joi.string().optional(),
|
log_path: Joi.string().optional(),
|
||||||
|
|||||||
+28
-30
@@ -6,30 +6,28 @@ import { celebrate, Joi } from 'celebrate';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/dependencies', route);
|
||||||
route.get(
|
|
||||||
'/dependencies',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const dependenceService = Container.get(DependenceService);
|
||||||
const dependenceService = Container.get(DependenceService);
|
const data = await dependenceService.dependencies(req.query as any);
|
||||||
const data = await dependenceService.dependencies(req.query as any);
|
return res.send({ code: 200, data });
|
||||||
return res.send({ code: 200, data });
|
} catch (e) {
|
||||||
} catch (e) {
|
logger.error('🔥 error: %o', e);
|
||||||
logger.error('🔥 error: %o', e);
|
return next(e);
|
||||||
return next(e);
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/dependencies',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(
|
body: Joi.array().items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
type: Joi.number().required(),
|
type: Joi.number().required(),
|
||||||
remark: Joi.number().optional().allow(''),
|
remark: Joi.string().optional().allow(''),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
@@ -47,13 +45,13 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/dependencies',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
_id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
type: Joi.number().required(),
|
type: Joi.number().required(),
|
||||||
remark: Joi.number().optional().allow(''),
|
remark: Joi.string().optional().allow(''),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -70,9 +68,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/dependencies',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -88,9 +86,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/dependencies/force',
|
'/force',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -106,17 +104,17 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/dependencies/:id',
|
'/:id',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const dependenceService = Container.get(DependenceService);
|
const dependenceService = Container.get(DependenceService);
|
||||||
const data = await dependenceService.get(req.params.id);
|
const data = await dependenceService.getDb({ id: req.params.id });
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
logger.error('🔥 error: %o', e);
|
||||||
@@ -126,9 +124,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/dependencies/reinstall',
|
'/reinstall',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
|
|||||||
+32
-34
@@ -6,24 +6,22 @@ import { celebrate, Joi } from 'celebrate';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/envs', route);
|
||||||
route.get(
|
|
||||||
'/envs',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const envService = Container.get(EnvService);
|
||||||
const envService = Container.get(EnvService);
|
const data = await envService.envs(req.query.searchValue as string);
|
||||||
const data = await envService.envs(req.query.searchValue as string);
|
return res.send({ code: 200, data });
|
||||||
return res.send({ code: 200, data });
|
} catch (e) {
|
||||||
} catch (e) {
|
logger.error('🔥 error: %o', e);
|
||||||
logger.error('🔥 error: %o', e);
|
return next(e);
|
||||||
return next(e);
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/envs',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(
|
body: Joi.array().items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
@@ -47,13 +45,13 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
value: Joi.string().required(),
|
value: Joi.string().required(),
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
remarks: Joi.string().optional().allow(''),
|
remarks: Joi.string().optional().allow('').allow(null),
|
||||||
_id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -70,9 +68,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/envs',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -88,17 +86,17 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/:id/move',
|
'/:id/move',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
fromIndex: Joi.number().required(),
|
fromIndex: Joi.number().required(),
|
||||||
toIndex: Joi.number().required(),
|
toIndex: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const envService = Container.get(EnvService);
|
const envService = Container.get(EnvService);
|
||||||
@@ -112,9 +110,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/disable',
|
'/disable',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -130,9 +128,9 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/enable',
|
'/enable',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -148,10 +146,10 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/name',
|
'/name',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
ids: Joi.array().items(Joi.string().required()),
|
ids: Joi.array().items(Joi.number().required()),
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -169,17 +167,17 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/envs/:id',
|
'/:id',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const envService = Container.get(EnvService);
|
const envService = Container.get(EnvService);
|
||||||
const data = await envService.get(req.params.id);
|
const data = await envService.getDb(req.params.id);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
logger.error('🔥 error: %o', e);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import cron from './cron';
|
|||||||
import script from './script';
|
import script from './script';
|
||||||
import open from './open';
|
import open from './open';
|
||||||
import dependence from './dependence';
|
import dependence from './dependence';
|
||||||
|
import system from './system';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -18,6 +19,7 @@ export default () => {
|
|||||||
script(app);
|
script(app);
|
||||||
open(app);
|
open(app);
|
||||||
dependence(app);
|
dependence(app);
|
||||||
|
system(app);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|||||||
+33
-35
@@ -7,44 +7,42 @@ import { getFileContentByName } from '../config/util';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/logs', route);
|
||||||
route.get(
|
|
||||||
'/logs',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const fileList = fs.readdirSync(config.logPath, 'utf-8');
|
||||||
const fileList = fs.readdirSync(config.logPath, 'utf-8');
|
const dirs = [];
|
||||||
const dirs = [];
|
for (let i = 0; i < fileList.length; i++) {
|
||||||
for (let i = 0; i < fileList.length; i++) {
|
const stat = fs.lstatSync(config.logPath + fileList[i]);
|
||||||
const stat = fs.lstatSync(config.logPath + fileList[i]);
|
if (stat.isDirectory()) {
|
||||||
if (stat.isDirectory()) {
|
const fileListTmp = fs.readdirSync(
|
||||||
const fileListTmp = fs.readdirSync(
|
`${config.logPath}/${fileList[i]}`,
|
||||||
`${config.logPath}/${fileList[i]}`,
|
'utf-8',
|
||||||
'utf-8',
|
);
|
||||||
);
|
dirs.push({
|
||||||
dirs.push({
|
name: fileList[i],
|
||||||
name: fileList[i],
|
isDir: true,
|
||||||
isDir: true,
|
files: fileListTmp.reverse(),
|
||||||
files: fileListTmp.reverse(),
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
dirs.push({
|
||||||
dirs.push({
|
name: fileList[i],
|
||||||
name: fileList[i],
|
isDir: false,
|
||||||
isDir: false,
|
files: [],
|
||||||
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(
|
route.get(
|
||||||
'/logs/:dir/:file',
|
'/:dir/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -61,7 +59,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/logs/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
|
|||||||
+4
-4
@@ -49,7 +49,7 @@ export default (app: Router) => {
|
|||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().optional().allow(''),
|
name: Joi.string().optional().allow(''),
|
||||||
scopes: Joi.array().items(Joi.string()),
|
scopes: Joi.array().items(Joi.string()),
|
||||||
_id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -68,7 +68,7 @@ export default (app: Router) => {
|
|||||||
route.delete(
|
route.delete(
|
||||||
'/apps',
|
'/apps',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.number().required()),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
@@ -87,10 +87,10 @@ export default (app: Router) => {
|
|||||||
'/apps/:id/reset-secret',
|
'/apps/:id/reset-secret',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const openService = Container.get(OpenService);
|
const openService = Container.get(OpenService);
|
||||||
|
|||||||
+46
-18
@@ -9,15 +9,15 @@ import { Logger } from 'winston';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import path from 'path';
|
import path, { join } from 'path';
|
||||||
import ScriptService from '../services/script';
|
import ScriptService from '../services/script';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/scripts', route);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/scripts/files',
|
'/files',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -44,7 +44,7 @@ export default (app: Router) => {
|
|||||||
children.push({
|
children.push({
|
||||||
title: childFile,
|
title: childFile,
|
||||||
value: childFile,
|
value: childFile,
|
||||||
key: `${fileOrDir}-${childFile}`,
|
key: `${fileOrDir}/${childFile}`,
|
||||||
mtime: statObj.mtimeMs,
|
mtime: statObj.mtimeMs,
|
||||||
parent: fileOrDir,
|
parent: fileOrDir,
|
||||||
});
|
});
|
||||||
@@ -80,14 +80,16 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/scripts/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const path = req.query.path ? `${req.query.path}/` : '';
|
const filePath = join(
|
||||||
const content = getFileContentByName(
|
config.scriptPath,
|
||||||
`${config.scriptPath}${path}${req.params.file}`,
|
req.query.path as string,
|
||||||
|
req.params.file,
|
||||||
);
|
);
|
||||||
|
const content = getFileContentByName(filePath);
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
logger.error('🔥 error: %o', e);
|
||||||
@@ -97,11 +99,11 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/scripts',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
path: Joi.string().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
content: Joi.string().allow(''),
|
content: Joi.string().allow(''),
|
||||||
originFilename: Joi.string().allow(''),
|
originFilename: Joi.string().allow(''),
|
||||||
}),
|
}),
|
||||||
@@ -157,11 +159,11 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/scripts',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
path: Joi.string().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
content: Joi.string().required(),
|
content: Joi.string().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -173,7 +175,7 @@ export default (app: Router) => {
|
|||||||
content: string;
|
content: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
const filePath = `${config.scriptPath}${path}/${filename}`;
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
fs.writeFileSync(filePath, content);
|
fs.writeFileSync(filePath, content);
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -184,7 +186,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/scripts',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
@@ -198,7 +200,7 @@ export default (app: Router) => {
|
|||||||
filename: string;
|
filename: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
const filePath = `${config.scriptPath}${path}/${filename}`;
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -209,7 +211,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/scripts/download',
|
'/download',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
@@ -240,7 +242,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/scripts/run',
|
'/run',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
@@ -254,7 +256,7 @@ export default (app: Router) => {
|
|||||||
filename: string;
|
filename: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
const filePath = `${path}/${filename}`;
|
const filePath = join(path, filename);
|
||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const result = await scriptService.runScript(filePath);
|
const result = await scriptService.runScript(filePath);
|
||||||
res.send(result);
|
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();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/user', route);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/login',
|
'/login',
|
||||||
celebrate({
|
celebrate({
|
||||||
@@ -47,7 +48,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
username: Joi.string().required(),
|
username: Joi.string().required(),
|
||||||
@@ -67,29 +68,26 @@ export default (app: Router) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
'/user',
|
const logger: Logger = Container.get('logger');
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
try {
|
||||||
const logger: Logger = Container.get('logger');
|
const userService = Container.get(UserService);
|
||||||
try {
|
const authInfo = await userService.getUserInfo();
|
||||||
const userService = Container.get(UserService);
|
res.send({
|
||||||
const authInfo = await userService.getUserInfo();
|
code: 200,
|
||||||
res.send({
|
data: {
|
||||||
code: 200,
|
username: authInfo.username,
|
||||||
data: {
|
twoFactorActivated: authInfo.twoFactorActivated,
|
||||||
username: authInfo.username,
|
},
|
||||||
twoFactorActivated: authInfo.twoFactorActivated,
|
});
|
||||||
},
|
} catch (e) {
|
||||||
});
|
logger.error('🔥 error: %o', e);
|
||||||
} catch (e) {
|
return next(e);
|
||||||
logger.error('🔥 error: %o', e);
|
}
|
||||||
return next(e);
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/user/two-factor/init',
|
'/two-factor/init',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -104,7 +102,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/two-factor/active',
|
'/two-factor/active',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
code: Joi.string().required(),
|
code: Joi.string().required(),
|
||||||
@@ -124,7 +122,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/two-factor/deactive',
|
'/two-factor/deactive',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -139,7 +137,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/two-factor/login',
|
'/two-factor/login',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
code: Joi.string().required(),
|
code: Joi.string().required(),
|
||||||
@@ -161,7 +159,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/user/login-log',
|
'/login-log',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -176,7 +174,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/user/notification',
|
'/notification',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -191,7 +189,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/notification',
|
'/notification',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
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(
|
route.put(
|
||||||
'/init/user',
|
'/init',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
username: Joi.string().required(),
|
username: Joi.string().required(),
|
||||||
@@ -259,7 +225,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/init/notification',
|
'/notification/init',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
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';
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
const lastVersionFile =
|
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 envFound = dotenv.config();
|
||||||
const rootPath = process.cwd();
|
const rootPath = process.cwd();
|
||||||
@@ -77,12 +77,12 @@ export default {
|
|||||||
writePathList: [configPath, scriptPath],
|
writePathList: [configPath, scriptPath],
|
||||||
bakPath,
|
bakPath,
|
||||||
apiWhiteList: [
|
apiWhiteList: [
|
||||||
'/api/login',
|
'/api/user/login',
|
||||||
'/open/auth/token',
|
'/open/auth/token',
|
||||||
'/api/user/two-factor/login',
|
'/api/user/two-factor/login',
|
||||||
'/api/system',
|
'/api/system',
|
||||||
'/api/init/user',
|
'/api/user/init',
|
||||||
'/api/init/notification',
|
'/api/user/notification/init',
|
||||||
],
|
],
|
||||||
versionFile,
|
versionFile,
|
||||||
lastVersionFile,
|
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;
|
||||||
|
}
|
||||||
|
|||||||
+15
-2
@@ -1,14 +1,17 @@
|
|||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
||||||
|
|
||||||
export class AuthInfo {
|
export class AuthInfo {
|
||||||
ip?: string;
|
ip?: string;
|
||||||
type: AuthDataType;
|
type: AuthDataType;
|
||||||
info?: any;
|
info?: any;
|
||||||
_id?: string;
|
id?: number;
|
||||||
|
|
||||||
constructor(options: AuthInfo) {
|
constructor(options: AuthInfo) {
|
||||||
this.ip = options.ip;
|
this.ip = options.ip;
|
||||||
this.info = options.info;
|
this.info = options.info;
|
||||||
this.type = options.type;
|
this.type = options.type;
|
||||||
this._id = options._id;
|
this.id = options.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,3 +26,13 @@ export enum AuthDataType {
|
|||||||
'notification' = 'notification',
|
'notification' = 'notification',
|
||||||
'removeLogFrequency' = 'removeLogFrequency',
|
'removeLogFrequency' = 'removeLogFrequency',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AuthInstance extends Model<AuthInfo, AuthInfo>, AuthInfo {}
|
||||||
|
export const AuthModel = sequelize.define<AuthInstance>('Auth', {
|
||||||
|
ip: DataTypes.STRING,
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
info: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
+36
-7
@@ -1,34 +1,42 @@
|
|||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
||||||
|
|
||||||
export class Crontab {
|
export class Crontab {
|
||||||
name?: string;
|
name?: string;
|
||||||
command: string;
|
command: string;
|
||||||
schedule: string;
|
schedule: string;
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
created?: number;
|
|
||||||
saved?: boolean;
|
saved?: boolean;
|
||||||
_id?: string;
|
id?: number;
|
||||||
status?: CrontabStatus;
|
status?: CrontabStatus;
|
||||||
isSystem?: 1 | 0;
|
isSystem?: 1 | 0;
|
||||||
pid?: number;
|
pid?: number;
|
||||||
isDisabled?: 1 | 0;
|
isDisabled?: 1 | 0;
|
||||||
log_path?: string;
|
log_path?: string;
|
||||||
isPinned?: 1 | 0;
|
isPinned?: 1 | 0;
|
||||||
|
labels?: string[];
|
||||||
|
last_running_time?: number;
|
||||||
|
last_execution_time?: number;
|
||||||
|
|
||||||
constructor(options: Crontab) {
|
constructor(options: Crontab) {
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
this.command = options.command;
|
this.command = options.command;
|
||||||
this.schedule = options.schedule;
|
this.schedule = options.schedule;
|
||||||
this.saved = options.saved;
|
this.saved = options.saved;
|
||||||
this._id = options._id;
|
this.id = options.id;
|
||||||
this.created = options.created;
|
this.status =
|
||||||
this.status = CrontabStatus[options.status]
|
options.status && CrontabStatus[options.status]
|
||||||
? options.status
|
? options.status
|
||||||
: CrontabStatus.idle;
|
: CrontabStatus.idle;
|
||||||
this.timestamp = new Date().toString();
|
this.timestamp = new Date().toString();
|
||||||
this.isSystem = options.isSystem || 0;
|
this.isSystem = options.isSystem || 0;
|
||||||
this.pid = options.pid;
|
this.pid = options.pid;
|
||||||
this.isDisabled = options.isDisabled || 0;
|
this.isDisabled = options.isDisabled || 0;
|
||||||
this.log_path = options.log_path || '';
|
this.log_path = options.log_path || '';
|
||||||
this.isPinned = options.isPinned || 0;
|
this.isPinned = options.isPinned || 0;
|
||||||
|
this.labels = options.labels || [''];
|
||||||
|
this.last_running_time = options.last_running_time || 0;
|
||||||
|
this.last_execution_time = options.last_execution_time || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,3 +46,24 @@ export enum CrontabStatus {
|
|||||||
'disabled',
|
'disabled',
|
||||||
'queued',
|
'queued',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
|
||||||
|
export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
|
||||||
|
name: DataTypes.STRING,
|
||||||
|
command: {
|
||||||
|
unique: 'command',
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
schedule: DataTypes.STRING,
|
||||||
|
timestamp: DataTypes.STRING,
|
||||||
|
saved: DataTypes.BOOLEAN,
|
||||||
|
status: DataTypes.NUMBER,
|
||||||
|
isSystem: DataTypes.NUMBER,
|
||||||
|
pid: DataTypes.NUMBER,
|
||||||
|
isDisabled: DataTypes.NUMBER,
|
||||||
|
isPinned: DataTypes.NUMBER,
|
||||||
|
log_path: DataTypes.STRING,
|
||||||
|
labels: DataTypes.JSON,
|
||||||
|
last_running_time: DataTypes.NUMBER,
|
||||||
|
last_execution_time: DataTypes.NUMBER,
|
||||||
|
});
|
||||||
|
|||||||
+20
-4
@@ -1,7 +1,9 @@
|
|||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
||||||
|
|
||||||
export class Dependence {
|
export class Dependence {
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
created?: number;
|
id?: number;
|
||||||
_id?: string;
|
|
||||||
status?: DependenceStatus;
|
status?: DependenceStatus;
|
||||||
type?: DependenceTypes;
|
type?: DependenceTypes;
|
||||||
name?: number;
|
name?: number;
|
||||||
@@ -9,8 +11,7 @@ export class Dependence {
|
|||||||
remark?: string;
|
remark?: string;
|
||||||
|
|
||||||
constructor(options: Dependence) {
|
constructor(options: Dependence) {
|
||||||
this._id = options._id;
|
this.id = options.id;
|
||||||
this.created = options.created || new Date().valueOf();
|
|
||||||
this.status = options.status || DependenceStatus.installing;
|
this.status = options.status || DependenceStatus.installing;
|
||||||
this.type = options.type || DependenceTypes.nodejs;
|
this.type = options.type || DependenceTypes.nodejs;
|
||||||
this.timestamp = new Date().toString();
|
this.timestamp = new Date().toString();
|
||||||
@@ -46,3 +47,18 @@ export enum unInstallDependenceCommandTypes {
|
|||||||
'pip3 uninstall -y',
|
'pip3 uninstall -y',
|
||||||
'apk del -f',
|
'apk del -f',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DependenceInstance
|
||||||
|
extends Model<Dependence, Dependence>,
|
||||||
|
Dependence {}
|
||||||
|
export const DependenceModel = sequelize.define<DependenceInstance>(
|
||||||
|
'Dependence',
|
||||||
|
{
|
||||||
|
name: DataTypes.STRING,
|
||||||
|
type: DataTypes.NUMBER,
|
||||||
|
timestamp: DataTypes.STRING,
|
||||||
|
status: DataTypes.NUMBER,
|
||||||
|
log: DataTypes.JSON,
|
||||||
|
remark: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
+17
-6
@@ -1,17 +1,18 @@
|
|||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
||||||
|
|
||||||
export class Env {
|
export class Env {
|
||||||
value?: string;
|
value?: string;
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
created?: number;
|
id?: number;
|
||||||
_id?: string;
|
|
||||||
status?: EnvStatus;
|
status?: EnvStatus;
|
||||||
position?: number;
|
position?: number;
|
||||||
name?: number;
|
name?: string;
|
||||||
remarks?: number;
|
remarks?: string;
|
||||||
|
|
||||||
constructor(options: Env) {
|
constructor(options: Env) {
|
||||||
this.value = options.value;
|
this.value = options.value;
|
||||||
this._id = options._id;
|
this.id = options.id;
|
||||||
this.created = options.created || new Date().valueOf();
|
|
||||||
this.status = options.status || EnvStatus.normal;
|
this.status = options.status || EnvStatus.normal;
|
||||||
this.timestamp = new Date().toString();
|
this.timestamp = new Date().toString();
|
||||||
this.position = options.position;
|
this.position = options.position;
|
||||||
@@ -26,3 +27,13 @@ export enum EnvStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const initEnvPosition = 9999999999;
|
export const initEnvPosition = 9999999999;
|
||||||
|
|
||||||
|
interface EnvInstance extends Model<Env, Env>, Env {}
|
||||||
|
export const EnvModel = sequelize.define<EnvInstance>('Env', {
|
||||||
|
value: DataTypes.STRING,
|
||||||
|
timestamp: DataTypes.STRING,
|
||||||
|
status: DataTypes.NUMBER,
|
||||||
|
position: DataTypes.NUMBER,
|
||||||
|
name: DataTypes.STRING,
|
||||||
|
remarks: DataTypes.STRING,
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Sequelize } from 'sequelize';
|
||||||
|
import config from '../config/index';
|
||||||
|
|
||||||
|
export const sequelize = new Sequelize({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: `${config.dbPath}database.sqlite`,
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
+15
-3
@@ -1,23 +1,26 @@
|
|||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
name: string;
|
name: string;
|
||||||
scopes: AppScope[];
|
scopes: AppScope[];
|
||||||
client_id: string;
|
client_id: string;
|
||||||
client_secret: string;
|
client_secret: string;
|
||||||
tokens?: AppToken[];
|
tokens?: AppToken[];
|
||||||
_id?: string;
|
id?: number;
|
||||||
|
|
||||||
constructor(options: App) {
|
constructor(options: App) {
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
this.scopes = options.scopes;
|
this.scopes = options.scopes;
|
||||||
this.client_id = options.client_id;
|
this.client_id = options.client_id;
|
||||||
this.client_secret = options.client_secret;
|
this.client_secret = options.client_secret;
|
||||||
this._id = options._id;
|
this.id = options.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppToken {
|
export interface AppToken {
|
||||||
value: string;
|
value: string;
|
||||||
type: 'Bearer';
|
type?: 'Bearer';
|
||||||
expiration: number;
|
expiration: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,3 +32,12 @@ export enum CrontabStatus {
|
|||||||
'disabled',
|
'disabled',
|
||||||
'queued',
|
'queued',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AppInstance extends Model<App, App>, App {}
|
||||||
|
export const AppModel = sequelize.define<AppInstance>('App', {
|
||||||
|
name: DataTypes.STRING,
|
||||||
|
scopes: DataTypes.JSON,
|
||||||
|
client_id: DataTypes.STRING,
|
||||||
|
client_secret: DataTypes.STRING,
|
||||||
|
tokens: DataTypes.JSON,
|
||||||
|
});
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
export class SockMessage {
|
export class SockMessage {
|
||||||
message?: string;
|
message?: string;
|
||||||
type?: SockMessageType;
|
type?: SockMessageType;
|
||||||
references?: string[];
|
references?: number[];
|
||||||
|
|
||||||
constructor(options: SockMessage) {
|
constructor(options: SockMessage) {
|
||||||
this.type = options.type;
|
this.type = options.type;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import dependencyInjectorLoader from './dependencyInjector';
|
|||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import initData from './initData';
|
import initData from './initData';
|
||||||
import { Application } from 'express';
|
import { Application } from 'express';
|
||||||
|
import linkDeps from './deps';
|
||||||
|
|
||||||
export default async ({ expressApp }: { expressApp: Application }) => {
|
export default async ({ expressApp }: { expressApp: Application }) => {
|
||||||
await dependencyInjectorLoader({
|
await dependencyInjectorLoader({
|
||||||
@@ -15,4 +16,7 @@ export default async ({ expressApp }: { expressApp: Application }) => {
|
|||||||
|
|
||||||
await initData();
|
await initData();
|
||||||
Logger.info('✌️ init data loaded');
|
Logger.info('✌️ init data loaded');
|
||||||
|
|
||||||
|
await linkDeps();
|
||||||
|
Logger.info('✌️ link deps');
|
||||||
};
|
};
|
||||||
|
|||||||
+71
-47
@@ -1,57 +1,83 @@
|
|||||||
import DataStore from 'nedb';
|
import DataStore from 'nedb';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import fs from 'fs';
|
|
||||||
import { fileExist } from '../config/util';
|
import { fileExist } from '../config/util';
|
||||||
|
import { EnvModel } from '../data/env';
|
||||||
|
import { CrontabModel } from '../data/cron';
|
||||||
|
import { DependenceModel } from '../data/dependence';
|
||||||
|
import { AppModel } from '../data/open';
|
||||||
|
import { AuthModel } from '../data/auth';
|
||||||
|
import { sequelize } from '../data';
|
||||||
|
|
||||||
interface Dbs {
|
|
||||||
cronDb: DataStore;
|
|
||||||
dependenceDb: DataStore;
|
|
||||||
envDb: DataStore;
|
|
||||||
appDb: DataStore;
|
|
||||||
authDb: DataStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
const db: Dbs = {} as any;
|
|
||||||
|
|
||||||
async function truncateDb() {
|
|
||||||
return new Promise(async (resolve) => {
|
|
||||||
const files = [
|
|
||||||
config.cronDbFile,
|
|
||||||
config.dependenceDbFile,
|
|
||||||
config.envDbFile,
|
|
||||||
config.appDbFile,
|
|
||||||
config.authDbFile,
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const _fileExist = await fileExist(file);
|
|
||||||
if (_fileExist && fs.statSync(file).size >= 1024 * 1024 * 500) {
|
|
||||||
fs.truncateSync(file, 1024 * 1024 * 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
try {
|
try {
|
||||||
await truncateDb();
|
await sequelize.sync({ alter: true });
|
||||||
|
|
||||||
db.cronDb = new DataStore({ filename: config.cronDbFile, autoload: true });
|
const crondbExist = await fileExist(config.cronDbFile);
|
||||||
db.dependenceDb = new DataStore({
|
const dependenceDbExist = await fileExist(config.dependenceDbFile);
|
||||||
filename: config.dependenceDbFile,
|
const envDbExist = await fileExist(config.envDbFile);
|
||||||
autoload: true,
|
const appDbExist = await fileExist(config.appDbFile);
|
||||||
});
|
const authDbExist = await fileExist(config.authDbFile);
|
||||||
db.envDb = new DataStore({ filename: config.envDbFile, autoload: true });
|
|
||||||
db.appDb = new DataStore({ filename: config.appDbFile, autoload: true });
|
|
||||||
db.authDb = new DataStore({ filename: config.authDbFile, autoload: true });
|
|
||||||
|
|
||||||
// compaction data file
|
const cronCount = await CrontabModel.count();
|
||||||
db.cronDb.persistence.compactDatafile();
|
const dependenceCount = await DependenceModel.count();
|
||||||
db.envDb.persistence.compactDatafile();
|
const envCount = await EnvModel.count();
|
||||||
db.dependenceDb.persistence.compactDatafile();
|
const appCount = await AppModel.count();
|
||||||
db.appDb.persistence.compactDatafile();
|
const authCount = await AuthModel.count();
|
||||||
db.authDb.persistence.compactDatafile();
|
if (crondbExist && cronCount === 0) {
|
||||||
|
const cronDb = new DataStore({
|
||||||
|
filename: config.cronDbFile,
|
||||||
|
autoload: true,
|
||||||
|
});
|
||||||
|
cronDb.persistence.compactDatafile();
|
||||||
|
cronDb.find({}).exec(async (err, docs) => {
|
||||||
|
await CrontabModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dependenceDbExist && dependenceCount === 0) {
|
||||||
|
const dependenceDb = new DataStore({
|
||||||
|
filename: config.dependenceDbFile,
|
||||||
|
autoload: true,
|
||||||
|
});
|
||||||
|
dependenceDb.persistence.compactDatafile();
|
||||||
|
dependenceDb.find({}).exec(async (err, docs) => {
|
||||||
|
await DependenceModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (envDbExist && envCount === 0) {
|
||||||
|
const envDb = new DataStore({
|
||||||
|
filename: config.envDbFile,
|
||||||
|
autoload: true,
|
||||||
|
});
|
||||||
|
envDb.persistence.compactDatafile();
|
||||||
|
envDb.find({}).exec(async (err, docs) => {
|
||||||
|
await EnvModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appDbExist && appCount === 0) {
|
||||||
|
const appDb = new DataStore({
|
||||||
|
filename: config.appDbFile,
|
||||||
|
autoload: true,
|
||||||
|
});
|
||||||
|
appDb.persistence.compactDatafile();
|
||||||
|
appDb.find({}).exec(async (err, docs) => {
|
||||||
|
await AppModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authDbExist && authCount === 0) {
|
||||||
|
const authDb = new DataStore({
|
||||||
|
filename: config.authDbFile,
|
||||||
|
autoload: true,
|
||||||
|
});
|
||||||
|
authDb.persistence.compactDatafile();
|
||||||
|
authDb.find({}).exec(async (err, docs) => {
|
||||||
|
await AuthModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Logger.info('✌️ DB loaded');
|
Logger.info('✌️ DB loaded');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -59,5 +85,3 @@ export default async () => {
|
|||||||
Logger.info(error);
|
Logger.info(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dbs: Dbs = db;
|
|
||||||
|
|||||||
@@ -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) => {
|
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();
|
return next();
|
||||||
}
|
}
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
|
|||||||
+23
-42
@@ -1,73 +1,54 @@
|
|||||||
import DependenceService from '../services/dependence';
|
import DependenceService from '../services/dependence';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Crontab, CrontabStatus } from '../data/cron';
|
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
|
||||||
import CronService from '../services/cron';
|
import CronService from '../services/cron';
|
||||||
import EnvService from '../services/env';
|
import EnvService from '../services/env';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { dbs } from '../loaders/db';
|
import { DependenceModel } from '../data/dependence';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const cronService = Container.get(CronService);
|
const cronService = Container.get(CronService);
|
||||||
const envService = Container.get(EnvService);
|
const envService = Container.get(EnvService);
|
||||||
const dependenceService = Container.get(DependenceService);
|
const dependenceService = Container.get(DependenceService);
|
||||||
const cronDb = dbs.cronDb;
|
|
||||||
const dependenceDb = dbs.dependenceDb;
|
|
||||||
|
|
||||||
// 初始化更新所有任务状态为空闲
|
// 初始化更新所有任务状态为空闲
|
||||||
cronDb.update(
|
await CrontabModel.update(
|
||||||
{ status: { $in: [CrontabStatus.running, CrontabStatus.queued] } },
|
{ status: CrontabStatus.idle },
|
||||||
{ $set: { status: CrontabStatus.idle } },
|
{ where: { status: [CrontabStatus.running, CrontabStatus.queued] } },
|
||||||
{ multi: true },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
||||||
dependenceDb.find({ status: { $in: [0, 1, 2] } }).exec((err, docs) => {
|
DependenceModel.findAll({ where: {} }).then(async (docs) => {
|
||||||
const groups = _.groupBy(docs, 'type');
|
const groups = _.groupBy(docs, 'type');
|
||||||
for (const key in groups) {
|
for (const key in groups) {
|
||||||
if (Object.prototype.hasOwnProperty.call(groups, key)) {
|
if (Object.prototype.hasOwnProperty.call(groups, key)) {
|
||||||
const group = groups[key];
|
const group = groups[key];
|
||||||
const depIds = group.map((x) => x._id);
|
const depIds = group.map((x) => x.id);
|
||||||
for (const dep of depIds) {
|
for (const dep of depIds) {
|
||||||
dependenceService.reInstall([dep]);
|
await dependenceService.reInstall([dep]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始化时执行一次所有的ql repo 任务
|
// 初始化时执行一次所有的ql repo 任务
|
||||||
cronDb
|
CrontabModel.findAll({
|
||||||
.find({
|
where: {
|
||||||
command: /ql (repo|raw)/,
|
isDisabled: { [Op.ne]: 1 },
|
||||||
isDisabled: { $ne: 1 },
|
command: {
|
||||||
})
|
[Op.or]: [{ [Op.like]: `%ql repo%` }, { [Op.like]: `%$ql raw%` }],
|
||||||
.exec((err, docs) => {
|
},
|
||||||
for (let i = 0; i < docs.length; i++) {
|
},
|
||||||
const doc = docs[i];
|
}).then((docs) => {
|
||||||
if (doc) {
|
for (let i = 0; i < docs.length; i++) {
|
||||||
exec(doc.command);
|
const doc = docs[i];
|
||||||
}
|
if (doc) {
|
||||||
|
exec(doc.command);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
// patch 禁用状态字段改变
|
|
||||||
cronDb
|
|
||||||
.find({
|
|
||||||
status: CrontabStatus.disabled,
|
|
||||||
})
|
|
||||||
.exec((err, docs) => {
|
|
||||||
if (docs.length > 0) {
|
|
||||||
const ids = docs.map((x) => x._id);
|
|
||||||
cronDb.update(
|
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { status: CrontabStatus.idle, isDisabled: 1 } },
|
|
||||||
{ multi: true },
|
|
||||||
(err) => {
|
|
||||||
cronService.autosave_crontab();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化保存一次ck和定时任务数据
|
// 初始化保存一次ck和定时任务数据
|
||||||
await cronService.autosave_crontab();
|
await cronService.autosave_crontab();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Logger from './logger';
|
|||||||
|
|
||||||
export default ({ expressApp }: { expressApp: Application }) => {
|
export default ({ expressApp }: { expressApp: Application }) => {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://e14681bce55f4849b11024a7d424b711@o1051273.ingest.sentry.io/6047906',
|
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
||||||
integrations: [
|
integrations: [
|
||||||
new Sentry.Integrations.Http({ tracing: true }),
|
new Sentry.Integrations.Http({ tracing: true }),
|
||||||
new Tracing.Integrations.Express({ app: expressApp }),
|
new Tracing.Integrations.Express({ app: expressApp }),
|
||||||
|
|||||||
+7
-13
@@ -2,24 +2,14 @@ import schedule from 'node-schedule';
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import Logger from './loaders/logger';
|
import Logger from './loaders/logger';
|
||||||
import { CrontabStatus } from './data/cron';
|
import { CrontabModel, CrontabStatus } from './data/cron';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import { dbs } from './loaders/db';
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
const cronDb = dbs.cronDb;
|
CrontabModel.findAll({ where: {} })
|
||||||
|
.then((docs) => {
|
||||||
cronDb
|
|
||||||
.find({})
|
|
||||||
.sort({ created: 1 })
|
|
||||||
.exec((err, docs) => {
|
|
||||||
if (err) {
|
|
||||||
Logger.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (docs && docs.length > 0) {
|
if (docs && docs.length > 0) {
|
||||||
for (let i = 0; i < docs.length; i++) {
|
for (let i = 0; i < docs.length; i++) {
|
||||||
const task = docs[i];
|
const task = docs[i];
|
||||||
@@ -40,6 +30,10 @@ const run = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Logger.error(err);
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+195
-228
@@ -1,25 +1,17 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import DataStore from 'nedb';
|
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { Crontab, CrontabStatus } from '../data/cron';
|
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
|
||||||
import { exec, execSync, spawn } from 'child_process';
|
import { exec, execSync, spawn } from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import cron_parser from 'cron-parser';
|
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 { promises, existsSync } from 'fs';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { dbs } from '../loaders/db';
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronService {
|
export default class CronService {
|
||||||
private cronDb = dbs.cronDb;
|
|
||||||
|
|
||||||
private queue = new PQueue({
|
|
||||||
concurrency: parseInt(process.env.MaxConcurrentNum as string) || 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
private isSixCron(cron: Crontab) {
|
private isSixCron(cron: Crontab) {
|
||||||
@@ -32,7 +24,6 @@ export default class CronService {
|
|||||||
|
|
||||||
public async create(payload: Crontab): Promise<Crontab> {
|
public async create(payload: Crontab): Promise<Crontab> {
|
||||||
const tab = new Crontab(payload);
|
const tab = new Crontab(payload);
|
||||||
tab.created = new Date().valueOf();
|
|
||||||
tab.saved = false;
|
tab.saved = false;
|
||||||
const doc = await this.insert(tab);
|
const doc = await this.insert(tab);
|
||||||
await this.set_crontab(this.isSixCron(doc));
|
await this.set_crontab(this.isSixCron(doc));
|
||||||
@@ -40,42 +31,25 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async insert(payload: Crontab): Promise<Crontab> {
|
public async insert(payload: Crontab): Promise<Crontab> {
|
||||||
return new Promise((resolve) => {
|
const cron = await CrontabModel.findOne({
|
||||||
this.cronDb.insert(payload, (err, docs) => {
|
where: { command: payload.command },
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(docs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
if (cron) {
|
||||||
|
return cron;
|
||||||
|
}
|
||||||
|
return await CrontabModel.create(payload, { returning: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update(payload: Crontab): Promise<Crontab> {
|
public async update(payload: Crontab): Promise<Crontab> {
|
||||||
const { _id, ...other } = payload;
|
payload.saved = false;
|
||||||
const doc = await this.get(_id);
|
const newDoc = await this.updateDb(payload);
|
||||||
const tab = new Crontab({ ...doc, ...other });
|
|
||||||
tab.saved = false;
|
|
||||||
const newDoc = await this.updateDb(tab);
|
|
||||||
await this.set_crontab(this.isSixCron(newDoc));
|
await this.set_crontab(this.isSixCron(newDoc));
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateDb(payload: Crontab): Promise<Crontab> {
|
public async updateDb(payload: Crontab): Promise<Crontab> {
|
||||||
return new Promise((resolve) => {
|
await CrontabModel.update(payload, { where: { id: payload.id } });
|
||||||
this.cronDb.update(
|
return await this.getDb({ id: payload.id });
|
||||||
{ _id: payload._id },
|
|
||||||
payload,
|
|
||||||
{ returnUpdatedDocs: true },
|
|
||||||
(err, num, docs: any) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(docs);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async status({
|
public async status({
|
||||||
@@ -86,156 +60,164 @@ export default class CronService {
|
|||||||
last_running_time = 0,
|
last_running_time = 0,
|
||||||
last_execution_time = 0,
|
last_execution_time = 0,
|
||||||
}: {
|
}: {
|
||||||
ids: string[];
|
ids: number[];
|
||||||
status: CrontabStatus;
|
status: CrontabStatus;
|
||||||
pid: number;
|
pid: number;
|
||||||
log_path: string;
|
log_path: string;
|
||||||
last_running_time: number;
|
last_running_time: number;
|
||||||
last_execution_time: number;
|
last_execution_time: number;
|
||||||
}) {
|
}) {
|
||||||
return new Promise((resolve) => {
|
const options: any = {
|
||||||
this.cronDb.update(
|
status,
|
||||||
{ _id: { $in: ids } },
|
pid,
|
||||||
|
log_path,
|
||||||
|
last_execution_time,
|
||||||
|
};
|
||||||
|
if (last_running_time > 0) {
|
||||||
|
options.last_running_time = last_running_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await CrontabModel.update({ ...options }, { where: { id: ids } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async remove(ids: number[]) {
|
||||||
|
await CrontabModel.destroy({ where: { id: ids } });
|
||||||
|
await this.set_crontab(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pin(ids: number[]) {
|
||||||
|
await CrontabModel.update({ isPinned: 1 }, { where: { id: ids } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async unPin(ids: number[]) {
|
||||||
|
await CrontabModel.update({ isPinned: 0 }, { where: { id: ids } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addLabels(ids: string[], labels: string[]) {
|
||||||
|
const docs = await CrontabModel.findAll({ where: { id: ids } });
|
||||||
|
for (const doc of docs) {
|
||||||
|
await CrontabModel.update(
|
||||||
{
|
{
|
||||||
$set: {
|
labels: Array.from(new Set((doc.labels || []).concat(labels))),
|
||||||
status,
|
|
||||||
pid,
|
|
||||||
log_path,
|
|
||||||
last_running_time,
|
|
||||||
last_execution_time,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ multi: true, returnUpdatedDocs: true },
|
|
||||||
(err) => {
|
|
||||||
resolve(null);
|
|
||||||
},
|
},
|
||||||
|
{ where: { id: doc.id } },
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async remove(ids: string[]) {
|
public async removeLabels(ids: string[], labels: string[]) {
|
||||||
return new Promise((resolve: any) => {
|
const docs = await CrontabModel.findAll({ where: { id: ids } });
|
||||||
this.cronDb.remove(
|
for (const doc of docs) {
|
||||||
{ _id: { $in: ids } },
|
await CrontabModel.update(
|
||||||
{ multi: true },
|
{
|
||||||
async (err) => {
|
labels: (doc.labels || []).filter((label) => !labels.includes(label)),
|
||||||
await this.set_crontab(true);
|
|
||||||
resolve();
|
|
||||||
},
|
},
|
||||||
|
{ where: { id: doc.id } },
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async pin(ids: string[]) {
|
|
||||||
return new Promise((resolve: any) => {
|
|
||||||
this.cronDb.update(
|
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { isPinned: 1 } },
|
|
||||||
{ multi: true },
|
|
||||||
async (err) => {
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async unPin(ids: string[]) {
|
|
||||||
return new Promise((resolve: any) => {
|
|
||||||
this.cronDb.update(
|
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { isPinned: 0 } },
|
|
||||||
{ multi: true },
|
|
||||||
async (err) => {
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
||||||
let query = {};
|
let query = {};
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const reg = new RegExp(searchText, 'i');
|
const textArray = searchText.split(':');
|
||||||
query = {
|
switch (textArray[0]) {
|
||||||
$or: [
|
case 'name':
|
||||||
{
|
case 'command':
|
||||||
name: reg,
|
case 'schedule':
|
||||||
},
|
case 'label':
|
||||||
{
|
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
|
||||||
command: reg,
|
query = {
|
||||||
},
|
[column]: {
|
||||||
{
|
[Op.or]: [
|
||||||
schedule: reg,
|
{ [Op.like]: `%${textArray[1]}%` },
|
||||||
},
|
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
||||||
],
|
],
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
const reg = {
|
||||||
|
[Op.or]: [
|
||||||
|
{ [Op.like]: `%${searchText}%` },
|
||||||
|
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
query = {
|
||||||
|
[Op.or]: [
|
||||||
|
{
|
||||||
|
name: reg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: reg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schedule: reg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labels: reg,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new Promise((resolve) => {
|
try {
|
||||||
this.cronDb
|
const result = await CrontabModel.findAll({
|
||||||
.find(query)
|
where: query,
|
||||||
.sort({ created: -1 })
|
order: [['updatedAt', 'DESC']],
|
||||||
.exec((err, docs) => {
|
|
||||||
resolve(docs);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async get(_id: string): Promise<Crontab> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.cronDb.find({ _id }).exec((err, docs) => {
|
|
||||||
resolve(docs[0]);
|
|
||||||
});
|
});
|
||||||
});
|
return result as any;
|
||||||
}
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
public async run(ids: string[]) {
|
|
||||||
this.cronDb.update(
|
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { status: CrontabStatus.queued } },
|
|
||||||
{ multi: true },
|
|
||||||
);
|
|
||||||
for (let i = 0; i < ids.length; i++) {
|
|
||||||
const id = ids[i];
|
|
||||||
this.queue.add(() => this.runSingle(id));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(ids: string[]) {
|
public async getDb(query: any): Promise<Crontab> {
|
||||||
return new Promise((resolve: any) => {
|
const doc: any = await CrontabModel.findOne({ where: { ...query } });
|
||||||
this.cronDb
|
return doc && (doc.get({ plain: true }) as Crontab);
|
||||||
.find({ _id: { $in: ids } })
|
|
||||||
.exec(async (err, docs: Crontab[]) => {
|
|
||||||
for (const doc of docs) {
|
|
||||||
if (doc.pid) {
|
|
||||||
try {
|
|
||||||
process.kill(-doc.pid);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.silly(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const err = await this.killTask(doc.command);
|
|
||||||
if (doc.log_path) {
|
|
||||||
const str = err ? `\n${err}` : '';
|
|
||||||
fs.appendFileSync(
|
|
||||||
`${doc.log_path}`,
|
|
||||||
`${str}\n## 执行结束... ${new Date()
|
|
||||||
.toLocaleString('zh', { hour12: false })
|
|
||||||
.replace(' 24:', ' 00:')} `,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.cronDb.update(
|
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { status: CrontabStatus.idle }, $unset: { pid: true } },
|
|
||||||
{ multi: true },
|
|
||||||
);
|
|
||||||
this.queue.clear();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async killTask(name: string) {
|
public async run(ids: number[]) {
|
||||||
|
await CrontabModel.update(
|
||||||
|
{ status: CrontabStatus.queued },
|
||||||
|
{ where: { id: ids } },
|
||||||
|
);
|
||||||
|
concurrentRun(
|
||||||
|
ids.map((id) => async () => await this.runSingle(id)),
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(ids: number[]) {
|
||||||
|
const docs = await CrontabModel.findAll({ where: { id: ids } });
|
||||||
|
for (const doc of docs) {
|
||||||
|
if (doc.pid) {
|
||||||
|
try {
|
||||||
|
process.kill(-doc.pid);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.silly(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const err = await this.killTask(doc.command);
|
||||||
|
if (doc.log_path) {
|
||||||
|
const str = err ? `\n${err}` : '';
|
||||||
|
fs.appendFileSync(
|
||||||
|
`${doc.log_path}`,
|
||||||
|
`${str}\n## 执行结束... ${new Date()
|
||||||
|
.toLocaleString('zh', { hour12: false })
|
||||||
|
.replace(' 24:', ' 00:')} `,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await CrontabModel.update(
|
||||||
|
{ status: CrontabStatus.idle, pid: undefined },
|
||||||
|
{ where: { id: ids } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async killTask(name: string) {
|
||||||
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
|
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
try {
|
try {
|
||||||
@@ -245,16 +227,20 @@ export default class CronService {
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pids = pid.match(/\(\d+/g);
|
let pids = pid.match(/\(\d+/g);
|
||||||
const killLogs = [];
|
const killLogs = [];
|
||||||
for (const id of pids) {
|
if (pids && pids.length > 0) {
|
||||||
const c = `kill -9 ${id.slice(1)}`;
|
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
|
||||||
const { stdout, stderr } = await execAsync(c);
|
pids = pids.slice(0, 3);
|
||||||
if (stderr) {
|
for (const id of pids) {
|
||||||
killLogs.push(stderr);
|
const c = `kill -9 ${id.slice(1)}`;
|
||||||
}
|
const { stdout, stderr } = await execAsync(c);
|
||||||
if (stdout) {
|
if (stderr) {
|
||||||
killLogs.push(stdout);
|
killLogs.push(stderr);
|
||||||
|
}
|
||||||
|
if (stdout) {
|
||||||
|
killLogs.push(stdout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
|
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
|
||||||
@@ -263,18 +249,18 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runSingle(id: string): Promise<number> {
|
private async runSingle(cronId: number): Promise<number> {
|
||||||
return new Promise(async (resolve: any) => {
|
return new Promise(async (resolve: any) => {
|
||||||
const cron = await this.get(id);
|
const cron = await this.getDb({ id: cronId });
|
||||||
if (cron.status !== CrontabStatus.queued) {
|
if (cron.status !== CrontabStatus.queued) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { _id, command, log_path } = cron;
|
let { id, command, log_path } = cron;
|
||||||
|
|
||||||
this.logger.silly('Running job');
|
this.logger.silly('Running job');
|
||||||
this.logger.silly('ID: ' + _id);
|
this.logger.silly('ID: ' + id);
|
||||||
this.logger.silly('Original command: ' + command);
|
this.logger.silly('Original command: ' + command);
|
||||||
|
|
||||||
let cmdStr = command;
|
let cmdStr = command;
|
||||||
@@ -286,9 +272,10 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cp = spawn(cmdStr, { shell: '/bin/bash' });
|
const cp = spawn(cmdStr, { shell: '/bin/bash' });
|
||||||
this.cronDb.update(
|
|
||||||
{ _id },
|
await CrontabModel.update(
|
||||||
{ $set: { status: CrontabStatus.running, pid: cp.pid } },
|
{ status: CrontabStatus.running, pid: cp.pid },
|
||||||
|
{ where: { id } },
|
||||||
);
|
);
|
||||||
cp.stderr.on('data', (data) => {
|
cp.stderr.on('data', (data) => {
|
||||||
if (log_path) {
|
if (log_path) {
|
||||||
@@ -301,57 +288,39 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('exit', (code, signal) => {
|
cp.on('exit', async (code, signal) => {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
|
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
|
||||||
);
|
);
|
||||||
this.cronDb.update(
|
await CrontabModel.update(
|
||||||
{ _id },
|
{ status: CrontabStatus.idle, pid: undefined },
|
||||||
{ $set: { status: CrontabStatus.idle }, $unset: { pid: true } },
|
{ where: { id } },
|
||||||
);
|
);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
cp.on('close', (code) => {
|
cp.on('close', async (code) => {
|
||||||
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
|
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
|
||||||
this.cronDb.update(
|
await CrontabModel.update(
|
||||||
{ _id },
|
{ status: CrontabStatus.idle, pid: undefined },
|
||||||
{ $set: { status: CrontabStatus.idle }, $unset: { pid: true } },
|
{ where: { id } },
|
||||||
);
|
);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disabled(ids: string[]) {
|
public async disabled(ids: number[]) {
|
||||||
return new Promise((resolve: any) => {
|
await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
||||||
this.cronDb.update(
|
await this.set_crontab(true);
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { isDisabled: 1 } },
|
|
||||||
{ multi: true },
|
|
||||||
async (err) => {
|
|
||||||
await this.set_crontab(true);
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async enabled(ids: string[]) {
|
public async enabled(ids: number[]) {
|
||||||
return new Promise((resolve: any) => {
|
await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
||||||
this.cronDb.update(
|
await this.set_crontab(true);
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { isDisabled: 0 } },
|
|
||||||
{ multi: true },
|
|
||||||
async (err) => {
|
|
||||||
await this.set_crontab(true);
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async log(_id: string) {
|
public async log(id: number) {
|
||||||
const doc = await this.get(_id);
|
const doc = await this.getDb({ id });
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -391,7 +360,7 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private make_command(tab: Crontab) {
|
private make_command(tab: Crontab) {
|
||||||
const crontab_job_string = `ID=${tab._id} ${tab.command}`;
|
const crontab_job_string = `ID=${tab.id} ${tab.command}`;
|
||||||
return crontab_job_string;
|
return crontab_job_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +390,7 @@ export default class CronService {
|
|||||||
if (needReloadSchedule) {
|
if (needReloadSchedule) {
|
||||||
exec(`pm2 reload schedule`);
|
exec(`pm2 reload schedule`);
|
||||||
}
|
}
|
||||||
this.cronDb.update({}, { $set: { saved: true } }, { multi: true });
|
await CrontabModel.update({ saved: true }, { where: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
public import_crontab() {
|
public import_crontab() {
|
||||||
@@ -429,7 +398,7 @@ export default class CronService {
|
|||||||
var lines = stdout.split('\n');
|
var lines = stdout.split('\n');
|
||||||
var namePrefix = new Date().getTime();
|
var namePrefix = new Date().getTime();
|
||||||
|
|
||||||
lines.reverse().forEach((line, index) => {
|
lines.reverse().forEach(async (line, index) => {
|
||||||
line = line.replace(/\t+/g, ' ');
|
line = line.replace(/\t+/g, ' ');
|
||||||
var regex =
|
var regex =
|
||||||
/^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/;
|
/^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/;
|
||||||
@@ -443,18 +412,16 @@ export default class CronService {
|
|||||||
) {
|
) {
|
||||||
var name = namePrefix + '_' + index;
|
var name = namePrefix + '_' + index;
|
||||||
|
|
||||||
this.cronDb.findOne({ command, schedule }, (err, doc) => {
|
const _crontab = await CrontabModel.findOne({
|
||||||
if (err) {
|
where: { command, schedule },
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
if (!doc) {
|
|
||||||
this.create({ name, command, schedule });
|
|
||||||
} else {
|
|
||||||
doc.command = command;
|
|
||||||
doc.schedule = schedule;
|
|
||||||
this.update(doc);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
if (!_crontab) {
|
||||||
|
await this.create({ name, command, schedule });
|
||||||
|
} else {
|
||||||
|
_crontab.command = command;
|
||||||
|
_crontab.schedule = schedule;
|
||||||
|
await this.update(_crontab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+142
-192
@@ -1,23 +1,21 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import DataStore from 'nedb';
|
|
||||||
import {
|
import {
|
||||||
Dependence,
|
Dependence,
|
||||||
InstallDependenceCommandTypes,
|
InstallDependenceCommandTypes,
|
||||||
DependenceStatus,
|
DependenceStatus,
|
||||||
DependenceTypes,
|
DependenceTypes,
|
||||||
unInstallDependenceCommandTypes,
|
unInstallDependenceCommandTypes,
|
||||||
|
DependenceModel,
|
||||||
} from '../data/dependence';
|
} from '../data/dependence';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import { dbs } from '../loaders/db';
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class DependenceService {
|
export default class DependenceService {
|
||||||
private dependenceDb = dbs.dependenceDb;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
@@ -34,22 +32,15 @@ export default class DependenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async insert(payloads: Dependence[]): Promise<Dependence[]> {
|
public async insert(payloads: Dependence[]): Promise<Dependence[]> {
|
||||||
return new Promise((resolve) => {
|
const docs = await DependenceModel.bulkCreate(payloads);
|
||||||
this.dependenceDb.insert(payloads, (err, docs) => {
|
return docs;
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(docs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update(
|
public async update(
|
||||||
payload: Dependence & { _id: string },
|
payload: Dependence & { id: string },
|
||||||
): Promise<Dependence> {
|
): Promise<Dependence> {
|
||||||
const { _id, ...other } = payload;
|
const { id, ...other } = payload;
|
||||||
const doc = await this.get(_id);
|
const doc = await this.getDb({ id });
|
||||||
const tab = new Dependence({
|
const tab = new Dependence({
|
||||||
...doc,
|
...doc,
|
||||||
...other,
|
...other,
|
||||||
@@ -61,46 +52,22 @@ export default class DependenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async updateDb(payload: Dependence): Promise<Dependence> {
|
private async updateDb(payload: Dependence): Promise<Dependence> {
|
||||||
return new Promise((resolve) => {
|
await DependenceModel.update(payload, { where: { id: payload.id } });
|
||||||
this.dependenceDb.update(
|
return await this.getDb({ id: payload.id });
|
||||||
{ _id: payload._id },
|
|
||||||
payload,
|
|
||||||
{ returnUpdatedDocs: true },
|
|
||||||
(err, num, doc) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(doc as Dependence);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async remove(ids: string[]) {
|
public async remove(ids: number[]) {
|
||||||
return new Promise((resolve: any) => {
|
await DependenceModel.update(
|
||||||
this.dependenceDb.update(
|
{ status: DependenceStatus.removing, log: [] },
|
||||||
{ _id: { $in: ids } },
|
{ where: { id: ids } },
|
||||||
{ $set: { status: DependenceStatus.removing, log: [] } },
|
);
|
||||||
{ multi: true, returnUpdatedDocs: true },
|
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||||
async (err, num, docs: Dependence[]) => {
|
this.installOrUninstallDependencies(docs, false);
|
||||||
this.installOrUninstallDependencies(docs, false);
|
return docs;
|
||||||
resolve(docs);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async removeDb(ids: string[]) {
|
public async removeDb(ids: number[]) {
|
||||||
return new Promise((resolve: any) => {
|
await DependenceModel.destroy({ where: { id: ids } });
|
||||||
this.dependenceDb.remove(
|
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ multi: true },
|
|
||||||
async (err) => {
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async dependencies(
|
public async dependencies(
|
||||||
@@ -110,171 +77,154 @@ export default class DependenceService {
|
|||||||
): Promise<Dependence[]> {
|
): Promise<Dependence[]> {
|
||||||
let condition = { ...query, type: DependenceTypes[type as any] };
|
let condition = { ...query, type: DependenceTypes[type as any] };
|
||||||
if (searchValue) {
|
if (searchValue) {
|
||||||
const reg = new RegExp(searchValue);
|
const encodeText = encodeURIComponent(searchValue);
|
||||||
condition = {
|
const reg = {
|
||||||
...condition,
|
[Op.or]: [
|
||||||
$or: [
|
{ [Op.like]: `%${searchValue}&` },
|
||||||
{
|
{ [Op.like]: `%${encodeText}%` },
|
||||||
name: reg,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
condition = {
|
||||||
|
...condition,
|
||||||
|
name: reg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await this.find(condition);
|
||||||
|
return result as any;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
const newDocs = await this.find(condition, sort);
|
|
||||||
return newDocs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reInstall(ids: string[]): Promise<Dependence[]> {
|
public async reInstall(ids: number[]): Promise<Dependence[]> {
|
||||||
return new Promise((resolve: any) => {
|
await DependenceModel.update(
|
||||||
this.dependenceDb.update(
|
{ status: DependenceStatus.installing, log: [] },
|
||||||
{ _id: { $in: ids } },
|
{ where: { id: ids } },
|
||||||
{ $set: { status: DependenceStatus.installing, log: [] } },
|
);
|
||||||
{ multi: true, returnUpdatedDocs: true },
|
|
||||||
async (err, num, docs: Dependence[]) => {
|
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||||
this.installOrUninstallDependencies(docs);
|
await this.installOrUninstallDependencies(docs);
|
||||||
resolve(docs);
|
return docs;
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async find(query: any, sort: any): Promise<Dependence[]> {
|
private async find(query: any, sort?: any): Promise<Dependence[]> {
|
||||||
return new Promise((resolve) => {
|
const docs = await DependenceModel.findAll({ where: { ...query } });
|
||||||
this.dependenceDb
|
return docs;
|
||||||
.find(query)
|
|
||||||
.sort({ ...sort })
|
|
||||||
.exec((err, docs) => {
|
|
||||||
resolve(docs);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async get(_id: string): Promise<Dependence> {
|
public async getDb(query: any): Promise<Dependence> {
|
||||||
return new Promise((resolve) => {
|
const doc: any = await DependenceModel.findOne({ where: { ...query } });
|
||||||
this.dependenceDb.find({ _id }).exec((err, docs) => {
|
return doc && (doc.get({ plain: true }) as Dependence);
|
||||||
resolve(docs[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateLog(ids: string[], log: string): Promise<void> {
|
private async updateLog(ids: number[], log: string): Promise<void> {
|
||||||
return new Promise((resolve) => {
|
const doc = await DependenceModel.findOne({ where: { id: ids } });
|
||||||
this.dependenceDb.update(
|
const newLog = doc?.log ? [...doc.log, log] : [log];
|
||||||
{ _id: { $in: ids } },
|
await DependenceModel.update({ log: newLog }, { where: { id: ids } });
|
||||||
{ $push: { log } },
|
|
||||||
{ multi: true },
|
|
||||||
(err, num, doc) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public installOrUninstallDependencies(
|
public installOrUninstallDependencies(
|
||||||
dependencies: Dependence[],
|
dependencies: Dependence[],
|
||||||
isInstall: boolean = true,
|
isInstall: boolean = true,
|
||||||
) {
|
) {
|
||||||
if (dependencies.length === 0) {
|
return new Promise(async (resolve) => {
|
||||||
return;
|
if (dependencies.length === 0) {
|
||||||
}
|
resolve(null);
|
||||||
const depNames = dependencies.map((x) => x.name).join(' ');
|
return;
|
||||||
const depRunCommand = (
|
}
|
||||||
isInstall
|
const depNames = dependencies.map((x) => x.name).join(' ');
|
||||||
? InstallDependenceCommandTypes
|
const depRunCommand = (
|
||||||
: unInstallDependenceCommandTypes
|
isInstall
|
||||||
)[dependencies[0].type as any];
|
? InstallDependenceCommandTypes
|
||||||
const actionText = isInstall ? '安装' : '删除';
|
: unInstallDependenceCommandTypes
|
||||||
const depIds = dependencies.map((x) => x._id) as string[];
|
)[dependencies[0].type as any];
|
||||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
const actionText = isInstall ? '安装' : '删除';
|
||||||
const startTime = Date.now();
|
const depIds = dependencies.map((x) => x.id) as number[];
|
||||||
this.sockService.sendMessage({
|
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||||
type: 'installDependence',
|
const startTime = Date.now();
|
||||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
|
||||||
startTime,
|
|
||||||
).toLocaleString()}`,
|
|
||||||
references: depIds,
|
|
||||||
});
|
|
||||||
this.updateLog(
|
|
||||||
depIds,
|
|
||||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
|
||||||
startTime,
|
|
||||||
).toLocaleString()}\n`,
|
|
||||||
);
|
|
||||||
cp.stdout.on('data', (data) => {
|
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'installDependence',
|
type: 'installDependence',
|
||||||
message: data.toString(),
|
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||||
|
startTime,
|
||||||
|
).toLocaleString()}`,
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
this.updateLog(depIds, data.toString());
|
await this.updateLog(
|
||||||
});
|
|
||||||
|
|
||||||
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} 秒`,
|
|
||||||
references: depIds,
|
|
||||||
});
|
|
||||||
this.updateLog(
|
|
||||||
depIds,
|
depIds,
|
||||||
`依赖${actionText}${resultText},结束时间 ${new Date(
|
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||||
endTime,
|
startTime,
|
||||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
).toLocaleString()}\n`,
|
||||||
);
|
);
|
||||||
|
cp.stdout.on('data', async (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: data.toString(),
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
await this.updateLog(depIds, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
let status = null;
|
cp.stderr.on('data', async (data) => {
|
||||||
if (isSucceed) {
|
this.sockService.sendMessage({
|
||||||
status = isInstall
|
type: 'installDependence',
|
||||||
? DependenceStatus.installed
|
message: data.toString(),
|
||||||
: DependenceStatus.removed;
|
references: depIds,
|
||||||
} else {
|
});
|
||||||
status = isInstall
|
await this.updateLog(depIds, data.toString());
|
||||||
? DependenceStatus.installFailed
|
});
|
||||||
: DependenceStatus.removeFailed;
|
|
||||||
}
|
|
||||||
this.dependenceDb.update(
|
|
||||||
{ _id: { $in: depIds } },
|
|
||||||
{
|
|
||||||
$set: { status },
|
|
||||||
$unset: { pid: true },
|
|
||||||
},
|
|
||||||
{ multi: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果删除依赖成功,3秒后删除数据库记录
|
cp.on('error', async (err) => {
|
||||||
if (isSucceed && !isInstall) {
|
this.sockService.sendMessage({
|
||||||
setTimeout(() => {
|
type: 'installDependence',
|
||||||
this.removeDb(depIds);
|
message: JSON.stringify(err),
|
||||||
}, 5000);
|
references: depIds,
|
||||||
}
|
});
|
||||||
|
await this.updateLog(depIds, JSON.stringify(err));
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('close', async (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,
|
||||||
|
});
|
||||||
|
await 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;
|
||||||
|
}
|
||||||
|
await DependenceModel.update({ status }, { where: { id: depIds } });
|
||||||
|
|
||||||
|
// 如果删除依赖成功,3秒后删除数据库记录
|
||||||
|
if (isSucceed && !isInstall) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.removeDb(depIds);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-109
@@ -3,15 +3,12 @@ import winston from 'winston';
|
|||||||
import { getFileContentByName } from '../config/util';
|
import { getFileContentByName } from '../config/util';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import DataStore from 'nedb';
|
import { Env, EnvModel, EnvStatus, initEnvPosition } from '../data/env';
|
||||||
import { Env, EnvStatus, initEnvPosition } from '../data/env';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { dbs } from '../loaders/db';
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class EnvService {
|
export default class EnvService {
|
||||||
private envDb = dbs.envDb;
|
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
public async create(payloads: Env[]): Promise<Env[]> {
|
public async create(payloads: Env[]): Promise<Env[]> {
|
||||||
@@ -31,54 +28,27 @@ export default class EnvService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async insert(payloads: Env[]): Promise<Env[]> {
|
public async insert(payloads: Env[]): Promise<Env[]> {
|
||||||
return new Promise((resolve) => {
|
const docs = await EnvModel.bulkCreate(payloads);
|
||||||
this.envDb.insert(payloads, (err, docs) => {
|
return docs;
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(docs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update(payload: Env): Promise<Env> {
|
public async update(payload: Env): Promise<Env> {
|
||||||
const { _id, ...other } = payload;
|
const newDoc = await this.updateDb(payload);
|
||||||
const doc = await this.get(_id);
|
|
||||||
const tab = new Env({ ...doc, ...other });
|
|
||||||
const newDoc = await this.updateDb(tab);
|
|
||||||
await this.set_envs();
|
await this.set_envs();
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateDb(payload: Env): Promise<Env> {
|
private async updateDb(payload: Env): Promise<Env> {
|
||||||
return new Promise((resolve) => {
|
await EnvModel.update({ ...payload }, { where: { id: payload.id } });
|
||||||
this.envDb.update(
|
return await this.getDb({ id: payload.id });
|
||||||
{ _id: payload._id },
|
|
||||||
payload,
|
|
||||||
{ returnUpdatedDocs: true },
|
|
||||||
(err, num, doc) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(doc as Env);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async remove(ids: string[]) {
|
public async remove(ids: string[]) {
|
||||||
return new Promise((resolve: any) => {
|
await EnvModel.destroy({ where: { id: ids } });
|
||||||
this.envDb.remove({ _id: { $in: ids } }, { multi: true }, async (err) => {
|
|
||||||
await this.set_envs();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async move(
|
public async move(
|
||||||
_id: string,
|
id: number,
|
||||||
{
|
{
|
||||||
fromIndex,
|
fromIndex,
|
||||||
toIndex,
|
toIndex,
|
||||||
@@ -100,7 +70,7 @@ export default class EnvService {
|
|||||||
: (envs[toIndex].position + envs[toIndex + 1].position) / 2;
|
: (envs[toIndex].position + envs[toIndex + 1].position) / 2;
|
||||||
}
|
}
|
||||||
const newDoc = await this.update({
|
const newDoc = await this.update({
|
||||||
_id,
|
id,
|
||||||
position: targetPosition,
|
position: targetPosition,
|
||||||
});
|
});
|
||||||
return newDoc;
|
return newDoc;
|
||||||
@@ -113,103 +83,73 @@ export default class EnvService {
|
|||||||
): Promise<Env[]> {
|
): Promise<Env[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const reg = new RegExp(searchText);
|
const encodeText = encodeURIComponent(searchText);
|
||||||
|
const reg = {
|
||||||
|
[Op.or]: [
|
||||||
|
{ [Op.like]: `%${searchText}%` },
|
||||||
|
{ [Op.like]: `%${encodeText}%` },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
condition = {
|
condition = {
|
||||||
$or: [
|
...condition,
|
||||||
{
|
[Op.or]: [
|
||||||
value: reg,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: reg,
|
name: reg,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: reg,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
remarks: reg,
|
remarks: reg,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const newDocs = await this.find(condition, sort);
|
try {
|
||||||
return newDocs;
|
const result = await this.find(condition, [['position', 'DESC']]);
|
||||||
|
return result as any;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async find(query: any, sort: any): Promise<Env[]> {
|
private async find(query: any, sort: any = []): Promise<Env[]> {
|
||||||
return new Promise((resolve) => {
|
const docs = await EnvModel.findAll({
|
||||||
this.envDb
|
where: { ...query },
|
||||||
.find(query)
|
order: [...sort],
|
||||||
.sort({ ...sort })
|
|
||||||
.exec((err, docs) => {
|
|
||||||
resolve(docs);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async get(_id: string): Promise<Env> {
|
public async getDb(query: any): Promise<Env> {
|
||||||
return new Promise((resolve) => {
|
const doc: any = await EnvModel.findOne({ where: { ...query } });
|
||||||
this.envDb.find({ _id }).exec((err, docs) => {
|
return doc && (doc.get({ plain: true }) as Env);
|
||||||
resolve(docs[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getBySort(sort: any): Promise<Env> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.envDb
|
|
||||||
.find({})
|
|
||||||
.sort({ ...sort })
|
|
||||||
.limit(1)
|
|
||||||
.exec((err, docs) => {
|
|
||||||
resolve(docs[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disabled(ids: string[]) {
|
public async disabled(ids: string[]) {
|
||||||
return new Promise((resolve: any) => {
|
await EnvModel.update(
|
||||||
this.envDb.update(
|
{ status: EnvStatus.disabled },
|
||||||
{ _id: { $in: ids } },
|
{ where: { id: ids } },
|
||||||
{ $set: { status: EnvStatus.disabled } },
|
);
|
||||||
{ multi: true },
|
await this.set_envs();
|
||||||
async (err) => {
|
|
||||||
await this.set_envs();
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async enabled(ids: string[]) {
|
public async enabled(ids: string[]) {
|
||||||
return new Promise((resolve: any) => {
|
await EnvModel.update({ status: EnvStatus.normal }, { where: { id: ids } });
|
||||||
this.envDb.update(
|
await this.set_envs();
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { status: EnvStatus.normal } },
|
|
||||||
{ multi: true },
|
|
||||||
async (err, num) => {
|
|
||||||
await this.set_envs();
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateNames({ ids, name }: { ids: string[]; name: string }) {
|
public async updateNames({ ids, name }: { ids: string[]; name: string }) {
|
||||||
return new Promise((resolve: any) => {
|
await EnvModel.update({ name }, { where: { id: ids } });
|
||||||
this.envDb.update(
|
await this.set_envs();
|
||||||
{ _id: { $in: ids } },
|
|
||||||
{ $set: { name } },
|
|
||||||
{ multi: true },
|
|
||||||
async (err, num) => {
|
|
||||||
await this.set_envs();
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async set_envs() {
|
public async set_envs() {
|
||||||
const envs = await this.envs(
|
const envs = await this.envs(
|
||||||
'',
|
'',
|
||||||
{ position: -1 },
|
{ position: -1 },
|
||||||
{ name: { $exists: true } },
|
{ name: { [Op.not]: null } },
|
||||||
);
|
);
|
||||||
const groups = _.groupBy(envs, 'name');
|
const groups = _.groupBy(envs, 'name');
|
||||||
let env_string = '';
|
let env_string = '';
|
||||||
@@ -219,11 +159,17 @@ export default class EnvService {
|
|||||||
|
|
||||||
// 忽略不符合bash要求的环境变量名称
|
// 忽略不符合bash要求的环境变量名称
|
||||||
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) {
|
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) {
|
||||||
env_string += `export ${key}="${_(group)
|
let value = _(group)
|
||||||
.filter((x) => x.status !== EnvStatus.disabled)
|
.filter((x) => x.status !== EnvStatus.disabled)
|
||||||
.map('value')
|
.map('value')
|
||||||
.join('&')
|
.join('&')
|
||||||
.replace(/ /g, '')}"\n`;
|
.replace(/ /g, '');
|
||||||
|
if (/"/.test(value)) {
|
||||||
|
value = `'${value}'`;
|
||||||
|
} else {
|
||||||
|
value = `"${value}"`;
|
||||||
|
}
|
||||||
|
env_string += `export ${key}=${value}\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-114
@@ -2,90 +2,68 @@ import { Service, Inject } from 'typedi';
|
|||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { createRandomString } from '../config/util';
|
import { createRandomString } from '../config/util';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import DataStore from 'nedb';
|
import { App, AppModel } from '../data/open';
|
||||||
import { App } from '../data/open';
|
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
import { dbs } from '../loaders/db';
|
import sequelize, { Op } from 'sequelize';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class OpenService {
|
export default class OpenService {
|
||||||
private appDb = dbs.appDb;
|
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
public async findTokenByValue(token: string): Promise<App> {
|
public async findTokenByValue(token: string): Promise<App | null> {
|
||||||
return new Promise((resolve) => {
|
const docs = await this.find({});
|
||||||
this.appDb.find(
|
const doc = docs.filter((x) => x.tokens?.find((y) => y.value === token));
|
||||||
{ tokens: { $elemMatch: { value: token } } },
|
return doc[0];
|
||||||
(err, docs) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(docs[0]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create(payload: App): Promise<App> {
|
public async create(payload: App): Promise<App> {
|
||||||
const tab = new App({ ...payload });
|
const tab = { ...payload };
|
||||||
tab.client_id = createRandomString(12, 12);
|
tab.client_id = createRandomString(12, 12);
|
||||||
tab.client_secret = createRandomString(24, 24);
|
tab.client_secret = createRandomString(24, 24);
|
||||||
const docs = await this.insert([tab]);
|
const doc = await this.insert(tab);
|
||||||
return { ...docs[0], tokens: [] };
|
return { ...doc, tokens: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async insert(payloads: App[]): Promise<App[]> {
|
public async insert(payloads: App): Promise<App> {
|
||||||
return new Promise((resolve) => {
|
const doc = await AppModel.create(payloads, { returning: true });
|
||||||
this.appDb.insert(payloads, (err, docs) => {
|
return doc.get({ plain: true }) as App;
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(docs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update(payload: App): Promise<App> {
|
public async update(payload: App): Promise<App> {
|
||||||
const { _id, client_id, client_secret, tokens, ...other } = payload;
|
const newDoc = await this.updateDb({
|
||||||
const doc = await this.get(_id);
|
name: payload.name,
|
||||||
const tab = new App({ ...doc, ...other });
|
scopes: payload.scopes,
|
||||||
const newDoc = await this.updateDb(tab);
|
id: payload.id,
|
||||||
|
} as any);
|
||||||
return { ...newDoc, tokens: [] };
|
return { ...newDoc, tokens: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateDb(payload: App): Promise<App> {
|
private async updateDb(payload: App): Promise<App> {
|
||||||
return new Promise((resolve) => {
|
await AppModel.update(payload, { where: { id: payload.id } });
|
||||||
this.appDb.update(
|
return await this.getDb({ id: payload.id });
|
||||||
{ _id: payload._id },
|
|
||||||
payload,
|
|
||||||
{ returnUpdatedDocs: true },
|
|
||||||
(err, num, doc, up) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(doc);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async remove(ids: string[]) {
|
public async getDb(query: any): Promise<App> {
|
||||||
return new Promise((resolve: any) => {
|
const doc: any = await AppModel.findOne({ where: query });
|
||||||
this.appDb.remove({ _id: { $in: ids } }, { multi: true }, async (err) => {
|
return doc && (doc.get({ plain: true }) as App);
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resetSecret(_id: string): Promise<App> {
|
public async remove(ids: number[]) {
|
||||||
const doc = await this.get(_id);
|
await AppModel.destroy({ where: { id: ids } });
|
||||||
const tab = new App({ ...doc });
|
}
|
||||||
tab.client_secret = createRandomString(24, 24);
|
|
||||||
tab.tokens = [];
|
public async resetSecret(id: number): Promise<App> {
|
||||||
|
const tab: any = {
|
||||||
|
client_secret: createRandomString(24, 24),
|
||||||
|
tokens: [],
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
// const doc = await this.get(id);
|
||||||
|
// const tab = new App({ ...doc });
|
||||||
|
// tab.client_secret = createRandomString(24, 24);
|
||||||
|
// tab.tokens = [];
|
||||||
|
// const newDoc = await this.updateDb(tab);
|
||||||
|
// return newDoc;
|
||||||
const newDoc = await this.updateDb(tab);
|
const newDoc = await this.updateDb(tab);
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
@@ -97,42 +75,30 @@ export default class OpenService {
|
|||||||
): Promise<App[]> {
|
): Promise<App[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const reg = new RegExp(searchText);
|
const encodeText = encodeURIComponent(searchText);
|
||||||
condition = {
|
const reg = {
|
||||||
$or: [
|
[Op.or]: [
|
||||||
{
|
{ [Op.like]: `%${searchText}%` },
|
||||||
value: reg,
|
{ [Op.like]: `%${encodeText}%` },
|
||||||
},
|
|
||||||
{
|
|
||||||
name: reg,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
remarks: reg,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
condition = {
|
||||||
|
...condition,
|
||||||
|
name: reg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await this.find(condition);
|
||||||
|
return result.map((x) => ({ ...x, tokens: [] }));
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
const newDocs = await this.find(condition, sort);
|
|
||||||
return newDocs.map((x) => ({ ...x, tokens: [] }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async find(query: any, sort: any): Promise<App[]> {
|
private async find(query: any, sort?: any): Promise<App[]> {
|
||||||
return new Promise((resolve) => {
|
const docs = await AppModel.findAll({ where: { ...query } });
|
||||||
this.appDb
|
return docs.map((x) => x.get({ plain: true }));
|
||||||
.find(query)
|
|
||||||
.sort({ ...sort })
|
|
||||||
.exec((err, docs) => {
|
|
||||||
resolve(docs);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async get(_id: string): Promise<App> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.appDb.find({ _id }).exec((err, docs) => {
|
|
||||||
resolve(docs[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async authToken({
|
public async authToken({
|
||||||
@@ -144,28 +110,22 @@ export default class OpenService {
|
|||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
const token = uuidV4();
|
const token = uuidV4();
|
||||||
const expiration = Math.round(Date.now() / 1000) + 2592000; // 2592000 30天
|
const expiration = Math.round(Date.now() / 1000) + 2592000; // 2592000 30天
|
||||||
return new Promise((resolve) => {
|
const doc = await AppModel.findOne({ where: { client_id, client_secret } });
|
||||||
this.appDb.find({ client_id, client_secret }).exec((err, docs) => {
|
if (doc) {
|
||||||
if (docs && docs[0]) {
|
await AppModel.update(
|
||||||
this.appDb.update(
|
{ tokens: [...(doc.tokens || []), { value: token, expiration }] },
|
||||||
{ client_id, client_secret },
|
{ where: { client_id, client_secret } },
|
||||||
{ $push: { tokens: { value: token, expiration } } },
|
);
|
||||||
{},
|
return {
|
||||||
(err, num, doc) => {
|
code: 200,
|
||||||
resolve({
|
data: {
|
||||||
code: 200,
|
token,
|
||||||
data: {
|
token_type: 'Bearer',
|
||||||
token,
|
expiration,
|
||||||
token_type: 'Bearer',
|
},
|
||||||
expiration,
|
};
|
||||||
},
|
} else {
|
||||||
});
|
return { code: 400, message: 'client_id或client_seret有误' };
|
||||||
},
|
}
|
||||||
);
|
|
||||||
} else {
|
|
||||||
resolve({ code: 400, message: 'client_id或client_seret有误' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ import { exec } from 'child_process';
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ScheduleService {
|
export default class ScheduleService {
|
||||||
private scheduleStacks = new Map<string, nodeSchedule.Job>();
|
private scheduleStacks = new Map<number, nodeSchedule.Job>();
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
async generateSchedule({ _id = '', command, name, schedule }: Crontab) {
|
async generateSchedule({ id = 0, command, name, schedule }: Crontab) {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
'[创建定时任务],任务ID: %s,cron: %s,任务名: %s,执行命令: %s',
|
'[创建定时任务],任务ID: %s,cron: %s,任务名: %s,执行命令: %s',
|
||||||
_id,
|
id,
|
||||||
schedule,
|
schedule,
|
||||||
name,
|
name,
|
||||||
command,
|
command,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.scheduleStacks.set(
|
this.scheduleStacks.set(
|
||||||
_id,
|
id,
|
||||||
nodeSchedule.scheduleJob(_id, schedule, async () => {
|
nodeSchedule.scheduleJob(id + '', schedule, async () => {
|
||||||
try {
|
try {
|
||||||
exec(command, async (error, stdout, stderr) => {
|
exec(command, async (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -55,8 +55,8 @@ export default class ScheduleService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelSchedule({ _id = '', name }: Crontab) {
|
async cancelSchedule({ id = 0, name }: Crontab) {
|
||||||
this.logger.info('[取消定时任务],任务名:%s', name);
|
this.logger.info('[取消定时任务],任务名:%s', name);
|
||||||
this.scheduleStacks.has(_id) && this.scheduleStacks.get(_id)?.cancel();
|
this.scheduleStacks.has(id) && this.scheduleStacks.get(id)?.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-4
@@ -2,21 +2,19 @@ import { Service, Inject } from 'typedi';
|
|||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
|
import CronService from './cron';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ScriptService {
|
export default class ScriptService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
|
private cronService: CronService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async runScript(path: string) {
|
public async runScript(path: string) {
|
||||||
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
|
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'manuallyRunScript',
|
|
||||||
message: `开始执行脚本`,
|
|
||||||
});
|
|
||||||
cp.stdout.on('data', (data) => {
|
cp.stdout.on('data', (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'manuallyRunScript',
|
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 };
|
return { code: 200 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import config from '../config';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { AuthDataType, AuthInfo, AuthModel, 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';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class SystemService {
|
||||||
|
@Inject((type) => NotificationService)
|
||||||
|
private notificationService!: NotificationService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject('logger') private logger: winston.Logger,
|
||||||
|
private scheduleService: ScheduleService,
|
||||||
|
private sockService: SockService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async getLogRemoveFrequency() {
|
||||||
|
const doc = await this.getDb({ type: AuthDataType.removeLogFrequency });
|
||||||
|
return (doc && doc.info) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateAuthDb(payload: AuthInfo): Promise<any> {
|
||||||
|
await AuthModel.upsert({ ...payload });
|
||||||
|
const doc = await this.getDb({ type: payload.type });
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDb(query: any): Promise<any> {
|
||||||
|
const doc: any = await AuthModel.findOne({ where: { ...query } });
|
||||||
|
return doc && (doc.get({ plain: true }) as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-71
@@ -6,8 +6,7 @@ import * as fs from 'fs';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { authenticator } from '@otplib/preset-default';
|
import { authenticator } from '@otplib/preset-default';
|
||||||
import DataStore from 'nedb';
|
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
||||||
import { AuthDataType, AuthInfo, LoginStatus } from '../data/auth';
|
|
||||||
import { NotificationInfo } from '../data/notify';
|
import { NotificationInfo } from '../data/notify';
|
||||||
import NotificationService from './notify';
|
import NotificationService from './notify';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
@@ -15,13 +14,11 @@ import ScheduleService from './schedule';
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import { dbs } from '../loaders/db';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
@Inject((type) => NotificationService)
|
@Inject((type) => NotificationService)
|
||||||
private notificationService!: NotificationService;
|
private notificationService!: NotificationService;
|
||||||
private authDb = dbs.authDb;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
@@ -174,33 +171,24 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getLoginLog(): Promise<AuthInfo[]> {
|
public async getLoginLog(): Promise<AuthInfo[]> {
|
||||||
return new Promise((resolve) => {
|
const docs = await AuthModel.findAll({
|
||||||
this.authDb.find({ type: AuthDataType.loginLog }).exec((err, docs) => {
|
where: { type: AuthDataType.loginLog },
|
||||||
if (err || docs.length === 0) {
|
|
||||||
resolve([]);
|
|
||||||
} else {
|
|
||||||
const result = docs.sort(
|
|
||||||
(a, b) => b.info.timestamp - a.info.timestamp,
|
|
||||||
);
|
|
||||||
if (result.length > 100) {
|
|
||||||
this.authDb.remove({ _id: result[result.length - 1]._id });
|
|
||||||
}
|
|
||||||
resolve(result.map((x) => x.info));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
if (docs && docs.length > 0) {
|
||||||
|
const result = docs.sort((a, b) => b.info.timestamp - a.info.timestamp);
|
||||||
|
if (result.length > 100) {
|
||||||
|
await AuthModel.destroy({
|
||||||
|
where: { id: result[result.length - 1].id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result.map((x) => x.info);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async insertDb(payload: AuthInfo): Promise<AuthInfo> {
|
private async insertDb(payload: AuthInfo): Promise<AuthInfo> {
|
||||||
return new Promise((resolve) => {
|
const doc = await AuthModel.create({ ...payload }, { returning: true });
|
||||||
this.authDb.insert(payload, (err, doc) => {
|
return doc;
|
||||||
if (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
} else {
|
|
||||||
resolve(doc);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initAuthInfo() {
|
private initAuthInfo() {
|
||||||
@@ -315,48 +303,24 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getNotificationMode(): Promise<NotificationInfo> {
|
public async getNotificationMode(): Promise<NotificationInfo> {
|
||||||
return new Promise((resolve) => {
|
const doc = await this.getDb({ type: AuthDataType.notification });
|
||||||
this.authDb
|
return (doc && doc.info) || {};
|
||||||
.find({ type: AuthDataType.notification })
|
|
||||||
.exec((err, docs) => {
|
|
||||||
if (err || docs.length === 0) {
|
|
||||||
resolve({} as NotificationInfo);
|
|
||||||
} else {
|
|
||||||
resolve(docs[0].info);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getLogRemoveFrequency() {
|
public async getLogRemoveFrequency() {
|
||||||
return new Promise((resolve) => {
|
const doc = await this.getDb({ type: AuthDataType.removeLogFrequency });
|
||||||
this.authDb
|
return (doc && doc.info) || {};
|
||||||
.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> {
|
private async updateAuthDb(payload: AuthInfo): Promise<any> {
|
||||||
return new Promise((resolve) => {
|
await AuthModel.upsert({ ...payload });
|
||||||
this.authDb.update(
|
const doc = await this.getDb({ type: payload.type });
|
||||||
{ type: payload.type },
|
return doc;
|
||||||
{ ...payload },
|
}
|
||||||
{ upsert: true, returnUpdatedDocs: true },
|
|
||||||
(err, num, doc: any) => {
|
public async getDb(query: any): Promise<any> {
|
||||||
if (err) {
|
const doc: any = await AuthModel.findOne({ where: { ...query } });
|
||||||
resolve({} as NotificationInfo);
|
return doc && (doc.get({ plain: true }) as any);
|
||||||
} else {
|
|
||||||
resolve({ ...doc.info, _id: doc._id });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateNotificationMode(notificationInfo: NotificationInfo) {
|
public async updateNotificationMode(notificationInfo: NotificationInfo) {
|
||||||
@@ -383,7 +347,7 @@ export default class UserService {
|
|||||||
info: { frequency },
|
info: { frequency },
|
||||||
});
|
});
|
||||||
const cron = {
|
const cron = {
|
||||||
_id: result._id,
|
id: result.id,
|
||||||
name: '删除日志',
|
name: '删除日志',
|
||||||
command: `ql rmlog ${frequency}`,
|
command: `ql rmlog ${frequency}`,
|
||||||
schedule: `5 23 */${frequency} * *`,
|
schedule: `5 23 */${frequency} * *`,
|
||||||
@@ -403,13 +367,22 @@ export default class UserService {
|
|||||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||||
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
||||||
|
|
||||||
const lastVersionFileContent = await (
|
let lastVersion = '';
|
||||||
await got.get(config.lastVersionFile)
|
let lastLog = '';
|
||||||
).body;
|
try {
|
||||||
const lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
const result = await Promise.race([
|
||||||
const lastLog = lastVersionFileContent.match(logRegx)
|
got.get(config.lastVersionFile, { timeout: 1000, retry: 0 }),
|
||||||
? lastVersionFileContent.match(logRegx)![1]
|
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 {
|
return {
|
||||||
code: 200,
|
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;
|
||||||
@@ -28,6 +28,9 @@ RUN set -x \
|
|||||||
jq \
|
jq \
|
||||||
openssh \
|
openssh \
|
||||||
py3-pip \
|
py3-pip \
|
||||||
|
python2 \
|
||||||
|
g++ \
|
||||||
|
make \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ link_shell
|
|||||||
|
|
||||||
echo -e "======================1. 检测配置文件========================\n"
|
echo -e "======================1. 检测配置文件========================\n"
|
||||||
make_dir /etc/nginx/conf.d
|
make_dir /etc/nginx/conf.d
|
||||||
|
make_dir /run/nginx
|
||||||
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
|
|||||||
@@ -42,4 +42,8 @@ server {
|
|||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~ .*\.(html)$ {
|
||||||
|
add_header Cache-Control no-cache;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-4
@@ -25,10 +25,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@otplib/preset-default": "^12.0.1",
|
"@otplib/preset-default": "^12.0.1",
|
||||||
"@sentry/node": "^6.14.0",
|
"@sentry/node": "^6.16.1",
|
||||||
"@sentry/tracing": "^6.14.0",
|
"@sentry/tracing": "^6.16.1",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"celebrate": "^13.0.3",
|
"celebrate": "^13.0.3",
|
||||||
|
"chokidar": "^3.5.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cron-parser": "^3.5.0",
|
"cron-parser": "^3.5.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
@@ -46,17 +47,20 @@
|
|||||||
"nodemailer": "^6.7.0",
|
"nodemailer": "^6.7.0",
|
||||||
"p-queue": "6.6.2",
|
"p-queue": "6.6.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"sequelize": "^7.0.0-alpha.3",
|
||||||
"serve-handler": "^6.1.3",
|
"serve-handler": "^6.1.3",
|
||||||
"sockjs": "^0.3.21",
|
"sockjs": "^0.3.21",
|
||||||
|
"sqlite3": "^5.0.2",
|
||||||
"typedi": "^0.8.0",
|
"typedi": "^0.8.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3",
|
||||||
|
"yargs": "^17.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^4.6.2",
|
"@ant-design/icons": "^4.6.2",
|
||||||
"@ant-design/pro-layout": "^6.26.0",
|
"@ant-design/pro-layout": "^6.26.0",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "^4.3.1",
|
||||||
"@sentry/react": "^6.14.0",
|
"@sentry/react": "^6.16.1",
|
||||||
"@sentry/webpack-plugin": "^1.18.3",
|
"@sentry/webpack-plugin": "^1.18.3",
|
||||||
"@types/cors": "^2.8.10",
|
"@types/cors": "^2.8.10",
|
||||||
"@types/express": "^4.17.8",
|
"@types/express": "^4.17.8",
|
||||||
@@ -74,6 +78,7 @@
|
|||||||
"@types/serve-handler": "^6.1.1",
|
"@types/serve-handler": "^6.1.1",
|
||||||
"@types/sockjs": "^0.3.33",
|
"@types/sockjs": "^0.3.33",
|
||||||
"@types/sockjs-client": "^1.5.1",
|
"@types/sockjs-client": "^1.5.1",
|
||||||
|
"@types/uuid": "^8.3.3",
|
||||||
"@umijs/plugin-antd": "^0.11.0",
|
"@umijs/plugin-antd": "^0.11.0",
|
||||||
"@umijs/test": "^3.3.9",
|
"@umijs/test": "^3.3.9",
|
||||||
"antd": "^4.17.0-alpha.6",
|
"antd": "^4.17.0-alpha.6",
|
||||||
@@ -81,6 +86,7 @@
|
|||||||
"compression-webpack-plugin": "6.1.1",
|
"compression-webpack-plugin": "6.1.1",
|
||||||
"darkreader": "^4.9.27",
|
"darkreader": "^4.9.27",
|
||||||
"lint-staged": "^10.0.7",
|
"lint-staged": "^10.0.7",
|
||||||
|
"@mapbox/node-pre-gyp": "1.0.8",
|
||||||
"nodemon": "^2.0.4",
|
"nodemon": "^2.0.4",
|
||||||
"prettier": "^2.2.0",
|
"prettier": "^2.2.0",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
|
|||||||
+12
-9
@@ -16,8 +16,8 @@ DefaultCronRule=""
|
|||||||
## ql repo命令拉取脚本时需要拉取的文件后缀,直接写文件后缀名即可
|
## ql repo命令拉取脚本时需要拉取的文件后缀,直接写文件后缀名即可
|
||||||
RepoFileExtensions="js py"
|
RepoFileExtensions="js py"
|
||||||
|
|
||||||
## 由于github仓库拉取较慢,所以会默认添加代理前缀,如不需要请移除
|
## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890
|
||||||
GithubProxyUrl="https://ghproxy.com/"
|
ProxyUrl=""
|
||||||
|
|
||||||
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||||
CommandTimeoutTime="1h"
|
CommandTimeoutTime="1h"
|
||||||
@@ -35,6 +35,9 @@ EnableExtraShell="true"
|
|||||||
## 是否自动启动bot,默认不启动,设置为true时自动启动,目前需要自行克隆bot仓库所需代码,存到ql/repo目录下,文件夹命名为dockerbot
|
## 是否自动启动bot,默认不启动,设置为true时自动启动,目前需要自行克隆bot仓库所需代码,存到ql/repo目录下,文件夹命名为dockerbot
|
||||||
AutoStartBot=""
|
AutoStartBot=""
|
||||||
|
|
||||||
|
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
||||||
|
BotRepoUrl=""
|
||||||
|
|
||||||
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
|
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
|
||||||
PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple"
|
PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ export BARK_SOUND=""
|
|||||||
## 下方填写推送消息分组,默认为"QingLong"
|
## 下方填写推送消息分组,默认为"QingLong"
|
||||||
export BARK_GROUP="QingLong"
|
export BARK_GROUP="QingLong"
|
||||||
|
|
||||||
## 3. Telegram
|
## 3. Telegram
|
||||||
## 下方填写自己申请@BotFather的Token,如10xxx4:AAFcqxxxxgER5uw
|
## 下方填写自己申请@BotFather的Token,如10xxx4:AAFcqxxxxgER5uw
|
||||||
export TG_BOT_TOKEN=""
|
export TG_BOT_TOKEN=""
|
||||||
## 下方填写 @getuseridbot 中获取到的纯数字ID
|
## 下方填写 @getuseridbot 中获取到的纯数字ID
|
||||||
@@ -73,7 +76,7 @@ export TG_PROXY_AUTH=""
|
|||||||
## 如需使用,请赋值代理地址链接,并自行解除下一行的注释
|
## 如需使用,请赋值代理地址链接,并自行解除下一行的注释
|
||||||
export TG_API_HOST=""
|
export TG_API_HOST=""
|
||||||
|
|
||||||
## 4. 钉钉
|
## 4. 钉钉
|
||||||
## 官方文档:https://developers.dingtalk.com/document/app/custom-robot-access
|
## 官方文档:https://developers.dingtalk.com/document/app/custom-robot-access
|
||||||
## 下方填写token后面的内容,只需 https://oapi.dingtalk.com/robot/send?access_token=XXX 等于=符号后面的XXX即可
|
## 下方填写token后面的内容,只需 https://oapi.dingtalk.com/robot/send?access_token=XXX 等于=符号后面的XXX即可
|
||||||
export DD_BOT_TOKEN=""
|
export DD_BOT_TOKEN=""
|
||||||
@@ -104,9 +107,9 @@ export PUSH_PLUS_TOKEN=""
|
|||||||
export PUSH_PLUS_USER=""
|
export PUSH_PLUS_USER=""
|
||||||
|
|
||||||
## 9. go-cqhttp
|
## 9. go-cqhttp
|
||||||
## gobot_url 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg
|
## gobot_url 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg
|
||||||
## gobot_token 填写在go-cqhttp文件设置的访问密钥
|
## gobot_token 填写在go-cqhttp文件设置的访问密钥
|
||||||
## gobot_qq 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群
|
## gobot_qq 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群
|
||||||
## go-cqhttp相关API https://docs.go-cqhttp.org/api
|
## go-cqhttp相关API https://docs.go-cqhttp.org/api
|
||||||
export GOBOT_URL=""
|
export GOBOT_URL=""
|
||||||
export GOBOT_TOKEN=""
|
export GOBOT_TOKEN=""
|
||||||
@@ -116,8 +119,8 @@ export GOBOT_QQ=""
|
|||||||
## gotify_url 填写gotify地址,如https://push.example.de:8080
|
## gotify_url 填写gotify地址,如https://push.example.de:8080
|
||||||
## gotify_token 填写gotify的消息应用token
|
## gotify_token 填写gotify的消息应用token
|
||||||
## gotify_priority 填写推送消息优先级,默认为0
|
## gotify_priority 填写推送消息优先级,默认为0
|
||||||
export GOTIFY_URL="";
|
export GOTIFY_URL=""
|
||||||
export GOTIFY_TOKEN="";
|
export GOTIFY_TOKEN=""
|
||||||
export GOTIFY_PRIORITY=0;
|
export GOTIFY_PRIORITY=0
|
||||||
|
|
||||||
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ def pushplus_bot(title: str, content: str) -> None:
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
url_old = "http://pushplus.hxtrip.com/send"
|
url_old = "http://pushplus.hxtrip.com/send"
|
||||||
|
headers["Accept"] = "application/json"
|
||||||
response = requests.post(url=url_old, data=body, headers=headers).json()
|
response = requests.post(url=url_old, data=body, headers=headers).json()
|
||||||
|
|
||||||
if response["code"] == 200:
|
if response["code"] == 200:
|
||||||
|
|||||||
+2
-2
@@ -61,7 +61,7 @@ update_cron_api() {
|
|||||||
-H "Origin: http://0.0.0.0:5700" \
|
-H "Origin: http://0.0.0.0:5700" \
|
||||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||||
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\",\"_id\":\"$id\"}" \
|
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo $api | jq -r .code)
|
code=$(echo $api | jq -r .code)
|
||||||
@@ -93,7 +93,7 @@ update_cron_command_api() {
|
|||||||
-H "Origin: http://0.0.0.0:5700" \
|
-H "Origin: http://0.0.0.0:5700" \
|
||||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||||
--data-raw "{\"command\":\"$command\",\"_id\":\"$id\"}" \
|
--data-raw "{\"command\":\"$command\",\"id\":\"$id\"}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo $api | jq -r .code)
|
code=$(echo $api | jq -r .code)
|
||||||
|
|||||||
+8
-2
@@ -3,8 +3,14 @@
|
|||||||
## 导入通用变量与函数
|
## 导入通用变量与函数
|
||||||
dir_shell=/ql/shell
|
dir_shell=/ql/shell
|
||||||
. $dir_shell/share.sh
|
. $dir_shell/share.sh
|
||||||
url="${github_proxy_url}https://github.com/SuMaiKaDe/bot.git"
|
|
||||||
repo_path="${dir_repo}/dockerbot"
|
if [[ -z ${BotRepoUrl} ]]; then
|
||||||
|
url="https://github.com/SuMaiKaDe/bot.git"
|
||||||
|
repo_path="${dir_repo}/dockerbot"
|
||||||
|
else
|
||||||
|
url=${BotRepoUrl}
|
||||||
|
repo_path="${dir_repo}/diybot"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "\n1、安装bot依赖...\n"
|
echo -e "\n1、安装bot依赖...\n"
|
||||||
apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev
|
apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev
|
||||||
|
|||||||
+36
-1
@@ -35,6 +35,8 @@ file_notify_js=$dir_scripts/sendNotify.js
|
|||||||
task_error_log_path=$dir_log/task_error.log
|
task_error_log_path=$dir_log/task_error.log
|
||||||
nginx_app_conf=$dir_root/docker/front.conf
|
nginx_app_conf=$dir_root/docker/front.conf
|
||||||
nginx_conf=$dir_root/docker/nginx.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
|
list_crontab_user=$dir_config/crontab.list
|
||||||
@@ -65,7 +67,7 @@ import_config() {
|
|||||||
[[ -f $file_env ]] && . $file_env
|
[[ -f $file_env ]] && . $file_env
|
||||||
|
|
||||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
||||||
github_proxy_url=${GithubProxyUrl:-""}
|
proxy_url=${ProxyUrl:-""}
|
||||||
file_extensions=${RepoFileExtensions:-"js py"}
|
file_extensions=${RepoFileExtensions:-"js py"}
|
||||||
|
|
||||||
if [[ -n "${DefaultCronRule}" ]]; then
|
if [[ -n "${DefaultCronRule}" ]]; then
|
||||||
@@ -75,6 +77,18 @@ import_config() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_proxy() {
|
||||||
|
if [[ $proxy_url ]]; then
|
||||||
|
export http_proxy="${proxy_url}"
|
||||||
|
export https_proxy="${proxy_url}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
unset_proxy() {
|
||||||
|
unset http_proxy
|
||||||
|
unset https_proxy
|
||||||
|
}
|
||||||
|
|
||||||
make_dir() {
|
make_dir() {
|
||||||
local dir=$1
|
local dir=$1
|
||||||
if [[ ! -d $dir ]]; then
|
if [[ ! -d $dir ]]; then
|
||||||
@@ -210,6 +224,19 @@ fix_config() {
|
|||||||
cat /dev/null > /etc/nginx/conf.d/default.conf
|
cat /dev/null > /etc/nginx/conf.d/default.conf
|
||||||
echo
|
echo
|
||||||
fi
|
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() {
|
npm_install_sub() {
|
||||||
@@ -272,7 +299,11 @@ git_clone_scripts() {
|
|||||||
local branch=$3
|
local branch=$3
|
||||||
[[ $branch ]] && local part_cmd="-b $branch "
|
[[ $branch ]] && local part_cmd="-b $branch "
|
||||||
echo -e "开始克隆仓库 $url 到 $dir\n"
|
echo -e "开始克隆仓库 $url 到 $dir\n"
|
||||||
|
|
||||||
|
set_proxy
|
||||||
git clone $part_cmd $url $dir
|
git clone $part_cmd $url $dir
|
||||||
|
unset_proxy
|
||||||
|
|
||||||
exit_status=$?
|
exit_status=$?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,10 +314,14 @@ git_pull_scripts() {
|
|||||||
[[ $branch ]] && local part_cmd="origin/${branch}"
|
[[ $branch ]] && local part_cmd="origin/${branch}"
|
||||||
cd $dir_work
|
cd $dir_work
|
||||||
echo -e "开始更新仓库:$dir_work\n"
|
echo -e "开始更新仓库:$dir_work\n"
|
||||||
|
|
||||||
|
set_proxy
|
||||||
git fetch --all
|
git fetch --all
|
||||||
exit_status=$?
|
exit_status=$?
|
||||||
git reset --hard $part_cmd
|
git reset --hard $part_cmd
|
||||||
git pull
|
git pull
|
||||||
|
unset_proxy
|
||||||
|
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-19
@@ -10,7 +10,7 @@ define_program() {
|
|||||||
local first_param=$1
|
local first_param=$1
|
||||||
if [[ $first_param == *.js ]]; then
|
if [[ $first_param == *.js ]]; then
|
||||||
which_program="node"
|
which_program="node"
|
||||||
elif [[ $first_param == *.py ]]; then
|
elif [[ $first_param == *.py ]] || [[ $first_param == *.pyc ]]; then
|
||||||
which_program="python3"
|
which_program="python3"
|
||||||
elif [[ $first_param == *.sh ]]; then
|
elif [[ $first_param == *.sh ]]; then
|
||||||
which_program="bash"
|
which_program="bash"
|
||||||
@@ -83,7 +83,7 @@ run_normal() {
|
|||||||
random_delay
|
random_delay
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||||
log_dir_tmp="${first_param##*/}"
|
log_dir_tmp="${first_param##*/}"
|
||||||
log_dir_tmp_path="${first_param%%/*}"
|
log_dir_tmp_path="${first_param%%/*}"
|
||||||
@@ -96,7 +96,7 @@ run_normal() {
|
|||||||
make_dir "$log_dir"
|
make_dir "$log_dir"
|
||||||
|
|
||||||
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s" -d "$begin_time")
|
local begin_timestamp=$(date "+%s")
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ run_normal() {
|
|||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
eval . $file_task_after "$@" $cmd
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local end_timestamp=$(date "+%s" -d "$end_time")
|
local end_timestamp=$(date "+%s")
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||||
@@ -141,7 +141,7 @@ run_concurrent() {
|
|||||||
array_run[n]=${array[$i - 1]}
|
array_run[n]=${array[$i - 1]}
|
||||||
let n++
|
let n++
|
||||||
done
|
done
|
||||||
|
|
||||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||||
|
|
||||||
define_program "$first_param"
|
define_program "$first_param"
|
||||||
@@ -157,7 +157,7 @@ run_concurrent() {
|
|||||||
make_dir $log_dir
|
make_dir $log_dir
|
||||||
|
|
||||||
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s" -d "$begin_time")
|
local begin_timestamp=$(date "+%s")
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||||
@@ -193,7 +193,7 @@ run_concurrent() {
|
|||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
eval . $file_task_after "$@" $cmd
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local end_timestamp=$(date "+%s" -d "$end_time")
|
local end_timestamp=$(date "+%s")
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||||
@@ -221,7 +221,7 @@ run_designated() {
|
|||||||
make_dir $log_dir
|
make_dir $log_dir
|
||||||
|
|
||||||
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s" -d "$begin_time")
|
local begin_timestamp=$(date "+%s")
|
||||||
|
|
||||||
local envs=$(eval echo "\$${env_param}")
|
local envs=$(eval echo "\$${env_param}")
|
||||||
local array=($(echo $envs | sed 's/&/ /g'))
|
local array=($(echo $envs | sed 's/&/ /g'))
|
||||||
@@ -234,7 +234,7 @@ run_designated() {
|
|||||||
array_run[n]=${array[$i - 1]}
|
array_run[n]=${array[$i - 1]}
|
||||||
let n++
|
let n++
|
||||||
done
|
done
|
||||||
|
|
||||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
@@ -256,7 +256,7 @@ run_designated() {
|
|||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
eval . $file_task_after "$@" $cmd
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local end_timestamp=$(date "+%s" -d "$end_time")
|
local end_timestamp=$(date "+%s")
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||||
@@ -264,29 +264,42 @@ run_designated() {
|
|||||||
|
|
||||||
## 运行其他命令
|
## 运行其他命令
|
||||||
run_else() {
|
run_else() {
|
||||||
local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
local file_param="$1"
|
||||||
local log_dir_tmp="${1##*/}"
|
define_program "$file_param"
|
||||||
local log_dir="$dir_log/${log_dir_tmp%%.*}"
|
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"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $log_path"
|
cmd="&>> $log_path"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
make_dir "$log_dir"
|
make_dir $log_dir
|
||||||
|
|
||||||
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s" -d "$begin_time")
|
local begin_timestamp=$(date "+%s")
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $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"
|
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||||
eval . $file_task_before "$@" $cmd
|
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_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local end_timestamp=$(date "+%s" -d "$end_time")
|
local end_timestamp=$(date "+%s")
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||||
|
|||||||
+6
-12
@@ -151,11 +151,6 @@ update_repo() {
|
|||||||
make_dir "${dir_scripts}/${uniq_path}"
|
make_dir "${dir_scripts}/${uniq_path}"
|
||||||
|
|
||||||
local formatUrl="$url"
|
local formatUrl="$url"
|
||||||
if [[ $github_proxy_url ]]; then
|
|
||||||
if [[ $url =~ "github.com" ]] || [[ $url =~ "githubusercontent.com" ]]; then
|
|
||||||
[[ ! $url =~ "https://ghproxy.com" ]] && formatUrl="${github_proxy_url}${url}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ -d ${repo_path}/.git ]]; then
|
if [[ -d ${repo_path}/.git ]]; then
|
||||||
reset_romote_url ${repo_path} "${formatUrl}" "${branch}"
|
reset_romote_url ${repo_path} "${formatUrl}" "${branch}"
|
||||||
git_pull_scripts ${repo_path} "${branch}"
|
git_pull_scripts ${repo_path} "${branch}"
|
||||||
@@ -183,15 +178,14 @@ update_raw() {
|
|||||||
echo -e "--------------------------------------------------------------\n"
|
echo -e "--------------------------------------------------------------\n"
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local raw_url="$url"
|
local raw_url="$url"
|
||||||
if [[ $github_proxy_url ]]; then
|
|
||||||
if [[ $url =~ "github.com" ]] || [[ $url =~ "githubusercontent.com" ]]; then
|
|
||||||
[[ ! $url =~ "https://ghproxy.com" ]] && raw_url="${github_proxy_url}${url}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
local suffix="${raw_url##*.}"
|
local suffix="${raw_url##*.}"
|
||||||
local raw_file_name="${uniq_path}.${suffix}"
|
local raw_file_name="${uniq_path}.${suffix}"
|
||||||
echo -e "开始下载:${raw_url} \n\n保存路径:$dir_raw/${raw_file_name}\n"
|
echo -e "开始下载:${raw_url} \n\n保存路径:$dir_raw/${raw_file_name}\n"
|
||||||
|
|
||||||
|
set_proxy
|
||||||
wget -q --no-check-certificate -O "$dir_raw/${raw_file_name}.new" ${raw_url}
|
wget -q --no-check-certificate -O "$dir_raw/${raw_file_name}.new" ${raw_url}
|
||||||
|
unset_proxy
|
||||||
|
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}"
|
mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}"
|
||||||
echo -e "下载 ${raw_file_name} 成功...\n"
|
echo -e "下载 ${raw_file_name} 成功...\n"
|
||||||
@@ -259,7 +253,7 @@ update_qinglong() {
|
|||||||
|
|
||||||
local no_restart="$1"
|
local no_restart="$1"
|
||||||
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
||||||
reset_romote_url ${dir_root} "${github_proxy_url}https://github.com/whyour/qinglong.git" "master"
|
reset_romote_url ${dir_root} "https://github.com/whyour/qinglong.git" "master"
|
||||||
git_pull_scripts $dir_root "master"
|
git_pull_scripts $dir_root "master"
|
||||||
|
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
@@ -274,7 +268,7 @@ update_qinglong() {
|
|||||||
echo -e "\n更新$dir_root失败,请检查原因...\n"
|
echo -e "\n更新$dir_root失败,请检查原因...\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local url="${github_proxy_url}https://github.com/whyour/qinglong-static.git"
|
local url="https://github.com/whyour/qinglong-static.git"
|
||||||
if [[ -d ${ql_static_repo}/.git ]]; then
|
if [[ -d ${ql_static_repo}/.git ]]; then
|
||||||
reset_romote_url ${ql_static_repo} ${url} "master"
|
reset_romote_url ${ql_static_repo} ${url} "master"
|
||||||
git_pull_scripts ${ql_static_repo} "master"
|
git_pull_scripts ${ql_static_repo} "master"
|
||||||
|
|||||||
@@ -79,6 +79,5 @@ export default {
|
|||||||
fixSiderbar: true,
|
fixSiderbar: true,
|
||||||
contentWidth: 'Fixed',
|
contentWidth: 'Fixed',
|
||||||
splitMenus: false,
|
splitMenus: false,
|
||||||
logo: 'https://z3.ax1x.com/2021/11/18/I7MpAe.png',
|
|
||||||
siderWidth: 180,
|
siderWidth: 180,
|
||||||
} as any;
|
} as any;
|
||||||
|
|||||||
+20
-2
@@ -152,8 +152,8 @@
|
|||||||
|
|
||||||
.ant-layout-content.ant-pro-basicLayout-content.ant-pro-basicLayout-has-header {
|
.ant-layout-content.ant-pro-basicLayout-content.ant-pro-basicLayout-has-header {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
min-height: calc(100vh - 66px);
|
min-height: calc(100vh - 72px);
|
||||||
min-height: calc(100vh - var(--vh-offset, 0px) - 66px);
|
min-height: calc(100vh - var(--vh-offset, 0px) - 72px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Resizer {
|
.Resizer {
|
||||||
@@ -293,3 +293,21 @@
|
|||||||
color: #1890ff;
|
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 vhCheck from 'vh-check';
|
||||||
import { version, changeLogLink, changeLog } from '../version';
|
import { version, changeLogLink, changeLog } from '../version';
|
||||||
import { useCtx, useTheme } from '@/utils/hooks';
|
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
|
// @ts-ignore
|
||||||
import SockJS from 'sockjs-client';
|
import SockJS from 'sockjs-client';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Integrations } from '@sentry/tracing';
|
import { Integrations } from '@sentry/tracing';
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://ea2fede373244db99c536210b910d9da@o1051273.ingest.sentry.io/6047851',
|
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
||||||
integrations: [new Integrations.BrowserTracing()],
|
integrations: [new Integrations.BrowserTracing()],
|
||||||
release: version,
|
release: version,
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
@@ -44,7 +44,7 @@ export default function (props: any) {
|
|||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
request.post(`${config.apiPrefix}logout`).then(() => {
|
request.post(`${config.apiPrefix}user/logout`).then(() => {
|
||||||
localStorage.removeItem(config.authKey);
|
localStorage.removeItem(config.authKey);
|
||||||
history.push('/login');
|
history.push('/login');
|
||||||
});
|
});
|
||||||
@@ -232,6 +232,12 @@ export default function (props: any) {
|
|||||||
selectedKeys={[props.location.pathname]}
|
selectedKeys={[props.location.pathname]}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
ErrorBoundary={Sentry.ErrorBoundary}
|
ErrorBoundary={Sentry.ErrorBoundary}
|
||||||
|
logo={
|
||||||
|
<Image
|
||||||
|
preview={false}
|
||||||
|
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||||
|
/>
|
||||||
|
}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<span style={{ fontSize: 16 }}>控制面板</span>
|
<span style={{ fontSize: 16 }}>控制面板</span>
|
||||||
@@ -275,14 +281,16 @@ export default function (props: any) {
|
|||||||
}}
|
}}
|
||||||
onCollapse={setCollapsed}
|
onCollapse={setCollapsed}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
rightContentRender={() => (
|
rightContentRender={() =>
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
ctx.isPhone && (
|
||||||
<span className="side-menu-user-wrapper">
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
<span className="side-menu-user-wrapper">
|
||||||
<span style={{ marginLeft: 5 }}>admin</span>
|
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
||||||
</span>
|
<span style={{ marginLeft: 5 }}>admin</span>
|
||||||
</Dropdown>
|
</span>
|
||||||
)}
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
collapsedButtonRender={(collapsed) => (
|
collapsedButtonRender={(collapsed) => (
|
||||||
<span
|
<span
|
||||||
className="side-menu-container"
|
className="side-menu-container"
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
.ant-table-pagination.ant-pagination {
|
.ant-table-pagination.ant-pagination {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
|
||||||
td:nth-child(2) {
|
|
||||||
span {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+74
-32
@@ -11,6 +11,7 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
Typography,
|
Typography,
|
||||||
Input,
|
Input,
|
||||||
|
Popover,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
@@ -30,7 +31,7 @@ import {
|
|||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import CronModal from './modal';
|
import CronModal,{ CronLabelModal } from './modal';
|
||||||
import CronLogModal from './logModal';
|
import CronLogModal from './logModal';
|
||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
import { diffTime } from '@/utils/date';
|
import { diffTime } from '@/utils/date';
|
||||||
@@ -77,20 +78,39 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
width: 150,
|
width: 150,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render: (text: string, record: any) => (
|
render: (text: string, record: any) => (
|
||||||
<span
|
<>
|
||||||
onClick={() => {
|
<a
|
||||||
goToScriptManager(record);
|
onClick={() => {
|
||||||
}}
|
goToScriptManager(record);
|
||||||
>
|
}}
|
||||||
{record.name || record._id}{' '}
|
>
|
||||||
{record.isPinned ? (
|
{record.name || record._id}{' '}
|
||||||
<span>
|
{record.isPinned ? (
|
||||||
<PushpinOutlined />
|
<span>
|
||||||
</span>
|
<PushpinOutlined />
|
||||||
) : (
|
</span>
|
||||||
''
|
) : (
|
||||||
)}
|
''
|
||||||
</span>
|
)}
|
||||||
|
</a>
|
||||||
|
<span>
|
||||||
|
{record.labels?.length > 0 && record.labels[0] !== '' ?
|
||||||
|
<Popover placement='right' trigger={isPhone ? 'click' : 'hover'}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
{record.labels?.map((label: string, i: number) => (
|
||||||
|
<Tag color="blue"
|
||||||
|
onClick={() => { onSearch(`label:${label}`) }}>
|
||||||
|
{label}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<Tag color="blue">{record.labels[0]}</Tag>
|
||||||
|
</Popover>
|
||||||
|
: ''}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a: any, b: any) => a.name.localeCompare(b.name),
|
compare: (a: any, b: any) => a.name.localeCompare(b.name),
|
||||||
@@ -325,6 +345,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
const [value, setValue] = useState<any[]>([]);
|
const [value, setValue] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [isLabelModalVisible, setisLabelModalVisible] = useState(false);
|
||||||
const [editedCron, setEditedCron] = useState();
|
const [editedCron, setEditedCron] = useState();
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||||
@@ -335,8 +356,12 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||||
|
|
||||||
const goToScriptManager = (record: any) => {
|
const goToScriptManager = (record: any) => {
|
||||||
const cmd = record.command.split(' ');
|
const cmd = record.command.split(' ') as string[];
|
||||||
if (cmd[0] === 'task') {
|
if (cmd[0] === 'task') {
|
||||||
|
if (cmd[1].startsWith('/ql/scripts')) {
|
||||||
|
cmd[1] = cmd[1].replace('/ql/scripts/', '');
|
||||||
|
}
|
||||||
|
|
||||||
let [p, s] = cmd[1].split('/');
|
let [p, s] = cmd[1].split('/');
|
||||||
if (!s) {
|
if (!s) {
|
||||||
s = p;
|
s = p;
|
||||||
@@ -404,12 +429,12 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}crons`, { data: [record._id] })
|
.delete(`${config.apiPrefix}crons`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
const i = result.findIndex((x) => x._id === record._id);
|
const i = result.findIndex((x) => x.id === record.id);
|
||||||
result.splice(i, 1);
|
result.splice(i, 1);
|
||||||
setValue(result);
|
setValue(result);
|
||||||
} else {
|
} else {
|
||||||
@@ -437,11 +462,11 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}crons/run`, { data: [record._id] })
|
.put(`${config.apiPrefix}crons/run`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
const i = result.findIndex((x) => x._id === record._id);
|
const i = result.findIndex((x) => x.id === record.id);
|
||||||
result.splice(i, 1, {
|
result.splice(i, 1, {
|
||||||
...record,
|
...record,
|
||||||
status: CrontabStatus.running,
|
status: CrontabStatus.running,
|
||||||
@@ -472,11 +497,11 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}crons/stop`, { data: [record._id] })
|
.put(`${config.apiPrefix}crons/stop`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
const i = result.findIndex((x) => x._id === record._id);
|
const i = result.findIndex((x) => x.id === record.id);
|
||||||
result.splice(i, 1, {
|
result.splice(i, 1, {
|
||||||
...record,
|
...record,
|
||||||
pid: null,
|
pid: null,
|
||||||
@@ -514,14 +539,14 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
record.isDisabled === 1 ? 'enable' : 'disable'
|
record.isDisabled === 1 ? 'enable' : 'disable'
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
data: [record._id],
|
data: [record.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
const newStatus = record.isDisabled === 1 ? 0 : 1;
|
const newStatus = record.isDisabled === 1 ? 0 : 1;
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
const i = result.findIndex((x) => x._id === record._id);
|
const i = result.findIndex((x) => x.id === record.id);
|
||||||
result.splice(i, 1, {
|
result.splice(i, 1, {
|
||||||
...record,
|
...record,
|
||||||
isDisabled: newStatus,
|
isDisabled: newStatus,
|
||||||
@@ -558,14 +583,14 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
record.isPinned === 1 ? 'unpin' : 'pin'
|
record.isPinned === 1 ? 'unpin' : 'pin'
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
data: [record._id],
|
data: [record.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
const newStatus = record.isPinned === 1 ? 0 : 1;
|
const newStatus = record.isPinned === 1 ? 0 : 1;
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
const i = result.findIndex((x) => x._id === record._id);
|
const i = result.findIndex((x) => x.id === record.id);
|
||||||
result.splice(i, 1, {
|
result.splice(i, 1, {
|
||||||
...record,
|
...record,
|
||||||
isPinned: newStatus,
|
isPinned: newStatus,
|
||||||
@@ -657,7 +682,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCrons = (cron: any) => {
|
const handleCrons = (cron: any) => {
|
||||||
const index = value.findIndex((x) => x._id === cron._id);
|
const index = value.findIndex((x) => x.id === cron.id);
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
cron.nextRunTime = cron_parser
|
cron.nextRunTime = cron_parser
|
||||||
.parseExpression(cron.schedule)
|
.parseExpression(cron.schedule)
|
||||||
@@ -675,9 +700,9 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
|
|
||||||
const getCronDetail = (cron: any) => {
|
const getCronDetail = (cron: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}crons/${cron._id}`)
|
.get(`${config.apiPrefix}crons/${cron.id}`)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
const index = value.findIndex((x) => x._id === cron._id);
|
const index = value.findIndex((x) => x.id === cron.id);
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
data.data.nextRunTime = cron_parser
|
data.data.nextRunTime = cron_parser
|
||||||
.parseExpression(data.data.schedule)
|
.parseExpression(data.data.schedule)
|
||||||
@@ -770,7 +795,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (logCron) {
|
if (logCron) {
|
||||||
localStorage.setItem('logCron', logCron._id);
|
localStorage.setItem('logCron', logCron.id);
|
||||||
setIsLogModalVisible(true);
|
setIsLogModalVisible(true);
|
||||||
}
|
}
|
||||||
}, [logCron]);
|
}, [logCron]);
|
||||||
@@ -799,7 +824,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
<Button key="2" type="primary" onClick={() => addCron()}>
|
<Button key="2" type="primary" onClick={() => addCron()}>
|
||||||
添加任务
|
新建任务
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
header={{
|
header={{
|
||||||
@@ -849,6 +874,13 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
>
|
>
|
||||||
批量取消置顶
|
批量取消置顶
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => setisLabelModalVisible(true)}
|
||||||
|
style={{ marginLeft: 8, marginRight: 8 }}
|
||||||
|
>
|
||||||
|
批量修改标签
|
||||||
|
</Button>
|
||||||
<span style={{ marginLeft: 8 }}>
|
<span style={{ marginLeft: 8 }}>
|
||||||
已选择
|
已选择
|
||||||
<a>{selectedRowIds?.length}</a>项
|
<a>{selectedRowIds?.length}</a>项
|
||||||
@@ -869,7 +901,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
pageSizeOptions: [10, 20, 50, 100, 200, 500, 1000],
|
pageSizeOptions: [10, 20, 50, 100, 200, 500, 1000],
|
||||||
}}
|
}}
|
||||||
dataSource={value}
|
dataSource={value}
|
||||||
rowKey="_id"
|
rowKey="id"
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ x: 1000, y: tableScrollHeight }}
|
scroll={{ x: 1000, y: tableScrollHeight }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -889,6 +921,16 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
cron={editedCron}
|
cron={editedCron}
|
||||||
/>
|
/>
|
||||||
|
<CronLabelModal
|
||||||
|
visible={isLabelModalVisible}
|
||||||
|
handleCancel={(needUpdate?: boolean) => {
|
||||||
|
setisLabelModalVisible(false);
|
||||||
|
if (needUpdate) {
|
||||||
|
getCrons();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ids={selectedRowIds}
|
||||||
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ const CronLogModal = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
}
|
}
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}crons/${cron._id}/log`)
|
.get(`${config.apiPrefix}crons/${cron.id}/log`)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (localStorage.getItem('logCron') === cron._id) {
|
if (localStorage.getItem('logCron') === String(cron.id)) {
|
||||||
const log = data.data as string;
|
const log = data.data as string;
|
||||||
setValue(log || '暂无日志');
|
setValue(log || '暂无日志');
|
||||||
setExecuting(
|
setExecuting(
|
||||||
@@ -61,7 +61,7 @@ const CronLogModal = ({
|
|||||||
<Countdown
|
<Countdown
|
||||||
className="inline-countdown"
|
className="inline-countdown"
|
||||||
format="ss"
|
format="ss"
|
||||||
value={Date.now() + 1000 * 10}
|
value={Date.now() + 1000 * 30}
|
||||||
/>
|
/>
|
||||||
秒后自动刷新
|
秒后自动刷新
|
||||||
</span>
|
</span>
|
||||||
@@ -70,7 +70,7 @@ const CronLogModal = ({
|
|||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 10000);
|
}, 30000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+99
-13
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Modal, message, Input, Form } from 'antd';
|
import { Modal, message, Input, Form, Button } from 'antd';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import cronParse from 'cron-parser';
|
import cronParse from 'cron-parser';
|
||||||
@@ -20,19 +20,28 @@ const CronModal = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const method = cron ? 'put' : 'post';
|
const method = cron ? 'put' : 'post';
|
||||||
const payload = { ...values };
|
const payload = { ...values };
|
||||||
|
if (typeof payload.labels === 'string') {
|
||||||
|
payload.labels = values.labels.split(/,|,/);
|
||||||
|
} else if (!payload.labels) {
|
||||||
|
payload.labels = [];
|
||||||
|
}
|
||||||
if (cron) {
|
if (cron) {
|
||||||
payload._id = cron._id;
|
payload.id = cron.id;
|
||||||
}
|
}
|
||||||
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
|
try {
|
||||||
data: payload,
|
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
|
||||||
});
|
data: payload,
|
||||||
if (code === 200) {
|
});
|
||||||
message.success(cron ? '更新Cron成功' : '添加Cron成功');
|
if (code === 200) {
|
||||||
} else {
|
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
||||||
message.error(data);
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
handleCancel(data);
|
||||||
|
} catch (error: any) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel(data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -41,7 +50,7 @@ const CronModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={cron ? '编辑定时' : '新建定时'}
|
title={cron ? '编辑任务' : '新建任务'}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
forceRender
|
forceRender
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
@@ -95,9 +104,86 @@ const CronModal = ({
|
|||||||
>
|
>
|
||||||
<Input placeholder="秒(可选) 分 时 天 月 周" />
|
<Input placeholder="秒(可选) 分 时 天 月 周" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item name="labels" label="标签">
|
||||||
|
<Input placeholder="请输入任务标签" />
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CronModal;
|
const CronLabelModal = ({
|
||||||
|
ids,
|
||||||
|
handleCancel,
|
||||||
|
visible,
|
||||||
|
}: {
|
||||||
|
ids: Array<string>;
|
||||||
|
visible: boolean;
|
||||||
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const update = async (action: 'delete' | 'post') => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then(async (values) => {
|
||||||
|
if (typeof values.labels === 'string') {
|
||||||
|
values.labels = values.labels.split(/,|,/);
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
const payload = { ids, labels: values.labels };
|
||||||
|
const { code, data } = await request[action](
|
||||||
|
`${config.apiPrefix}crons/labels`,
|
||||||
|
{
|
||||||
|
data: payload,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(
|
||||||
|
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
handleCancel(true);
|
||||||
|
})
|
||||||
|
.catch((info) => {
|
||||||
|
console.log('Validate Failed:', info);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.resetFields();
|
||||||
|
}, [ids, visible]);
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
<Button onClick={() => handleCancel(false)}>取消</Button>,
|
||||||
|
<Button type="primary" danger onClick={() => update('delete')}>
|
||||||
|
删除
|
||||||
|
</Button>,
|
||||||
|
<Button type="primary" onClick={() => update('post')}>
|
||||||
|
添加
|
||||||
|
</Button>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="批量修改标签"
|
||||||
|
visible={visible}
|
||||||
|
footer={buttons}
|
||||||
|
forceRender
|
||||||
|
onCancel={() => handleCancel(false)}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical" name="form_in_label_modal">
|
||||||
|
<Form.Item name="labels" label="标签">
|
||||||
|
<Input placeholder="请输入任务标签" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CronModal as default, CronLabelModal };
|
||||||
|
|||||||
@@ -82,13 +82,34 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
dataIndex: 'remark',
|
||||||
|
key: 'remark',
|
||||||
|
align: 'center' as const,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
key: 'created',
|
key: 'timestamp',
|
||||||
dataIndex: 'created',
|
dataIndex: 'timestamp',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
return <span>{new Date(record.created).toLocaleString()}</span>;
|
const language = navigator.language || navigator.languages[0];
|
||||||
|
const time = record.createdAt || record.timestamp;
|
||||||
|
const date = new Date(time)
|
||||||
|
.toLocaleString(language, {
|
||||||
|
hour12: false,
|
||||||
|
})
|
||||||
|
.replace(' 24:', ' 00:');
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
placement="topLeft"
|
||||||
|
title={date}
|
||||||
|
trigger={['hover', 'click']}
|
||||||
|
>
|
||||||
|
<span>{date}</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -175,7 +196,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}dependencies`, { data: [record._id] })
|
.delete(`${config.apiPrefix}dependencies`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
handleDependence(data.data[0]);
|
handleDependence(data.data[0]);
|
||||||
@@ -205,7 +226,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||||
data: [record._id],
|
data: [record.id],
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
@@ -231,7 +252,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
if (Array.isArray(dependence)) {
|
if (Array.isArray(dependence)) {
|
||||||
result.push(...dependence);
|
result.push(...dependence);
|
||||||
} else {
|
} else {
|
||||||
const index = value.findIndex((x) => x._id === dependence._id);
|
const index = value.findIndex((x) => x.id === dependence.id);
|
||||||
result.splice(index, 1, {
|
result.splice(index, 1, {
|
||||||
...dependence,
|
...dependence,
|
||||||
});
|
});
|
||||||
@@ -278,9 +299,9 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
|
|
||||||
const getDependenceDetail = (dependence: any) => {
|
const getDependenceDetail = (dependence: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}dependencies/${dependence._id}`)
|
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
const index = value.findIndex((x) => x._id === dependence._id);
|
const index = value.findIndex((x) => x.id === dependence.id);
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
result.splice(index, 1, {
|
result.splice(index, 1, {
|
||||||
...dependence,
|
...dependence,
|
||||||
@@ -307,7 +328,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (logDependence) {
|
if (logDependence) {
|
||||||
localStorage.setItem('logDependence', logDependence._id);
|
localStorage.setItem('logDependence', logDependence.id);
|
||||||
setIsLogModalVisible(true);
|
setIsLogModalVisible(true);
|
||||||
}
|
}
|
||||||
}, [logDependence]);
|
}, [logDependence]);
|
||||||
@@ -328,7 +349,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
}
|
}
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
for (let i = 0; i < references.length; i++) {
|
for (let i = 0; i < references.length; i++) {
|
||||||
const index = value.findIndex((x) => x._id === references[i]);
|
const index = value.findIndex((x) => x.id === references[i]);
|
||||||
result.splice(index, 1, {
|
result.splice(index, 1, {
|
||||||
...result[index],
|
...result[index],
|
||||||
status,
|
status,
|
||||||
@@ -340,7 +361,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const _result = [...value];
|
const _result = [...value];
|
||||||
for (let i = 0; i < references.length; i++) {
|
for (let i = 0; i < references.length; i++) {
|
||||||
const index = value.findIndex((x) => x._id === references[i]);
|
const index = value.findIndex((x) => x.id === references[i]);
|
||||||
_result.splice(index, 1);
|
_result.splice(index, 1);
|
||||||
}
|
}
|
||||||
setValue(_result);
|
setValue(_result);
|
||||||
@@ -372,7 +393,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
dataSource={value}
|
dataSource={value}
|
||||||
rowKey="_id"
|
rowKey="id"
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ x: 768, y: tableScrollHeight }}
|
scroll={{ x: 768, y: tableScrollHeight }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -398,7 +419,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
<Button key="2" type="primary" onClick={() => addDependence()}>
|
<Button key="2" type="primary" onClick={() => addDependence()}>
|
||||||
添加依赖
|
新建依赖
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
header={{
|
header={{
|
||||||
@@ -432,11 +453,11 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
handleCancel={(needRemove?: boolean) => {
|
handleCancel={(needRemove?: boolean) => {
|
||||||
setIsLogModalVisible(false);
|
setIsLogModalVisible(false);
|
||||||
if (needRemove) {
|
if (needRemove) {
|
||||||
const index = value.findIndex((x) => x._id === logDependence._id);
|
const index = value.findIndex((x) => x.id === logDependence.id);
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
result.splice(index, 1);
|
result.splice(index, 1);
|
||||||
setValue(result);
|
setValue(result);
|
||||||
} else if ([...value].map((x) => x._id).includes(logDependence._id)) {
|
} else if ([...value].map((x) => x.id).includes(logDependence.id)) {
|
||||||
getDependenceDetail(logDependence);
|
getDependenceDetail(logDependence);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ const DependenceLogModal = ({
|
|||||||
const getDependenceLog = () => {
|
const getDependenceLog = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}dependencies/${dependence._id}`)
|
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (localStorage.getItem('logDependence') === dependence._id) {
|
if (localStorage.getItem('logDependence') === String(dependence.id)) {
|
||||||
const log = (data.data.log || []).join('\n') as string;
|
const log = (data.data.log || []).join('') as string;
|
||||||
setValue(log);
|
setValue(log);
|
||||||
setExecuting(!log.includes('结束时间'));
|
setExecuting(!log.includes('结束时间'));
|
||||||
setIsRemoveFailed(log.includes('删除失败'));
|
setIsRemoveFailed(log.includes('删除失败'));
|
||||||
@@ -64,7 +64,7 @@ const DependenceLogModal = ({
|
|||||||
setRemoveLoading(true);
|
setRemoveLoading(true);
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}dependencies/force`, {
|
.delete(`${config.apiPrefix}dependencies/force`, {
|
||||||
data: [dependence._id],
|
data: [dependence.id],
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
cancel(true);
|
cancel(true);
|
||||||
@@ -99,7 +99,7 @@ const DependenceLogModal = ({
|
|||||||
setExecuting(false);
|
setExecuting(false);
|
||||||
setIsRemoveFailed(message.includes('删除失败'));
|
setIsRemoveFailed(message.includes('删除失败'));
|
||||||
}
|
}
|
||||||
setValue(`${value} \n ${message}`);
|
setValue(`${value}${message}`);
|
||||||
}, [socketMessage]);
|
}, [socketMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const DependenceModal = ({
|
|||||||
|
|
||||||
const handleOk = async (values: any) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { name, split, type } = values;
|
const { name, split, type, remark } = values;
|
||||||
const method = dependence ? 'put' : 'post';
|
const method = dependence ? 'put' : 'post';
|
||||||
let payload;
|
let payload;
|
||||||
if (!dependence) {
|
if (!dependence) {
|
||||||
@@ -36,13 +36,14 @@ const DependenceModal = ({
|
|||||||
return {
|
return {
|
||||||
name: x,
|
name: x,
|
||||||
type,
|
type,
|
||||||
|
remark,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
payload = [{ name, type }];
|
payload = [{ name, type, remark }];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payload = { ...values, _id: dependence._id };
|
payload = { ...values, id: dependence.id };
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { code, data } = await request[method](
|
const { code, data } = await request[method](
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.diff-switch-file {
|
.diff-switch-file {
|
||||||
|
min-width: 768px;
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+10
-9
@@ -101,7 +101,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
{
|
{
|
||||||
title: '序号',
|
title: '序号',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
width: 50,
|
width: 60,
|
||||||
render: (text: string, record: any, index: number) => {
|
render: (text: string, record: any, index: number) => {
|
||||||
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
|
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
|
||||||
},
|
},
|
||||||
@@ -151,7 +151,8 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
},
|
},
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
const language = navigator.language || navigator.languages[0];
|
const language = navigator.language || navigator.languages[0];
|
||||||
const date = new Date(text)
|
const time = record.updatedAt || record.timestamp;
|
||||||
|
const date = new Date(time)
|
||||||
.toLocaleString(language, {
|
.toLocaleString(language, {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
})
|
})
|
||||||
@@ -197,7 +198,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 100,
|
width: 120,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render: (text: string, record: any, index: number) => {
|
render: (text: string, record: any, index: number) => {
|
||||||
const isPc = !isPhone;
|
const isPc = !isPhone;
|
||||||
@@ -270,7 +271,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
record.status === Status.已禁用 ? 'enable' : 'disable'
|
record.status === Status.已禁用 ? 'enable' : 'disable'
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
data: [record._id],
|
data: [record.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
@@ -321,7 +322,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}envs`, { data: [record._id] })
|
.delete(`${config.apiPrefix}envs`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
@@ -351,7 +352,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const handleEnv = (env: any) => {
|
const handleEnv = (env: any) => {
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
const index = value.findIndex((x) => x._id === env._id);
|
const index = value.findIndex((x) => x.id === env.id);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
env = Array.isArray(env) ? env : [env];
|
env = Array.isArray(env) ? env : [env];
|
||||||
result.push(...env);
|
result.push(...env);
|
||||||
@@ -376,7 +377,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
}
|
}
|
||||||
const dragRow = value[dragIndex];
|
const dragRow = value[dragIndex];
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}envs/${dragRow._id}/move`, {
|
.put(`${config.apiPrefix}envs/${dragRow.id}/move`, {
|
||||||
data: { fromIndex: dragIndex, toIndex: hoverIndex },
|
data: { fromIndex: dragIndex, toIndex: hoverIndex },
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
@@ -485,7 +486,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
<Button key="2" type="primary" onClick={() => addEnv()}>
|
<Button key="2" type="primary" onClick={() => addEnv()}>
|
||||||
添加变量
|
新建变量
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
header={{
|
header={{
|
||||||
@@ -534,7 +535,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
dataSource={value}
|
dataSource={value}
|
||||||
rowKey="_id"
|
rowKey="id"
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ x: 1000, y: tableScrollHeight }}
|
scroll={{ x: 1000, y: tableScrollHeight }}
|
||||||
components={components}
|
components={components}
|
||||||
|
|||||||
Vendored
+15
-11
@@ -34,18 +34,22 @@ const EnvModal = ({
|
|||||||
payload = [{ value, name, remarks }];
|
payload = [{ value, name, remarks }];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payload = { ...values, _id: env._id };
|
payload = { ...values, id: env.id };
|
||||||
}
|
}
|
||||||
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
try {
|
||||||
data: payload,
|
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
||||||
});
|
data: payload,
|
||||||
if (code === 200) {
|
});
|
||||||
message.success(env ? '更新变量成功' : '添加变量成功');
|
if (code === 200) {
|
||||||
} else {
|
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||||
message.error(data);
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
handleCancel(data);
|
||||||
|
} catch (error: any) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel(data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -77,7 +81,7 @@ const EnvModal = ({
|
|||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入环境变量名称', whitespace: true },
|
{ required: true, message: '请输入环境变量名称', whitespace: true },
|
||||||
{
|
{
|
||||||
pattern: /^[a-zA-Z_][0-9a-zA-Z_]+$/,
|
pattern: /^[a-zA-Z_][0-9a-zA-Z_]*$/,
|
||||||
message: '只能输入字母数字下划线,且不能以数字开头',
|
message: '只能输入字母数字下划线,且不能以数字开头',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const Initialization = () => {
|
|||||||
const submitAccountSetting = (values: any) => {
|
const submitAccountSetting = (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}init/user`, {
|
.put(`${config.apiPrefix}user/init`, {
|
||||||
data: {
|
data: {
|
||||||
username: values.username,
|
username: values.username,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
@@ -52,8 +52,9 @@ const Initialization = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitNotification = (values: any) => {
|
const submitNotification = (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}init/notification`, {
|
.put(`${config.apiPrefix}user/notification/init`, {
|
||||||
data: {
|
data: {
|
||||||
...values,
|
...values,
|
||||||
},
|
},
|
||||||
@@ -73,6 +74,10 @@ const Initialization = () => {
|
|||||||
setFields(_fields || []);
|
setFields(_fields || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.removeItem(config.authKey);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: '欢迎使用',
|
title: '欢迎使用',
|
||||||
@@ -228,7 +233,7 @@ const Initialization = () => {
|
|||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
className={styles.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>
|
<span className={styles.title}>初始化配置</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+34
-17
@@ -1,12 +1,11 @@
|
|||||||
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
||||||
import { TreeSelect, Tree, Input } from 'antd';
|
import { TreeSelect, Tree, Input, Empty } from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||||
import { useCtx, useTheme } from '@/utils/hooks';
|
|
||||||
import SplitPane from 'react-split-pane';
|
import SplitPane from 'react-split-pane';
|
||||||
|
|
||||||
function getFilterData(keyword: string, data: any) {
|
function getFilterData(keyword: string, data: any) {
|
||||||
@@ -143,21 +142,39 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
{!isPhone && (
|
{!isPhone && (
|
||||||
<SplitPane split="vertical" size={200} maxSize={-100}>
|
<SplitPane split="vertical" size={200} maxSize={-100}>
|
||||||
<div className={styles['left-tree-container']}>
|
<div className={styles['left-tree-container']}>
|
||||||
<Input.Search
|
{data.length > 0 ? (
|
||||||
className={styles['left-tree-search']}
|
<>
|
||||||
onChange={onSearch}
|
<Input.Search
|
||||||
></Input.Search>
|
className={styles['left-tree-search']}
|
||||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
onChange={onSearch}
|
||||||
<Tree
|
></Input.Search>
|
||||||
className={styles['left-tree']}
|
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||||
treeData={filterData}
|
<Tree
|
||||||
showIcon={true}
|
className={styles['left-tree']}
|
||||||
height={height}
|
treeData={filterData}
|
||||||
selectedKeys={[select]}
|
showIcon={true}
|
||||||
showLine={{ showLeafIcon: true }}
|
height={height}
|
||||||
onSelect={onTreeSelect}
|
selectedKeys={[select]}
|
||||||
></Tree>
|
showLine={{ showLeafIcon: true }}
|
||||||
</div>
|
onSelect={onTreeSelect}
|
||||||
|
></Tree>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Empty
|
||||||
|
description="暂无日志"
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Editor
|
<Editor
|
||||||
language="shell"
|
language="shell"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const Login = () => {
|
|||||||
setTwoFactor(false);
|
setTwoFactor(false);
|
||||||
setWaitTime(null);
|
setWaitTime(null);
|
||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}login`, {
|
.post(`${config.apiPrefix}user/login`, {
|
||||||
data: {
|
data: {
|
||||||
username: values.username,
|
username: values.username,
|
||||||
password: values.password,
|
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(() => {
|
useEffect(() => {
|
||||||
const isAuth = localStorage.getItem(config.authKey);
|
const isAuth = localStorage.getItem(config.authKey);
|
||||||
if (isAuth) {
|
if (isAuth) {
|
||||||
@@ -127,7 +135,7 @@ const Login = () => {
|
|||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
className={styles.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 className={styles.title}>
|
||||||
{twoFactor ? '两步验证' : config.siteName}
|
{twoFactor ? '两步验证' : config.siteName}
|
||||||
@@ -144,12 +152,15 @@ const Login = () => {
|
|||||||
{
|
{
|
||||||
pattern: /^[0-9]{6}$/,
|
pattern: /^[0-9]{6}$/,
|
||||||
message: '验证码为6位数字',
|
message: '验证码为6位数字',
|
||||||
validateTrigger: 'onBlur',
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
hasFeedback
|
|
||||||
>
|
>
|
||||||
<Input placeholder="6位数字" autoFocus autoComplete="off" />
|
<Input
|
||||||
|
placeholder="6位数字"
|
||||||
|
onChange={codeInputChange}
|
||||||
|
autoFocus
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
@@ -24,22 +24,22 @@ const prefixMap: any = {
|
|||||||
|
|
||||||
const EditModal = ({
|
const EditModal = ({
|
||||||
treeData,
|
treeData,
|
||||||
currentFile,
|
currentNode,
|
||||||
content,
|
content,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
visible,
|
||||||
socketMessage,
|
socketMessage,
|
||||||
}: {
|
}: {
|
||||||
treeData?: any;
|
treeData?: any;
|
||||||
currentFile?: string;
|
|
||||||
content?: string;
|
content?: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
socketMessage: any;
|
socketMessage: any;
|
||||||
|
currentNode: any;
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [language, setLanguage] = useState<string>('javascript');
|
const [language, setLanguage] = useState<string>('javascript');
|
||||||
const [fileName, setFileName] = useState<string>('');
|
const [cNode, setCNode] = useState<any>();
|
||||||
const [selectedKey, setSelectedKey] = useState<string>('');
|
const [selectedKey, setSelectedKey] = useState<string>('');
|
||||||
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
||||||
const [settingModalVisible, setSettingModalVisible] =
|
const [settingModalVisible, setSettingModalVisible] =
|
||||||
@@ -47,37 +47,59 @@ const EditModal = ({
|
|||||||
const [log, setLog] = useState<string>('');
|
const [log, setLog] = useState<string>('');
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
handleCancel();
|
handleCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
if (node.value === fileName || !value) {
|
if (node.key === selectedKey || !value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newMode = LangMap[value.slice(-3)] || '';
|
const newMode = LangMap[value.slice(-3)] || '';
|
||||||
setFileName(value);
|
setCNode(node);
|
||||||
setLanguage(newMode);
|
setLanguage(newMode);
|
||||||
getDetail(node);
|
getDetail(node);
|
||||||
setSelectedKey(node.key);
|
setSelectedKey(node.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request.get(`${config.apiPrefix}scripts/${node.value}`).then((data) => {
|
request
|
||||||
setValue(data.data);
|
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
|
||||||
});
|
.then((data) => {
|
||||||
|
setValue(data.data);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const run = () => {
|
const run = () => {
|
||||||
|
setLog('');
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}scripts/run`, {
|
.put(`${config.apiPrefix}scripts/run`, {
|
||||||
data: {
|
data: {
|
||||||
filename: fileName,
|
filename: cNode.value,
|
||||||
path: '',
|
path: cNode.parent || '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => {});
|
.then((data) => {
|
||||||
|
setIsRunning(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
if (!cNode || !cNode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}scripts/stop`, {
|
||||||
|
data: {
|
||||||
|
filename: cNode.value,
|
||||||
|
path: cNode.parent || '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
setIsRunning(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -91,6 +113,13 @@ const EditModal = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_message === '执行结束') {
|
||||||
|
_message = '';
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsRunning(false);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
if (log) {
|
if (log) {
|
||||||
_message = `\n${_message}`;
|
_message = `\n${_message}`;
|
||||||
}
|
}
|
||||||
@@ -98,21 +127,21 @@ const EditModal = ({
|
|||||||
}, [socketMessage]);
|
}, [socketMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentFile) {
|
if (currentNode) {
|
||||||
setFileName(currentFile);
|
setCNode(currentNode);
|
||||||
setValue(content as string);
|
setValue(content as string);
|
||||||
setSelectedKey(currentFile);
|
setSelectedKey(currentNode.key);
|
||||||
}
|
}
|
||||||
}, [currentFile, content]);
|
}, [content, currentNode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
className="edit-modal"
|
className="edit-modal"
|
||||||
|
closable={false}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<span style={{ marginRight: 8 }}>{fileName}</span>
|
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
style={{ marginRight: 8, width: 120 }}
|
style={{ marginRight: 8, width: 150 }}
|
||||||
value={selectedKey}
|
value={selectedKey}
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={treeData}
|
treeData={treeData}
|
||||||
@@ -122,7 +151,7 @@ const EditModal = ({
|
|||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
value={language}
|
value={language}
|
||||||
style={{ width: 120, marginRight: 8 }}
|
style={{ width: 110, marginRight: 8 }}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setLanguage(e);
|
setLanguage(e);
|
||||||
}}
|
}}
|
||||||
@@ -132,8 +161,12 @@ const EditModal = ({
|
|||||||
<Option value="shell">shell</Option>
|
<Option value="shell">shell</Option>
|
||||||
<Option value="python">python</Option>
|
<Option value="python">python</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Button type="primary" style={{ marginRight: 8 }} onClick={run}>
|
<Button
|
||||||
运行
|
type="primary"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
onClick={isRunning ? stop : run}
|
||||||
|
>
|
||||||
|
{isRunning ? '停止' : '运行'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -162,6 +195,16 @@ const EditModal = ({
|
|||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
onClick={() => {
|
||||||
|
stop();
|
||||||
|
handleCancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
退出
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
@@ -169,7 +212,12 @@ const EditModal = ({
|
|||||||
onClose={cancel}
|
onClose={cancel}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
>
|
>
|
||||||
<SplitPane split="vertical" minSize={200} defaultSize="50%">
|
<SplitPane
|
||||||
|
split="vertical"
|
||||||
|
minSize={200}
|
||||||
|
defaultSize="50%"
|
||||||
|
style={{ height: 'calc(100vh - 55px)' }}
|
||||||
|
>
|
||||||
<Editor
|
<Editor
|
||||||
language={language}
|
language={language}
|
||||||
value={value}
|
value={value}
|
||||||
@@ -184,9 +232,7 @@ const EditModal = ({
|
|||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div>
|
<pre style={{ height: '100%', whiteSpace: 'break-spaces' }}>{log}</pre>
|
||||||
<pre>{log}</pre>
|
|
||||||
</div>
|
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
<SaveModal
|
<SaveModal
|
||||||
visible={saveModalVisible}
|
visible={saveModalVisible}
|
||||||
@@ -197,7 +243,7 @@ const EditModal = ({
|
|||||||
content:
|
content:
|
||||||
editorRef.current &&
|
editorRef.current &&
|
||||||
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
||||||
filename: fileName,
|
filename: cNode?.value,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SettingModal
|
<SettingModal
|
||||||
|
|||||||
+41
-21
@@ -10,6 +10,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Menu,
|
Menu,
|
||||||
|
Empty,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
@@ -112,16 +113,17 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
const initGetScript = () => {
|
const initGetScript = () => {
|
||||||
const { p, s } = history.location.query as any;
|
const { p, s } = history.location.query as any;
|
||||||
if (s) {
|
if (s) {
|
||||||
|
const vkey = `${p}/${s}`;
|
||||||
const obj = {
|
const obj = {
|
||||||
node: {
|
node: {
|
||||||
title: s,
|
title: s,
|
||||||
value: s,
|
value: s,
|
||||||
key: p ? `${p}-${s}` : s,
|
key: p ? vkey : s,
|
||||||
parent: p,
|
parent: p,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
setExpandedKeys([p]);
|
setExpandedKeys([p]);
|
||||||
onTreeSelect([`${p}-${s}`], obj);
|
onTreeSelect([vkey], obj);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -377,7 +379,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
) : (
|
) : (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="add" icon={<PlusOutlined />} onClick={addFile}>
|
<Menu.Item key="add" icon={<PlusOutlined />} onClick={addFile}>
|
||||||
添加
|
新建
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key="edit"
|
key="edit"
|
||||||
@@ -470,23 +472,41 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
{!isPhone && (
|
{!isPhone && (
|
||||||
<SplitPane split="vertical" size={200} maxSize={-100}>
|
<SplitPane split="vertical" size={200} maxSize={-100}>
|
||||||
<div className={styles['left-tree-container']}>
|
<div className={styles['left-tree-container']}>
|
||||||
<Input.Search
|
{data.length > 0 ? (
|
||||||
className={styles['left-tree-search']}
|
<>
|
||||||
onChange={onSearch}
|
<Input.Search
|
||||||
></Input.Search>
|
className={styles['left-tree-search']}
|
||||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
onChange={onSearch}
|
||||||
<Tree
|
></Input.Search>
|
||||||
className={styles['left-tree']}
|
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||||
treeData={filterData}
|
<Tree
|
||||||
showIcon={true}
|
className={styles['left-tree']}
|
||||||
height={height}
|
treeData={filterData}
|
||||||
selectedKeys={[select]}
|
showIcon={true}
|
||||||
expandedKeys={expandedKeys}
|
height={height}
|
||||||
onExpand={onExpand}
|
selectedKeys={[select]}
|
||||||
showLine={{ showLeafIcon: true }}
|
expandedKeys={expandedKeys}
|
||||||
onSelect={onTreeSelect}
|
onExpand={onExpand}
|
||||||
></Tree>
|
showLine={{ showLeafIcon: true }}
|
||||||
</div>
|
onSelect={onTreeSelect}
|
||||||
|
></Tree>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Empty
|
||||||
|
description="暂无脚本"
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Editor
|
<Editor
|
||||||
language={mode}
|
language={mode}
|
||||||
@@ -525,7 +545,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
<EditModal
|
<EditModal
|
||||||
visible={isLogModalVisible}
|
visible={isLogModalVisible}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
currentFile={select}
|
currentNode={currentNode}
|
||||||
content={value}
|
content={value}
|
||||||
socketMessage={socketMessage}
|
socketMessage={socketMessage}
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ const AppModal = ({
|
|||||||
const method = app ? 'put' : 'post';
|
const method = app ? 'put' : 'post';
|
||||||
const payload = { ...values };
|
const payload = { ...values };
|
||||||
if (app) {
|
if (app) {
|
||||||
payload._id = app._id;
|
payload.id = app.id;
|
||||||
}
|
}
|
||||||
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(app ? '更新应用成功' : '添加应用成功');
|
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
if (data.hasNewVersion) {
|
if (data.hasNewVersion) {
|
||||||
showConfirmUpdateModal(data);
|
showConfirmUpdateModal(data);
|
||||||
} else {
|
} else {
|
||||||
message.success('已经是最新版了!');
|
showForceUpdateModal();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
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 showConfirmUpdateModal = (data: any) => {
|
||||||
const { lastVersion, lastLog } = data;
|
const { lastVersion, lastLog } = data;
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
@@ -113,7 +139,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newMessage = `${value}\n${_message}`;
|
const newMessage = `${value}${_message}`;
|
||||||
modalRef.current.update({
|
modalRef.current.update({
|
||||||
content: (
|
content: (
|
||||||
<div style={{ height: '60vh', overflowY: 'auto' }}>
|
<div style={{ height: '60vh', overflowY: 'auto' }}>
|
||||||
@@ -146,7 +172,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
<Countdown
|
<Countdown
|
||||||
className="inline-countdown"
|
className="inline-countdown"
|
||||||
format="ss"
|
format="ss"
|
||||||
value={Date.now() + 1000 * 10}
|
value={Date.now() + 1000 * 30}
|
||||||
/>
|
/>
|
||||||
秒后自动刷新
|
秒后自动刷新
|
||||||
</span>
|
</span>
|
||||||
@@ -155,7 +181,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 10000);
|
}, 30000);
|
||||||
}
|
}
|
||||||
}, [socketMessage]);
|
}, [socketMessage]);
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ const Setting = ({
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}apps`, { data: [record._id] })
|
.delete(`${config.apiPrefix}apps`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
@@ -198,7 +198,7 @@ const Setting = ({
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}apps/${record._id}/reset-secret`)
|
.put(`${config.apiPrefix}apps/${record.id}/reset-secret`)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
message.success('重置成功');
|
message.success('重置成功');
|
||||||
@@ -222,7 +222,7 @@ const Setting = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleApp = (app: any) => {
|
const handleApp = (app: any) => {
|
||||||
const index = dataSource.findIndex((x) => x._id === app._id);
|
const index = dataSource.findIndex((x) => x.id === app.id);
|
||||||
const result = [...dataSource];
|
const result = [...dataSource];
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
result.push(app);
|
result.push(app);
|
||||||
@@ -319,7 +319,7 @@ const Setting = ({
|
|||||||
tabActiveKey === 'app'
|
tabActiveKey === 'app'
|
||||||
? [
|
? [
|
||||||
<Button key="2" type="primary" onClick={() => addApp()}>
|
<Button key="2" type="primary" onClick={() => addApp()}>
|
||||||
添加应用
|
新建应用
|
||||||
</Button>,
|
</Button>,
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
@@ -339,7 +339,7 @@ const Setting = ({
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
rowKey="_id"
|
rowKey="id"
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ x: 768 }}
|
scroll={{ x: 768 }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ const LoginLog = ({ data }: any) => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
rowKey="_id"
|
rowKey="id"
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ x: 768 }}
|
scroll={{ x: 768 }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ export default {
|
|||||||
name: '任务日志',
|
name: '任务日志',
|
||||||
value: 'logs',
|
value: 'logs',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '依赖管理',
|
||||||
|
value: 'dependencies',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '系统信息',
|
||||||
|
value: 'system',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
scopesMap: {
|
scopesMap: {
|
||||||
crons: '定时任务',
|
crons: '定时任务',
|
||||||
@@ -62,6 +70,8 @@ export default {
|
|||||||
configs: '配置文件',
|
configs: '配置文件',
|
||||||
scripts: '脚本管理',
|
scripts: '脚本管理',
|
||||||
logs: '任务日志',
|
logs: '任务日志',
|
||||||
|
dependencies: '依赖管理',
|
||||||
|
system: '系统信息',
|
||||||
},
|
},
|
||||||
notificationModes: [
|
notificationModes: [
|
||||||
{ value: 'gotify', label: 'Gotify' },
|
{ value: 'gotify', label: 'Gotify' },
|
||||||
|
|||||||
+3
-3
@@ -34,12 +34,12 @@ const errorHandler = function (error: any) {
|
|||||||
|
|
||||||
const _request = extend({ timeout: 60000, params: { t: time }, errorHandler });
|
const _request = extend({ timeout: 60000, params: { t: time }, errorHandler });
|
||||||
const apiWhiteList = [
|
const apiWhiteList = [
|
||||||
'/api/login',
|
'/api/user/login',
|
||||||
'/open/auth/token',
|
'/open/auth/token',
|
||||||
'/api/user/two-factor/login',
|
'/api/user/two-factor/login',
|
||||||
'/api/system',
|
'/api/system',
|
||||||
'/api/init/user',
|
'/api/user/init',
|
||||||
'/api/init/notification',
|
'/api/user/notification/init',
|
||||||
];
|
];
|
||||||
|
|
||||||
_request.interceptors.request.use((url, options) => {
|
_request.interceptors.request.use((url, options) => {
|
||||||
|
|||||||
+12
-9
@@ -1,10 +1,13 @@
|
|||||||
export const version = '2.10.9';
|
export const version = '2.11.1';
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/228';
|
export const changeLogLink = 'https://t.me/jiao_long/261';
|
||||||
export const changeLog = `2.10.9 版本说明
|
export const changeLog = `2.11.1 版本说明
|
||||||
1. 任务管理支持任务名跳转脚本管理页,感谢 https://github.com/kilo5hz PR
|
1. 修复openapi获取token,token认证
|
||||||
2. 系统通知支持gotify,感谢 https://github.com/kilo5hz PR
|
2. 修复环境变量搜索,排序,更新
|
||||||
3. 定时任务列表pageSize增加200/500/1000,感谢 https://github.com/fzls PR
|
3. 修复重置应用秘钥
|
||||||
4. 修复deps目录依赖文件拷贝
|
4. 修复环境变量更新时间显示
|
||||||
5. 修改alpine基础镜像版本,解决arm32位系统无法启动容器,感谢 https://github.com/lx200916
|
5. 修复定时默认任务排序
|
||||||
6. 修复调试功能
|
6. task支持运行pyc文件,感谢https://github.com/chiupam
|
||||||
|
6. 移除ghproxy默认代理,增加新配置ProxyUrl,支持http或者socks代理。例如 http://127.0.0.1:7890
|
||||||
|
7. 更换JavaScript cdn地址
|
||||||
|
8. 其他bug修复
|
||||||
`;
|
`;
|
||||||
|
|||||||
+1
-7
@@ -27,13 +27,7 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noEmit": false
|
"noEmit": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*", ".umirc.ts", "typings.d.ts", "back/**/*"],
|
||||||
"src/**/*",
|
|
||||||
"config/**/*",
|
|
||||||
".umirc.ts",
|
|
||||||
"typings.d.ts",
|
|
||||||
"back/**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"lib",
|
"lib",
|
||||||
|
|||||||
Reference in New Issue
Block a user