mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 19:05:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b31b054d0c | ||
|
|
fb4a87f5ce | ||
|
|
049c73880b | ||
|
|
7b2c54f6a6 | ||
|
|
23bda39812 | ||
|
|
90af5801ee | ||
|
|
543c241bc6 | ||
|
|
19a58a3510 | ||
|
|
73c6fd18ed | ||
|
|
409297c6aa | ||
|
|
4272b9ea35 | ||
|
|
3c5c5b0bbc | ||
|
|
802b2d722e | ||
|
|
8f90d3f8ff | ||
|
|
f93cbbf508 | ||
|
|
2ba8756bd3 | ||
|
|
8013de56c9 | ||
|
|
4af9b22442 | ||
|
|
8a392170cf | ||
|
|
6385e053ae | ||
|
|
2789119882 | ||
|
|
3e0553c3e8 | ||
|
|
5e16ace1e7 | ||
|
|
0ca23cc4ca | ||
|
|
4949457001 | ||
|
|
8ae13fe823 | ||
|
|
fdf672092a | ||
|
|
48dd6ea826 | ||
|
|
68bb4f15e2 | ||
|
|
9c6d9ec22d | ||
|
|
ac075ace73 | ||
|
|
eb58774ee9 | ||
|
|
04613bf9a9 | ||
|
|
4194f1964d | ||
|
|
42c64c82d7 | ||
|
|
d44a8966f2 | ||
|
|
ff44330aa1 | ||
|
|
ed259e8168 | ||
|
|
444836975d | ||
|
|
3756fa93e4 | ||
|
|
064b52b306 | ||
|
|
648dc51474 | ||
|
|
7c0bc32759 | ||
|
|
d4b6cc5e4d | ||
|
|
75fca715e3 | ||
|
|
6e9a4a29ef | ||
|
|
78f643c50f | ||
|
|
2db4f1ba87 | ||
|
|
5741dbb78c | ||
|
|
88cf671465 | ||
|
|
29f082dea2 | ||
|
|
9a55968ed6 | ||
|
|
6ec6e25855 | ||
|
|
af48bc378b | ||
|
|
3f5ae4bcb9 | ||
|
|
c6624e9001 | ||
|
|
5530ce76e3 | ||
|
|
654b51e476 | ||
|
|
6428ac3624 | ||
|
|
c36450f436 | ||
|
|
dcea231249 | ||
|
|
f236119bd6 | ||
|
|
cb870fad85 | ||
|
|
44e2caedfc | ||
|
|
c0b5192296 | ||
|
|
ba43882c0b | ||
|
|
dfc706e16d | ||
|
|
25b03d4345 | ||
|
|
4e8f36d9a4 | ||
|
|
8f4d67ffa7 | ||
|
|
ad47277149 | ||
|
|
98c2a37ab6 | ||
|
|
f2fea47336 | ||
|
|
e274d3e2f9 | ||
|
|
36f4c3c02c | ||
|
|
ae891f8e55 | ||
|
|
c874dc9705 | ||
|
|
23bfbeb995 | ||
|
|
a75df03243 | ||
|
|
e44a2b5995 | ||
|
|
f23e7a9e49 | ||
|
|
e08f0cd308 | ||
|
|
1ebcfd8001 | ||
|
|
5168b044a3 | ||
|
|
931638b687 | ||
|
|
4d9bbcfd3f | ||
|
|
b0e3b26369 | ||
|
|
1e55f4065d | ||
|
|
518e8903a3 | ||
|
|
6e56a55f6f | ||
|
|
967071ad4e | ||
|
|
d23fcfaa5a | ||
|
|
a2f2306430 | ||
|
|
144b543091 | ||
|
|
e4ad8f512e | ||
|
|
591a5006da | ||
|
|
281ed6cfba | ||
|
|
7354a61184 |
@@ -57,12 +57,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
|
||||||
- name: Set time zone
|
- name: Setup timezone
|
||||||
uses: szenius/set-timezone@v1.0
|
uses: zcong1993/setup-timezone@master
|
||||||
with:
|
with:
|
||||||
timezoneLinux: "Asia/Shanghai"
|
timezone: Asia/Shanghai
|
||||||
timezoneMacos: "Asia/Shanghai"
|
|
||||||
timezoneWindows: "China Standard Time"
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
import { defineConfig } from 'umi';
|
import { defineConfig } from '@umijs/max';
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
hash: true,
|
hash: true,
|
||||||
layout: false,
|
antd: {},
|
||||||
outputPath: 'static/dist',
|
outputPath: 'static/dist',
|
||||||
nodeModulesTransform: {
|
fastRefresh: true,
|
||||||
type: 'none',
|
favicons: ['/images/favicon.svg'],
|
||||||
|
mfsu: {
|
||||||
|
strategy: 'eager',
|
||||||
},
|
},
|
||||||
fastRefresh: {},
|
|
||||||
esbuild: {},
|
|
||||||
webpack5: {},
|
|
||||||
dynamicImport: {
|
|
||||||
loading: '@/components/pageLoading',
|
|
||||||
},
|
|
||||||
favicon: '/images/favicon.svg',
|
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/public': {
|
'/api/public': {
|
||||||
target: 'http://127.0.0.1:5400/',
|
target: 'http://127.0.0.1:5400/',
|
||||||
@@ -26,7 +21,7 @@ export default defineConfig({
|
|||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chainWebpack: (config) => {
|
chainWebpack: ((config: any) => {
|
||||||
config.plugin('compression-webpack-plugin').use(
|
config.plugin('compression-webpack-plugin').use(
|
||||||
new CompressionPlugin({
|
new CompressionPlugin({
|
||||||
algorithm: 'gzip',
|
algorithm: 'gzip',
|
||||||
@@ -35,13 +30,13 @@ export default defineConfig({
|
|||||||
minRatio: 0.6,
|
minRatio: 0.6,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
}) as any,
|
||||||
externals: {
|
externals: {
|
||||||
react: 'window.React',
|
react: 'window.React',
|
||||||
'react-dom': 'window.ReactDOM',
|
'react-dom': 'window.ReactDOM',
|
||||||
},
|
},
|
||||||
scripts: [
|
headScripts: [
|
||||||
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
'https://gw.alipayobjects.com/os/lib/react/18.2.0/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/18.2.0/umd/react-dom.production.min.js',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/whyour/qinglong">
|
<a href="https://github.com/whyour/qinglong">
|
||||||
<img width="150" src="https://z3.ax1x.com/2021/11/18/I7MpAe.png">
|
<img width="150" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -61,7 +61,6 @@ podman run -dit \
|
|||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
--name qinglong \
|
--name qinglong \
|
||||||
--hostname qinglong \
|
--hostname qinglong \
|
||||||
--restart unless-stopped \
|
|
||||||
docker.io/whyour/qinglong:latest
|
docker.io/whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -183,7 +182,7 @@ task <file_path> desi <env_name> <account_number>
|
|||||||
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
||||||
- [Ant Design](https://ant.design)
|
- [Ant Design](https://ant.design)
|
||||||
- [Ant Design Pro](https://pro.ant.design/)
|
- [Ant Design Pro](https://pro.ant.design/)
|
||||||
- [Umijs3.0](https://umijs.org)
|
- [Umijs](https://umijs.org)
|
||||||
- [darkreader](https://github.com/darkreader/darkreader)
|
- [darkreader](https://github.com/darkreader/darkreader)
|
||||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/whyour/qinglong">
|
<a href="https://github.com/whyour/qinglong">
|
||||||
<img width="150" src="https://z3.ax1x.com/2021/11/18/I7MpAe.png">
|
<img width="150" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -61,7 +61,6 @@ podman run -dit \
|
|||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
--name qinglong \
|
--name qinglong \
|
||||||
--hostname qinglong \
|
--hostname qinglong \
|
||||||
--restart unless-stopped \
|
|
||||||
docker.io/whyour/qinglong:latest
|
docker.io/whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -183,7 +182,7 @@ task <file_path> desi <env_name> <account_number>
|
|||||||
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
||||||
- [Ant Design](https://ant.design)
|
- [Ant Design](https://ant.design)
|
||||||
- [Ant Design Pro](https://pro.ant.design/)
|
- [Ant Design Pro](https://pro.ant.design/)
|
||||||
- [Umijs3.0](https://umijs.org)
|
- [Umijs](https://umijs.org)
|
||||||
- [darkreader](https://github.com/darkreader/darkreader)
|
- [darkreader](https://github.com/darkreader/darkreader)
|
||||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||||
|
|
||||||
|
|||||||
+49
-2
@@ -3,8 +3,21 @@ import { Container } from 'typedi';
|
|||||||
import EnvService from '../services/env';
|
import EnvService from '../services/env';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
|
import multer from 'multer';
|
||||||
|
import config from '../config';
|
||||||
|
import fs from 'fs';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, config.scriptPath);
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
cb(null, file.originalname);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const upload = multer({ storage: storage });
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/envs', route);
|
app.use('/envs', route);
|
||||||
|
|
||||||
@@ -26,7 +39,9 @@ export default (app: Router) => {
|
|||||||
body: Joi.array().items(
|
body: Joi.array().items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
value: Joi.string().required(),
|
value: Joi.string().required(),
|
||||||
name: Joi.string().required(),
|
name: Joi.string()
|
||||||
|
.required()
|
||||||
|
.pattern(/^[a-zA-Z_][0-9a-zA-Z_]*$/),
|
||||||
remarks: Joi.string().optional().allow(''),
|
remarks: Joi.string().optional().allow(''),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -94,7 +109,6 @@ export default (app: Router) => {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
try {
|
||||||
const envService = Container.get(EnvService);
|
const envService = Container.get(EnvService);
|
||||||
const data = await envService.move(req.params.id, req.body);
|
const data = await envService.move(req.params.id, req.body);
|
||||||
@@ -177,4 +191,37 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
'/upload',
|
||||||
|
upload.single('env'),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const envService = Container.get(EnvService);
|
||||||
|
const fileContent = await fs.promises.readFile(req!.file!.path, 'utf8');
|
||||||
|
const parseContent = JSON.parse(fileContent);
|
||||||
|
const data = Array.isArray(parseContent)
|
||||||
|
? parseContent
|
||||||
|
: [parseContent];
|
||||||
|
if (data.every((x) => x.name && x.value)) {
|
||||||
|
const result = await envService.create(
|
||||||
|
data.map((x) => ({
|
||||||
|
name: x.name,
|
||||||
|
value: x.value,
|
||||||
|
remarks: x.remarks,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
return res.send({ code: 200, data: result });
|
||||||
|
} else {
|
||||||
|
return res.send({
|
||||||
|
code: 400,
|
||||||
|
message: '文件缺少name或者value字段,参考导出文件格式',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+32
-1
@@ -3,8 +3,9 @@ import { Container } from 'typedi';
|
|||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { getFileContentByName, readDirs } from '../config/util';
|
import { emptyDir, getFileContentByName, readDirs } from '../config/util';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { celebrate, Joi } from 'celebrate';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
const blacklist = ['.tmp'];
|
const blacklist = ['.tmp'];
|
||||||
|
|
||||||
@@ -45,4 +46,34 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
'/',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
filename: Joi.string().required(),
|
||||||
|
path: Joi.string().allow(''),
|
||||||
|
type: Joi.string().optional()
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
let { filename, path, type } = req.body as {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
const filePath = join(config.logPath, path, filename);
|
||||||
|
if (type === 'directory') {
|
||||||
|
emptyDir(filePath);
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
res.send({ code: 200 });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
+27
-10
@@ -4,6 +4,7 @@ import {
|
|||||||
readDirs,
|
readDirs,
|
||||||
getLastModifyFilePath,
|
getLastModifyFilePath,
|
||||||
readDir,
|
readDir,
|
||||||
|
emptyDir,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
@@ -77,12 +78,14 @@ export default (app: Router) => {
|
|||||||
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 {
|
||||||
let { filename, path, content, originFilename } = req.body as {
|
let { filename, path, content, originFilename, directory } =
|
||||||
filename: string;
|
req.body as {
|
||||||
path: string;
|
filename: string;
|
||||||
content: string;
|
path: string;
|
||||||
originFilename: string;
|
content: string;
|
||||||
};
|
originFilename: string;
|
||||||
|
directory: string;
|
||||||
|
};
|
||||||
|
|
||||||
if (!path) {
|
if (!path) {
|
||||||
path = config.scriptPath;
|
path = config.scriptPath;
|
||||||
@@ -105,11 +108,19 @@ export default (app: Router) => {
|
|||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (directory) {
|
||||||
|
fs.mkdirSync(join(path, directory), { recursive: true });
|
||||||
|
return res.send({ code: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
if (!originFilename) {
|
if (!originFilename) {
|
||||||
originFilename = filename;
|
originFilename = filename;
|
||||||
}
|
}
|
||||||
const originFilePath = `${path}${originFilename.replace(/\//g, '')}`;
|
const originFilePath = join(
|
||||||
const filePath = `${path}${filename.replace(/\//g, '')}`;
|
path,
|
||||||
|
`${originFilename.replace(/\//g, '')}`,
|
||||||
|
);
|
||||||
|
const filePath = join(path, `${filename.replace(/\//g, '')}`);
|
||||||
if (fs.existsSync(originFilePath)) {
|
if (fs.existsSync(originFilePath)) {
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
originFilePath,
|
originFilePath,
|
||||||
@@ -159,17 +170,23 @@ export default (app: Router) => {
|
|||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
path: Joi.string().allow(''),
|
path: Joi.string().allow(''),
|
||||||
|
type: Joi.string().optional()
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
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 {
|
||||||
let { filename, path } = req.body as {
|
let { filename, path, type } = req.body as {
|
||||||
filename: string;
|
filename: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
type: string;
|
||||||
};
|
};
|
||||||
const filePath = join(config.scriptPath, path, filename);
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
fs.unlinkSync(filePath);
|
if (type === 'directory') {
|
||||||
|
emptyDir(filePath);
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import SystemService from '../services/system';
|
|||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import UserService from '../services/user';
|
import UserService from '../services/user';
|
||||||
import { EnvModel } from '../data/env';
|
import { EnvModel } from '../data/env';
|
||||||
|
import { promiseExec } from '../config/util';
|
||||||
|
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
@@ -22,6 +24,15 @@ export default (app: Router) => {
|
|||||||
|
|
||||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||||
const version = currentVersionFile.match(versionRegx)![1];
|
const version = currentVersionFile.match(versionRegx)![1];
|
||||||
|
const lastCommitTime = (
|
||||||
|
await promiseExec('git show -s --format=%ai')
|
||||||
|
).replace('\n', '');
|
||||||
|
const lastCommitId = (
|
||||||
|
await promiseExec('git rev-parse --short HEAD')
|
||||||
|
).replace('\n', '');
|
||||||
|
const branch = (
|
||||||
|
await promiseExec('git symbolic-ref --short HEAD')
|
||||||
|
).replace('\n', '');
|
||||||
|
|
||||||
let isInitialized = true;
|
let isInitialized = true;
|
||||||
if (
|
if (
|
||||||
@@ -37,6 +48,9 @@ export default (app: Router) => {
|
|||||||
data: {
|
data: {
|
||||||
isInitialized,
|
isInitialized,
|
||||||
version,
|
version,
|
||||||
|
lastCommitTime,
|
||||||
|
lastCommitId,
|
||||||
|
branch,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ async function startServer() {
|
|||||||
|
|
||||||
const server = app
|
const server = app
|
||||||
.listen(config.port, () => {
|
.listen(config.port, () => {
|
||||||
Logger.debug(`✌️ Back server launched on port ${config.port}`);
|
Logger.debug(`✌️ 后端服务启动成功!`);
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
Logger.error(err);
|
Logger.error(err);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const LOG_END_SYMBOL = '\n ';
|
||||||
@@ -11,10 +11,10 @@ if (!process.env.QL_DIR) {
|
|||||||
if (qlHomePath.endsWith('/static/')) {
|
if (qlHomePath.endsWith('/static/')) {
|
||||||
qlHomePath = path.join(qlHomePath, '../');
|
qlHomePath = path.join(qlHomePath, '../');
|
||||||
}
|
}
|
||||||
process.env.QL_DIR = qlHomePath;
|
process.env.QL_DIR = qlHomePath.replace(/\/$/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastVersionFile = `http://qn.whyour.cn/version.ts?v=${Date.now()}`;
|
const lastVersionFile = `https://qn.whyour.cn/version.ts`;
|
||||||
|
|
||||||
const rootPath = process.env.QL_DIR as string;
|
const rootPath = process.env.QL_DIR as string;
|
||||||
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
|
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
|
||||||
|
|||||||
+87
-1
@@ -3,6 +3,7 @@ import * as path from 'path';
|
|||||||
import got from 'got';
|
import got from 'got';
|
||||||
import iconv from 'iconv-lite';
|
import iconv from 'iconv-lite';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
|
import FormData from 'form-data';
|
||||||
|
|
||||||
export function getFileContentByName(fileName: string) {
|
export function getFileContentByName(fileName: string) {
|
||||||
if (fs.existsSync(fileName)) {
|
if (fs.existsSync(fileName)) {
|
||||||
@@ -301,7 +302,6 @@ export function readDirs(
|
|||||||
title: file,
|
title: file,
|
||||||
key,
|
key,
|
||||||
type: 'directory',
|
type: 'directory',
|
||||||
disabled: true,
|
|
||||||
parent: relativePath,
|
parent: relativePath,
|
||||||
children: readDirs(subPath, baseDir).sort(
|
children: readDirs(subPath, baseDir).sort(
|
||||||
(a: any, b: any) =>
|
(a: any, b: any) =>
|
||||||
@@ -312,6 +312,7 @@ export function readDirs(
|
|||||||
return {
|
return {
|
||||||
title: file,
|
title: file,
|
||||||
type: 'file',
|
type: 'file',
|
||||||
|
isLeaf: true,
|
||||||
key,
|
key,
|
||||||
parent: relativePath,
|
parent: relativePath,
|
||||||
};
|
};
|
||||||
@@ -344,6 +345,20 @@ export function readDir(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emptyDir(path: string) {
|
||||||
|
const files = fs.readdirSync(path);
|
||||||
|
files.forEach((file) => {
|
||||||
|
const filePath = `${path}/${file}`;
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
emptyDir(filePath);
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fs.rmdirSync(path);
|
||||||
|
}
|
||||||
|
|
||||||
export function promiseExec(command: string): Promise<string> {
|
export function promiseExec(command: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(
|
exec(
|
||||||
@@ -355,3 +370,74 @@ export function promiseExec(command: string): Promise<string> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseHeaders(headers: string) {
|
||||||
|
if (!headers) return {};
|
||||||
|
|
||||||
|
const parsed: any = {};
|
||||||
|
let key;
|
||||||
|
let val;
|
||||||
|
let i;
|
||||||
|
|
||||||
|
headers &&
|
||||||
|
headers.split('\n').forEach(function parser(line) {
|
||||||
|
i = line.indexOf(':');
|
||||||
|
key = line.substring(0, i).trim().toLowerCase();
|
||||||
|
val = line.substring(i + 1).trim();
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
|
||||||
|
});
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseBody(
|
||||||
|
body: string,
|
||||||
|
contentType:
|
||||||
|
| 'application/json'
|
||||||
|
| 'multipart/form-data'
|
||||||
|
| 'application/x-www-form-urlencoded',
|
||||||
|
) {
|
||||||
|
if (!body) return '';
|
||||||
|
|
||||||
|
const parsed: any = {};
|
||||||
|
let key;
|
||||||
|
let val;
|
||||||
|
let i;
|
||||||
|
|
||||||
|
body &&
|
||||||
|
body.split('\n').forEach(function parser(line) {
|
||||||
|
i = line.indexOf(':');
|
||||||
|
key = line.substring(0, i).trim().toLowerCase();
|
||||||
|
val = line.substring(i + 1).trim();
|
||||||
|
|
||||||
|
if (!key || parsed[key]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonValue = JSON.parse(val);
|
||||||
|
parsed[key] = jsonValue;
|
||||||
|
} catch (error) {
|
||||||
|
parsed[key] = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (contentType) {
|
||||||
|
case 'multipart/form-data':
|
||||||
|
return Object.keys(parsed).reduce((p, c) => {
|
||||||
|
p.append(c, parsed[c]);
|
||||||
|
return p;
|
||||||
|
}, new FormData());
|
||||||
|
case 'application/x-www-form-urlencoded':
|
||||||
|
return Object.keys(parsed).reduce((p, c) => {
|
||||||
|
return p ? `${p}&${c}=${parsed[c]}` : `${c}=${parsed[c]}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|||||||
+4
-1
@@ -26,7 +26,10 @@ export enum EnvStatus {
|
|||||||
'disabled',
|
'disabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initEnvPosition = 9999999999;
|
export const maxPosition = 9000000000000000;
|
||||||
|
export const initPosition = 4500000000000000;
|
||||||
|
export const stepPosition = 10000000;
|
||||||
|
export const minPosition = 100;
|
||||||
|
|
||||||
interface EnvInstance extends Model<Env, Env>, Env {}
|
interface EnvInstance extends Model<Env, Env>, Env {}
|
||||||
export const EnvModel = sequelize.define<EnvInstance>('Env', {
|
export const EnvModel = sequelize.define<EnvInstance>('Env', {
|
||||||
|
|||||||
+12
-5
@@ -1,15 +1,22 @@
|
|||||||
import { Sequelize } from 'sequelize';
|
import { Sequelize, Transaction } from 'sequelize';
|
||||||
import config from '../config/index';
|
import config from '../config/index';
|
||||||
|
|
||||||
export const sequelize = new Sequelize({
|
export const sequelize = new Sequelize({
|
||||||
dialect: 'sqlite',
|
dialect: 'sqlite',
|
||||||
storage: `${config.dbPath}database.sqlite`,
|
storage: `${config.dbPath}database.sqlite`,
|
||||||
logging: false,
|
logging: false,
|
||||||
pool: {
|
retry: {
|
||||||
max: 6,
|
max: 10,
|
||||||
min: 0,
|
match: ['SQLITE_BUSY: database is locked'],
|
||||||
idle: 30000,
|
|
||||||
},
|
},
|
||||||
|
pool: {
|
||||||
|
max: 5,
|
||||||
|
min: 2,
|
||||||
|
idle: 30000,
|
||||||
|
acquire: 30000,
|
||||||
|
evict: 10000,
|
||||||
|
},
|
||||||
|
transactionType: Transaction.TYPES.IMMEDIATE,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ResponseType<T> = { code: number; data?: T; message?: string };
|
export type ResponseType<T> = { code: number; data?: T; message?: string };
|
||||||
|
|||||||
+31
-2
@@ -1,3 +1,5 @@
|
|||||||
|
import { IncomingHttpHeaders } from 'http';
|
||||||
|
|
||||||
export enum NotificationMode {
|
export enum NotificationMode {
|
||||||
'gotify' = 'gotify',
|
'gotify' = 'gotify',
|
||||||
'goCqHttpBot' = 'goCqHttpBot',
|
'goCqHttpBot' = 'goCqHttpBot',
|
||||||
@@ -9,9 +11,12 @@ export enum NotificationMode {
|
|||||||
'dingtalkBot' = 'dingtalkBot',
|
'dingtalkBot' = 'dingtalkBot',
|
||||||
'weWorkBot' = 'weWorkBot',
|
'weWorkBot' = 'weWorkBot',
|
||||||
'weWorkApp' = 'weWorkApp',
|
'weWorkApp' = 'weWorkApp',
|
||||||
|
'aibotk' = 'aibotk',
|
||||||
'iGot' = 'iGot',
|
'iGot' = 'iGot',
|
||||||
'pushPlus' = 'pushPlus',
|
'pushPlus' = 'pushPlus',
|
||||||
'email' = 'email',
|
'email' = 'email',
|
||||||
|
'feishu' = 'feishu',
|
||||||
|
'webhook' = 'webhook',
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class NotificationBaseInfo {
|
abstract class NotificationBaseInfo {
|
||||||
@@ -45,7 +50,7 @@ export class ChatNotification extends NotificationBaseInfo {
|
|||||||
|
|
||||||
export class BarkNotification extends NotificationBaseInfo {
|
export class BarkNotification extends NotificationBaseInfo {
|
||||||
public barkPush = '';
|
public barkPush = '';
|
||||||
public barkIcon = 'http://qn.whyour.cn/logo.png';
|
public barkIcon = 'https://qn.whyour.cn/logo.png';
|
||||||
public barkSound = '';
|
public barkSound = '';
|
||||||
public barkGroup = 'qinglong';
|
public barkGroup = 'qinglong';
|
||||||
}
|
}
|
||||||
@@ -72,6 +77,12 @@ export class WeWorkAppNotification extends NotificationBaseInfo {
|
|||||||
public weWorkAppKey = '';
|
public weWorkAppKey = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AibotkNotification extends NotificationBaseInfo {
|
||||||
|
public aibotkKey: string = '';
|
||||||
|
public aibotkType: 'room' | 'contact' = 'room';
|
||||||
|
public aibotkName: string = '';
|
||||||
|
}
|
||||||
|
|
||||||
export class IGotNotification extends NotificationBaseInfo {
|
export class IGotNotification extends NotificationBaseInfo {
|
||||||
public iGotPushKey = '';
|
public iGotPushKey = '';
|
||||||
}
|
}
|
||||||
@@ -87,6 +98,21 @@ export class EmailNotification extends NotificationBaseInfo {
|
|||||||
public emailPass: string = '';
|
public emailPass: string = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WebhookNotification extends NotificationBaseInfo {
|
||||||
|
public webhookHeaders: string = '';
|
||||||
|
public webhookBody: string = '';
|
||||||
|
public webhookUrl: string = '';
|
||||||
|
public webhookMethod: 'GET' | 'POST' | 'PUT' = 'GET';
|
||||||
|
public webhookContentType:
|
||||||
|
| 'application/json'
|
||||||
|
| 'multipart/form-data'
|
||||||
|
| 'application/x-www-form-urlencoded' = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LarkNotification extends NotificationBaseInfo {
|
||||||
|
public larkKey = '';
|
||||||
|
}
|
||||||
|
|
||||||
export interface NotificationInfo
|
export interface NotificationInfo
|
||||||
extends GoCqHttpBotNotification,
|
extends GoCqHttpBotNotification,
|
||||||
GotifyNotification,
|
GotifyNotification,
|
||||||
@@ -98,6 +124,9 @@ export interface NotificationInfo
|
|||||||
DingtalkBotNotification,
|
DingtalkBotNotification,
|
||||||
WeWorkBotNotification,
|
WeWorkBotNotification,
|
||||||
WeWorkAppNotification,
|
WeWorkAppNotification,
|
||||||
|
AibotkNotification,
|
||||||
IGotNotification,
|
IGotNotification,
|
||||||
PushPlusNotification,
|
PushPlusNotification,
|
||||||
EmailNotification {}
|
EmailNotification,
|
||||||
|
WebhookNotification,
|
||||||
|
LarkNotification {}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Container } from 'typedi';
|
|||||||
import { Crontab, CrontabModel, 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 groupBy from 'lodash/groupBy';
|
||||||
import { DependenceModel } from '../data/dependence';
|
import { DependenceModel } from '../data/dependence';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
@@ -26,7 +26,7 @@ export default async () => {
|
|||||||
order: [['type', 'DESC']],
|
order: [['type', 'DESC']],
|
||||||
raw: true,
|
raw: true,
|
||||||
}).then(async (docs) => {
|
}).then(async (docs) => {
|
||||||
const groups = _.groupBy(docs, 'type');
|
const groups = groupBy(docs, 'type');
|
||||||
const keys = Object.keys(groups).sort((a, b) => parseInt(b) - parseInt(a));
|
const keys = Object.keys(groups).sort((a, b) => parseInt(b) - parseInt(a));
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const group = groups[key];
|
const group = groups[key];
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import _ from 'lodash';
|
|
||||||
import SystemService from '../services/system';
|
import SystemService from '../services/system';
|
||||||
import ScheduleService from '../services/schedule';
|
import ScheduleService from '../services/schedule';
|
||||||
import SubscriptionService from '../services/subscription';
|
import SubscriptionService from '../services/subscription';
|
||||||
@@ -13,18 +12,17 @@ export default async () => {
|
|||||||
|
|
||||||
// 生成内置token
|
// 生成内置token
|
||||||
let tokenCommand = `ts-node-transpile-only ${config.rootPath}/back/token.ts`;
|
let tokenCommand = `ts-node-transpile-only ${config.rootPath}/back/token.ts`;
|
||||||
const tokenFile = `${config.rootPath}/static/build/token.js`;
|
const tokenFile = `${config.rootPath}static/build/token.js`;
|
||||||
if (await fileExist(tokenFile)) {
|
if (await fileExist(tokenFile)) {
|
||||||
tokenCommand = `node ${tokenFile}`;
|
tokenCommand = `node ${tokenFile}`;
|
||||||
}
|
}
|
||||||
const cron = {
|
const cron = {
|
||||||
id: 'token',
|
id: NaN,
|
||||||
name: '生成token',
|
name: '生成token',
|
||||||
command: tokenCommand,
|
command: tokenCommand,
|
||||||
};
|
};
|
||||||
scheduleService.createIntervalTask(cron as any, {
|
scheduleService.createIntervalTask(cron, {
|
||||||
days: 28,
|
days: 28,
|
||||||
runImmediately: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 运行删除日志任务
|
// 运行删除日志任务
|
||||||
@@ -37,7 +35,6 @@ export default async () => {
|
|||||||
};
|
};
|
||||||
scheduleService.createIntervalTask(cron, {
|
scheduleService.createIntervalTask(cron, {
|
||||||
days: data.info.frequency,
|
days: data.info.frequency,
|
||||||
runImmediately: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,23 @@ import { Application } from 'express';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import * as Tracing from '@sentry/tracing';
|
import * as Tracing from '@sentry/tracing';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
|
import config from '../config';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
export default ({ expressApp }: { expressApp: Application }) => {
|
export default ({ expressApp }: { expressApp: Application }) => {
|
||||||
|
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
||||||
|
|
||||||
|
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||||
|
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
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 }),
|
||||||
],
|
],
|
||||||
|
tracesSampleRate: 0.1,
|
||||||
tracesSampleRate: 1.0,
|
release: currentVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
expressApp.use(Sentry.Handlers.requestHandler());
|
expressApp.use(Sentry.Handlers.requestHandler());
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ app
|
|||||||
await require('./loaders/sentry').default({ expressApp: app });
|
await require('./loaders/sentry').default({ expressApp: app });
|
||||||
await require('./loaders/db').default();
|
await require('./loaders/db').default();
|
||||||
|
|
||||||
Logger.debug(`✌️ Back server launched on port ${config.publicPort}`);
|
Logger.debug(`✌️ 公共服务启动成功!`);
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
Logger.error(err);
|
Logger.error(err);
|
||||||
|
|||||||
+4
-7
@@ -18,14 +18,15 @@ const run = async () => {
|
|||||||
_schedule &&
|
_schedule &&
|
||||||
_schedule.length > 5 &&
|
_schedule.length > 5 &&
|
||||||
task.status !== CrontabStatus.disabled &&
|
task.status !== CrontabStatus.disabled &&
|
||||||
!task.isDisabled
|
!task.isDisabled &&
|
||||||
|
task.schedule
|
||||||
) {
|
) {
|
||||||
schedule.scheduleJob(task.schedule, function () {
|
schedule.scheduleJob(task.schedule, function () {
|
||||||
let command = task.command as string;
|
let command = task.command as string;
|
||||||
if (!command.includes('task ') && !command.includes('ql ')) {
|
if (!command.includes('task ') && !command.includes('ql ')) {
|
||||||
command = `task ${command}`;
|
command = `task ${command}`;
|
||||||
}
|
}
|
||||||
exec(command);
|
exec(`ID=${task.id} ${command}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,11 +44,7 @@ app
|
|||||||
await require('./loaders/db').default();
|
await require('./loaders/db').default();
|
||||||
|
|
||||||
await run();
|
await run();
|
||||||
Logger.info(`
|
Logger.debug('定时任务服务启动成功!');
|
||||||
################################################
|
|
||||||
🛡️ Schedule listening on port: ${config.cronPort} 🛡️
|
|
||||||
################################################
|
|
||||||
`);
|
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
Logger.error(err);
|
Logger.error(err);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { promisify } from 'util';
|
|||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { LOG_END_SYMBOL } from '../config/const';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronService {
|
export default class CronService {
|
||||||
@@ -335,7 +336,7 @@ export default class CronService {
|
|||||||
`${absolutePath}`,
|
`${absolutePath}`,
|
||||||
`${str}\n## 执行结束... ${endTime.format(
|
`${str}\n## 执行结束... ${endTime.format(
|
||||||
'YYYY-MM-DD HH:mm:ss',
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
)}${diffTimeStr}`,
|
)}${diffTimeStr}${LOG_END_SYMBOL}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,7 +413,7 @@ export default class CronService {
|
|||||||
cmdStr = `${cmdStr} now`;
|
cmdStr = `${cmdStr} now`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cp = spawn(cmdStr, { shell: '/bin/bash' });
|
const cp = spawn(`ID=${id} ${cmdStr}`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
{ status: CrontabStatus.running, pid: cp.pid },
|
{ status: CrontabStatus.running, pid: cp.pid },
|
||||||
@@ -420,7 +421,7 @@ export default class CronService {
|
|||||||
);
|
);
|
||||||
cp.stderr.on('data', (data) => {
|
cp.stderr.on('data', (data) => {
|
||||||
if (logFileExist) {
|
if (logFileExist) {
|
||||||
fs.appendFileSync(`${absolutePath}`, `${data}`);
|
fs.appendFileSync(`${absolutePath}`, `${data.toString()}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cp.on('error', (err) => {
|
cp.on('error', (err) => {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { CrontabView, CrontabViewModel } from '../data/cronView';
|
import { CrontabView, CrontabViewModel } from '../data/cronView';
|
||||||
import { initEnvPosition } from '../data/env';
|
import { initPosition } from '../data/env';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronViewService {
|
export default class CronViewService {
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
public async create(payload: CrontabView): Promise<CrontabView> {
|
public async create(payload: CrontabView): Promise<CrontabView> {
|
||||||
let position = initEnvPosition;
|
let position = initPosition;
|
||||||
const views = await this.list();
|
const views = await this.list();
|
||||||
if (views && views.length > 0 && views[views.length - 1].position) {
|
if (views && views.length > 0 && views[views.length - 1].position) {
|
||||||
position = views[views.length - 1].position as number;
|
position = views[views.length - 1].position as number;
|
||||||
@@ -76,12 +76,12 @@ export default class CronViewService {
|
|||||||
const views = await this.list();
|
const views = await this.list();
|
||||||
if (toIndex === 0 || toIndex === views.length - 1) {
|
if (toIndex === 0 || toIndex === views.length - 1) {
|
||||||
targetPosition = isUpward
|
targetPosition = isUpward
|
||||||
? views[0].position * 2
|
? views[0].position! * 2
|
||||||
: views[toIndex].position / 2;
|
: views[toIndex].position! / 2;
|
||||||
} else {
|
} else {
|
||||||
targetPosition = isUpward
|
targetPosition = isUpward
|
||||||
? (views[toIndex].position + views[toIndex - 1].position) / 2
|
? (views[toIndex].position! + views[toIndex - 1].position!) / 2
|
||||||
: (views[toIndex].position + views[toIndex + 1].position) / 2;
|
: (views[toIndex].position! + views[toIndex + 1].position!) / 2;
|
||||||
}
|
}
|
||||||
const newDoc = await this.update({
|
const newDoc = await this.update({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
unInstallDependenceCommandTypes,
|
unInstallDependenceCommandTypes,
|
||||||
DependenceModel,
|
DependenceModel,
|
||||||
} from '../data/dependence';
|
} from '../data/dependence';
|
||||||
import _ from 'lodash';
|
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
@@ -125,8 +124,11 @@ export default class DependenceService {
|
|||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async find(query: any, sort?: any): Promise<Dependence[]> {
|
private async find(query: any, sort: any = []): Promise<Dependence[]> {
|
||||||
const docs = await DependenceModel.findAll({ where: { ...query } });
|
const docs = await DependenceModel.findAll({
|
||||||
|
where: { ...query },
|
||||||
|
order: [...sort, ['createdAt', 'DESC']],
|
||||||
|
});
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-28
@@ -1,10 +1,17 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { getFileContentByName } from '../config/util';
|
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { Env, EnvModel, EnvStatus, initEnvPosition } from '../data/env';
|
import {
|
||||||
import _ from 'lodash';
|
Env,
|
||||||
|
EnvModel,
|
||||||
|
EnvStatus,
|
||||||
|
initPosition,
|
||||||
|
maxPosition,
|
||||||
|
minPosition,
|
||||||
|
stepPosition,
|
||||||
|
} from '../data/env';
|
||||||
|
import groupBy from 'lodash/groupBy';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -13,17 +20,22 @@ export default class EnvService {
|
|||||||
|
|
||||||
public async create(payloads: Env[]): Promise<Env[]> {
|
public async create(payloads: Env[]): Promise<Env[]> {
|
||||||
const envs = await this.envs();
|
const envs = await this.envs();
|
||||||
let position = initEnvPosition;
|
let position = initPosition;
|
||||||
if (envs && envs.length > 0 && envs[envs.length - 1].position) {
|
if (
|
||||||
position = envs[envs.length - 1].position as number;
|
envs &&
|
||||||
|
envs.length > 0 &&
|
||||||
|
typeof envs[envs.length - 1].position === 'number'
|
||||||
|
) {
|
||||||
|
position = envs[envs.length - 1].position!;
|
||||||
}
|
}
|
||||||
const tabs = payloads.map((x) => {
|
const tabs = payloads.map((x) => {
|
||||||
position = position / 2;
|
position = position - stepPosition;
|
||||||
const tab = new Env({ ...x, position });
|
const tab = new Env({ ...x, position });
|
||||||
return tab;
|
return tab;
|
||||||
});
|
});
|
||||||
const docs = await this.insert(tabs);
|
const docs = await this.insert(tabs);
|
||||||
await this.set_envs();
|
await this.set_envs();
|
||||||
|
await this.checkPosition(tabs[tabs.length - 1].position!);
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,25 +79,40 @@ export default class EnvService {
|
|||||||
const envs = await this.envs();
|
const envs = await this.envs();
|
||||||
if (toIndex === 0 || toIndex === envs.length - 1) {
|
if (toIndex === 0 || toIndex === envs.length - 1) {
|
||||||
targetPosition = isUpward
|
targetPosition = isUpward
|
||||||
? envs[0].position * 2
|
? envs[0].position! + stepPosition
|
||||||
: envs[toIndex].position / 2;
|
: envs[toIndex].position! - stepPosition;
|
||||||
} else {
|
} else {
|
||||||
targetPosition = isUpward
|
targetPosition = isUpward
|
||||||
? (envs[toIndex].position + envs[toIndex - 1].position) / 2
|
? (envs[toIndex].position! + envs[toIndex - 1].position!) / 2
|
||||||
: (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: this.getPrecisionPosition(targetPosition),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.checkPosition(targetPosition);
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async envs(
|
private async checkPosition(position: number) {
|
||||||
searchText: string = '',
|
const precisionPosition = parseFloat(position.toPrecision(16));
|
||||||
sort: any = { position: -1 },
|
if (precisionPosition < minPosition || precisionPosition > maxPosition) {
|
||||||
query: any = {},
|
const envs = await this.envs();
|
||||||
): Promise<Env[]> {
|
let position = initPosition;
|
||||||
|
for (const env of envs) {
|
||||||
|
position = position - stepPosition;
|
||||||
|
await this.updateDb({ ...env, position });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPrecisionPosition(position: number): number {
|
||||||
|
return parseFloat(position.toPrecision(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async envs(searchText: string = '', query: any = {}): Promise<Env[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const encodeText = encodeURIComponent(searchText);
|
const encodeText = encodeURIComponent(searchText);
|
||||||
@@ -112,7 +139,11 @@ export default class EnvService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await this.find(condition, [['position', 'DESC']]);
|
const result = await this.find(condition, [
|
||||||
|
['status', 'ASC'],
|
||||||
|
['position', 'DESC'],
|
||||||
|
['createdAt', 'ASC'],
|
||||||
|
]);
|
||||||
return result as any;
|
return result as any;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -151,22 +182,20 @@ export default class EnvService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async set_envs() {
|
public async set_envs() {
|
||||||
const envs = await this.envs(
|
const envs = await this.envs('', {
|
||||||
'',
|
name: { [Op.not]: null },
|
||||||
{ position: -1 },
|
status: EnvStatus.normal,
|
||||||
{ name: { [Op.not]: null } },
|
});
|
||||||
);
|
const groups = groupBy(envs, 'name');
|
||||||
const groups = _.groupBy(envs, 'name');
|
|
||||||
let env_string = '';
|
let env_string = '';
|
||||||
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];
|
||||||
|
|
||||||
// 忽略不符合bash要求的环境变量名称
|
// 忽略不符合bash要求的环境变量名称
|
||||||
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) {
|
if (/^[a-zA-Z_][0-9a-zA-Z_]*$/.test(key)) {
|
||||||
let value = _(group)
|
let value = group
|
||||||
.filter((x) => x.status !== EnvStatus.disabled)
|
.map((x) => x.value)
|
||||||
.map('value')
|
|
||||||
.join('&')
|
.join('&')
|
||||||
.replace(/(\\)[^n]/g, '\\\\')
|
.replace(/(\\)[^n]/g, '\\\\')
|
||||||
.replace(/(\\$)/, '\\\\')
|
.replace(/(\\$)/, '\\\\')
|
||||||
|
|||||||
+135
-27
@@ -6,6 +6,7 @@ import got from 'got';
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
||||||
|
import { parseBody, parseHeaders } from '../config/util';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class NotificationService {
|
export default class NotificationService {
|
||||||
@@ -23,15 +24,21 @@ export default class NotificationService {
|
|||||||
['dingtalkBot', this.dingtalkBot],
|
['dingtalkBot', this.dingtalkBot],
|
||||||
['weWorkBot', this.weWorkBot],
|
['weWorkBot', this.weWorkBot],
|
||||||
['weWorkApp', this.weWorkApp],
|
['weWorkApp', this.weWorkApp],
|
||||||
|
['aibotk', this.aibotk],
|
||||||
['iGot', this.iGot],
|
['iGot', this.iGot],
|
||||||
['pushPlus', this.pushPlus],
|
['pushPlus', this.pushPlus],
|
||||||
['email', this.email],
|
['email', this.email],
|
||||||
|
['webhook', this.webhook],
|
||||||
|
['lark', this.lark],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
private timeout = 10000;
|
|
||||||
private title = '';
|
private title = '';
|
||||||
private content = '';
|
private content = '';
|
||||||
private params!: Omit<NotificationInfo, 'type'>;
|
private params!: Omit<NotificationInfo, 'type'>;
|
||||||
|
private gotOption = {
|
||||||
|
timeout: 30000,
|
||||||
|
retry: 1,
|
||||||
|
};
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
@@ -74,8 +81,7 @@ export default class NotificationService {
|
|||||||
const { gotifyUrl, gotifyToken, gotifyPriority } = this.params;
|
const { gotifyUrl, gotifyToken, gotifyPriority } = this.params;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
|
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
body: `title=${encodeURIComponent(
|
body: `title=${encodeURIComponent(
|
||||||
this.title,
|
this.title,
|
||||||
)}&message=${encodeURIComponent(
|
)}&message=${encodeURIComponent(
|
||||||
@@ -93,8 +99,7 @@ export default class NotificationService {
|
|||||||
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(`${goCqHttpBotUrl}?${goCqHttpBotQq}`, {
|
.post(`${goCqHttpBotUrl}?${goCqHttpBotQq}`, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
json: { message: `${this.title}\n${this.content}` },
|
json: { message: `${this.title}\n${this.content}` },
|
||||||
headers: { Authorization: 'Bearer ' + goCqHttpBotToken },
|
headers: { Authorization: 'Bearer ' + goCqHttpBotToken },
|
||||||
})
|
})
|
||||||
@@ -109,8 +114,7 @@ export default class NotificationService {
|
|||||||
: `https://sc.ftqq.com/${serverChanKey}.send`;
|
: `https://sc.ftqq.com/${serverChanKey}.send`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
body: `title=${this.title}&desp=${this.content}`,
|
body: `title=${this.title}&desp=${this.content}`,
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
@@ -123,8 +127,7 @@ export default class NotificationService {
|
|||||||
const url = `https://api2.pushdeer.com/message/push`;
|
const url = `https://api2.pushdeer.com/message/push`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
body: `pushkey=${pushDeerKey}&text=${encodeURIComponent(
|
body: `pushkey=${pushDeerKey}&text=${encodeURIComponent(
|
||||||
this.title,
|
this.title,
|
||||||
)}&desp=${encodeURIComponent(this.content)}&type=markdown`,
|
)}&desp=${encodeURIComponent(this.content)}&type=markdown`,
|
||||||
@@ -141,8 +144,7 @@ export default class NotificationService {
|
|||||||
const url = `${chatUrl}${chatToken}`;
|
const url = `${chatUrl}${chatToken}`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
body: `payload={"text":"${this.title}\n${this.content}"}`,
|
body: `payload={"text":"${this.title}\n${this.content}"}`,
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
@@ -162,8 +164,7 @@ export default class NotificationService {
|
|||||||
)}?icon=${barkIcon}?sound=${barkSound}&group=${barkGroup}`;
|
)}?icon=${barkIcon}?sound=${barkSound}&group=${barkGroup}`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.get(url, {
|
.get(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
@@ -201,8 +202,7 @@ export default class NotificationService {
|
|||||||
}
|
}
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
body: `chat_id=${telegramBotUserId}&text=${this.title}\n\n${this.content}&disable_web_page_preview=true`,
|
body: `chat_id=${telegramBotUserId}&text=${this.title}\n\n${this.content}&disable_web_page_preview=true`,
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
agent,
|
agent,
|
||||||
@@ -224,8 +224,7 @@ export default class NotificationService {
|
|||||||
const url = `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkBotToken}${secretParam}`;
|
const url = `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkBotToken}${secretParam}`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
json: {
|
json: {
|
||||||
msgtype: 'text',
|
msgtype: 'text',
|
||||||
text: {
|
text: {
|
||||||
@@ -242,8 +241,7 @@ export default class NotificationService {
|
|||||||
const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weWorkBotKey}`;
|
const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weWorkBotKey}`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
json: {
|
json: {
|
||||||
msgtype: 'text',
|
msgtype: 'text',
|
||||||
text: {
|
text: {
|
||||||
@@ -262,8 +260,7 @@ export default class NotificationService {
|
|||||||
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
|
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
|
||||||
const tokenRes: any = await got
|
const tokenRes: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
json: {
|
json: {
|
||||||
corpid,
|
corpid,
|
||||||
corpsecret,
|
corpsecret,
|
||||||
@@ -313,8 +310,7 @@ export default class NotificationService {
|
|||||||
.post(
|
.post(
|
||||||
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
|
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
|
||||||
{
|
{
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
json: {
|
json: {
|
||||||
touser,
|
touser,
|
||||||
agentid,
|
agentid,
|
||||||
@@ -328,13 +324,53 @@ export default class NotificationService {
|
|||||||
return res.errcode === 0;
|
return res.errcode === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async aibotk() {
|
||||||
|
const { aibotkKey, aibotkType, aibotkName } = this.params;
|
||||||
|
let url = '';
|
||||||
|
let json = {};
|
||||||
|
switch (aibotkType) {
|
||||||
|
case 'room':
|
||||||
|
url = 'https://api-bot.aibotk.com/openapi/v1/chat/room';
|
||||||
|
json = {
|
||||||
|
apiKey: `${aibotkKey}`,
|
||||||
|
roomName: `${aibotkName}`,
|
||||||
|
message: {
|
||||||
|
type: 1,
|
||||||
|
content: `【青龙快讯】\n\n${this.title}\n${this.content}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'contact':
|
||||||
|
url = 'https://api-bot.aibotk.com/openapi/v1/chat/contact';
|
||||||
|
json = {
|
||||||
|
apiKey: `${aibotkKey}`,
|
||||||
|
name: `${aibotkName}`,
|
||||||
|
message: {
|
||||||
|
type: 1,
|
||||||
|
content: `【青龙快讯】\n\n${this.title}\n${this.content}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res: any = await got
|
||||||
|
.post(url, {
|
||||||
|
...this.gotOption,
|
||||||
|
json: {
|
||||||
|
...json,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
|
||||||
|
return res.code === 0;
|
||||||
|
}
|
||||||
|
|
||||||
private async iGot() {
|
private async iGot() {
|
||||||
const { iGotPushKey } = this.params;
|
const { iGotPushKey } = this.params;
|
||||||
const url = `https://push.hellyw.com/${iGotPushKey.toLowerCase()}`;
|
const url = `https://push.hellyw.com/${iGotPushKey.toLowerCase()}`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
body: `title=${this.title}&content=${this.content}`,
|
body: `title=${this.title}&content=${this.content}`,
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
@@ -348,8 +384,7 @@ export default class NotificationService {
|
|||||||
const url = `https://www.pushplus.plus/send`;
|
const url = `https://www.pushplus.plus/send`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
...this.gotOption,
|
||||||
retry: 0,
|
|
||||||
json: {
|
json: {
|
||||||
token: `${pushPlusToken}`,
|
token: `${pushPlusToken}`,
|
||||||
title: `${this.title}`,
|
title: `${this.title}`,
|
||||||
@@ -362,6 +397,21 @@ export default class NotificationService {
|
|||||||
return res.code === 200;
|
return res.code === 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async lark() {
|
||||||
|
const { larkKey } = this.params;
|
||||||
|
const res: any = await got
|
||||||
|
.post(`https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`, {
|
||||||
|
...this.gotOption,
|
||||||
|
json: {
|
||||||
|
msg_type: 'text',
|
||||||
|
content: { text: `${this.title}\n\n${this.content}` },
|
||||||
|
},
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
return res.StatusCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
private async email() {
|
private async email() {
|
||||||
const { emailPass, emailService, emailUser } = this.params;
|
const { emailPass, emailService, emailUser } = this.params;
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
@@ -383,4 +433,62 @@ export default class NotificationService {
|
|||||||
|
|
||||||
return !!info.messageId;
|
return !!info.messageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async webhook() {
|
||||||
|
const {
|
||||||
|
webhookUrl,
|
||||||
|
webhookBody,
|
||||||
|
webhookHeaders,
|
||||||
|
webhookMethod,
|
||||||
|
webhookContentType,
|
||||||
|
} = this.params;
|
||||||
|
|
||||||
|
const { formatBody, formatUrl } = this.formatNotifyContent(
|
||||||
|
webhookUrl,
|
||||||
|
webhookBody,
|
||||||
|
);
|
||||||
|
if (!formatUrl && !formatBody) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const headers = parseHeaders(webhookHeaders);
|
||||||
|
const body = parseBody(formatBody, webhookContentType);
|
||||||
|
const bodyParam = this.formatBody(webhookContentType, body);
|
||||||
|
const options = {
|
||||||
|
method: webhookMethod,
|
||||||
|
headers,
|
||||||
|
...this.gotOption,
|
||||||
|
allowGetBody: true,
|
||||||
|
...bodyParam,
|
||||||
|
};
|
||||||
|
const res = await got(formatUrl, options);
|
||||||
|
return String(res.statusCode).startsWith('20');
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatBody(contentType: string, body: any): object {
|
||||||
|
if (!body) return {};
|
||||||
|
switch (contentType) {
|
||||||
|
case 'application/json':
|
||||||
|
return { json: body };
|
||||||
|
case 'multipart/form-data':
|
||||||
|
return { form: body };
|
||||||
|
case 'application/x-www-form-urlencoded':
|
||||||
|
return { body };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatNotifyContent(url: string, body: string) {
|
||||||
|
if (!url.includes('$title') && !body.includes('$title')) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formatUrl: url
|
||||||
|
.replaceAll('$title', encodeURIComponent(this.title))
|
||||||
|
.replaceAll('$content', encodeURIComponent(this.content)),
|
||||||
|
formatBody: body
|
||||||
|
.replaceAll('$title', this.title)
|
||||||
|
.replaceAll('$content', this.content),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SockService from './sock';
|
|||||||
import CronService from './cron';
|
import CronService from './cron';
|
||||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
import { LOG_END_SYMBOL } from '../config/const';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ScriptService {
|
export default class ScriptService {
|
||||||
@@ -55,7 +56,7 @@ export default class ScriptService {
|
|||||||
type: 'manuallyRunScript',
|
type: 'manuallyRunScript',
|
||||||
message: `${str}\n## 执行结束... ${new Date()
|
message: `${str}\n## 执行结束... ${new Date()
|
||||||
.toLocaleString('zh', { hour12: false })
|
.toLocaleString('zh', { hour12: false })
|
||||||
.replace(' 24:', ' 00:')} `,
|
.replace(' 24:', ' 00:')}${LOG_END_SYMBOL}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { code: 200 };
|
return { code: 200 };
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { SimpleIntervalSchedule } from 'toad-scheduler';
|
|||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import SshKeyService from './sshKey';
|
import SshKeyService from './sshKey';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { LOG_END_SYMBOL } from '../config/const';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SubscriptionService {
|
export default class SubscriptionService {
|
||||||
@@ -62,7 +63,10 @@ export default class SubscriptionService {
|
|||||||
try {
|
try {
|
||||||
const result = await SubscriptionModel.findAll({
|
const result = await SubscriptionModel.findAll({
|
||||||
where: query,
|
where: query,
|
||||||
order: [['createdAt', 'DESC']],
|
order: [
|
||||||
|
['is_disabled', 'ASC'],
|
||||||
|
['createdAt', 'DESC'],
|
||||||
|
],
|
||||||
});
|
});
|
||||||
return result as any;
|
return result as any;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -229,7 +233,7 @@ export default class SubscriptionService {
|
|||||||
absolutePath,
|
absolutePath,
|
||||||
`\n## 执行结束... ${endTime.format(
|
`\n## 执行结束... ${endTime.format(
|
||||||
'YYYY-MM-DD HH:mm:ss',
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
)} 耗时 ${diff} 秒`,
|
)} 耗时 ${diff} 秒${LOG_END_SYMBOL}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await SubscriptionModel.update(
|
await SubscriptionModel.update(
|
||||||
@@ -350,7 +354,7 @@ export default class SubscriptionService {
|
|||||||
|
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(
|
||||||
`${absolutePath}`,
|
`${absolutePath}`,
|
||||||
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `,
|
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')}${LOG_END_SYMBOL}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-12
@@ -2,7 +2,6 @@ import { Service, Inject } from 'typedi';
|
|||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import _ from 'lodash';
|
|
||||||
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
||||||
import { NotificationInfo } from '../data/notify';
|
import { NotificationInfo } from '../data/notify';
|
||||||
import NotificationService from './notify';
|
import NotificationService from './notify';
|
||||||
@@ -10,6 +9,7 @@ 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 { promiseExec } from '../config/util';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SystemService {
|
export default class SystemService {
|
||||||
@@ -70,9 +70,8 @@ export default class SystemService {
|
|||||||
};
|
};
|
||||||
await this.scheduleService.cancelIntervalTask(cron);
|
await this.scheduleService.cancelIntervalTask(cron);
|
||||||
if (frequency > 0) {
|
if (frequency > 0) {
|
||||||
await this.scheduleService.createIntervalTask(cron, {
|
this.scheduleService.createIntervalTask(cron, {
|
||||||
days: frequency,
|
days: frequency,
|
||||||
runImmediately: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { code: 200, data: { ...cron } };
|
return { code: 200, data: { ...cron } };
|
||||||
@@ -89,11 +88,9 @@ export default class SystemService {
|
|||||||
let lastVersion = '';
|
let lastVersion = '';
|
||||||
let lastLog = '';
|
let lastLog = '';
|
||||||
try {
|
try {
|
||||||
const result = await got.get(config.lastVersionFile, {
|
const lastVersionFileContent = await promiseExec(
|
||||||
timeout: 6000,
|
`curl ${config.lastVersionFile}?t=${Date.now()}`,
|
||||||
retry: 0,
|
);
|
||||||
});
|
|
||||||
const lastVersionFileContent = result.body;
|
|
||||||
lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
||||||
lastLog = lastVersionFileContent.match(logRegx)
|
lastLog = lastVersionFileContent.match(logRegx)
|
||||||
? lastVersionFileContent.match(logRegx)![1]
|
? lastVersionFileContent.match(logRegx)![1]
|
||||||
@@ -138,10 +135,6 @@ export default class SystemService {
|
|||||||
public async updateSystem() {
|
public async updateSystem() {
|
||||||
const cp = spawn('ql -l update', { shell: '/bin/bash' });
|
const cp = spawn('ql -l update', { shell: '/bin/bash' });
|
||||||
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'updateSystemVersion',
|
|
||||||
message: `开始更新系统`,
|
|
||||||
});
|
|
||||||
cp.stdout.on('data', (data) => {
|
cp.stdout.on('data', (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'updateSystemVersion',
|
type: 'updateSystemVersion',
|
||||||
|
|||||||
+33
-20
@@ -3,7 +3,6 @@ import winston from 'winston';
|
|||||||
import { createRandomString, getNetIp, getPlatform } from '../config/util';
|
import { createRandomString, getNetIp, getPlatform } from '../config/util';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import _ from 'lodash';
|
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { authenticator } from '@otplib/preset-default';
|
import { authenticator } from '@otplib/preset-default';
|
||||||
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
||||||
@@ -65,32 +64,37 @@ export default class UserService {
|
|||||||
return this.initAuthInfo();
|
return this.initAuthInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retries > 2 && Date.now() - lastlogon < Math.pow(3, retries) * 1000) {
|
const retriesTime = Math.pow(3, retries) * 1000;
|
||||||
|
if (retries > 2 && timestamp - lastlogon < retriesTime) {
|
||||||
|
const waitTime = Math.ceil(
|
||||||
|
(retriesTime - (timestamp - lastlogon)) / 1000,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
code: 410,
|
code: 410,
|
||||||
message: `失败次数过多,请${Math.round(
|
message: `失败次数过多,请${waitTime}秒后重试`,
|
||||||
(Math.pow(3, retries) * 1000 - Date.now() + lastlogon) / 1000,
|
data: waitTime,
|
||||||
)}秒后重试`,
|
};
|
||||||
data: Math.round(
|
}
|
||||||
(Math.pow(3, retries) * 1000 - Date.now() + lastlogon) / 1000,
|
|
||||||
),
|
if (
|
||||||
|
username === cUsername &&
|
||||||
|
password === cPassword &&
|
||||||
|
twoFactorActivated &&
|
||||||
|
needTwoFactor
|
||||||
|
) {
|
||||||
|
this.updateAuthInfo(content, {
|
||||||
|
isTwoFactorChecking: true,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 420,
|
||||||
|
message: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ip, address } = await getNetIp(req);
|
const { ip, address } = await getNetIp(req);
|
||||||
if (username === cUsername && password === cPassword) {
|
if (username === cUsername && password === cPassword) {
|
||||||
if (twoFactorActivated && needTwoFactor) {
|
|
||||||
this.updateAuthInfo(content, {
|
|
||||||
isTwoFactorChecking: true,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
code: 420,
|
|
||||||
message: '请输入两步验证token',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = createRandomString(50, 100);
|
const data = createRandomString(50, 100);
|
||||||
const expiration = twoFactorActivated ? 30 : 3;
|
const expiration = twoFactorActivated ? 60 : 20;
|
||||||
let token = jwt.sign({ data }, config.secret as any, {
|
let token = jwt.sign({ data }, config.secret as any, {
|
||||||
expiresIn: 60 * 60 * 24 * expiration,
|
expiresIn: 60 * 60 * 24 * expiration,
|
||||||
algorithm: 'HS384',
|
algorithm: 'HS384',
|
||||||
@@ -155,7 +159,16 @@ export default class UserService {
|
|||||||
status: LoginStatus.fail,
|
status: LoginStatus.fail,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return { code: 400, message: config.authError };
|
if (retries > 2) {
|
||||||
|
const waitTime = Math.round(Math.pow(3, retries + 1));
|
||||||
|
return {
|
||||||
|
code: 410,
|
||||||
|
message: `失败次数过多,请${waitTime}秒后重试`,
|
||||||
|
data: waitTime,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { code: 400, message: config.authError };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.initAuthInfo();
|
return this.initAuthInfo();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ dir_shell=/ql/shell
|
|||||||
. $dir_shell/share.sh
|
. $dir_shell/share.sh
|
||||||
link_shell
|
link_shell
|
||||||
|
|
||||||
|
export isFirstStartServer=true
|
||||||
|
|
||||||
echo -e "======================1. 检测配置文件========================\n"
|
echo -e "======================1. 检测配置文件========================\n"
|
||||||
make_dir /etc/nginx/conf.d
|
make_dir /etc/nginx/conf.d
|
||||||
make_dir /run/nginx
|
make_dir /run/nginx
|
||||||
@@ -12,12 +14,7 @@ cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
|||||||
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
|
|
||||||
if [[ $PipMirror ]]; then
|
patch_version &>/dev/null
|
||||||
pip3 config set global.index-url $PipMirror
|
|
||||||
fi
|
|
||||||
if [[ $NpmMirror ]]; then
|
|
||||||
npm config set registry $NpmMirror
|
|
||||||
fi
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo -e "======================2. 安装依赖========================\n"
|
echo -e "======================2. 安装依赖========================\n"
|
||||||
@@ -45,13 +42,13 @@ echo -e "定时任务启动成功...\n"
|
|||||||
|
|
||||||
if [[ $AutoStartBot == true ]]; then
|
if [[ $AutoStartBot == true ]]; then
|
||||||
echo -e "======================7. 启动bot========================\n"
|
echo -e "======================7. 启动bot========================\n"
|
||||||
nohup ql bot >>$dir_log/start.log 2>&1 &
|
nohup ql bot >$dir_log/bot.log 2>&1 &
|
||||||
echo -e "bot后台启动中...\n"
|
echo -e "bot后台启动中...\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $EnableExtraShell == true ]]; then
|
if [[ $EnableExtraShell == true ]]; then
|
||||||
echo -e "======================8. 执行自定义脚本========================\n"
|
echo -e "======================8. 执行自定义脚本========================\n"
|
||||||
nohup ql extra >>$dir_log/start.log 2>&1 &
|
nohup ql extra >$dir_log/extra.log 2>&1 &
|
||||||
echo -e "自定义脚本后台执行中...\n"
|
echo -e "自定义脚本后台执行中...\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+34
-17
@@ -2,16 +2,16 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "concurrently -n w: npm:start:*",
|
"start": "concurrently -n w: npm:start:*",
|
||||||
"start:front": "umi dev",
|
"start:front": "max dev",
|
||||||
"start:back": "nodemon",
|
"start:back": "nodemon",
|
||||||
"start:public": "ts-node back/public.ts",
|
"start:public": "ts-node back/public.ts",
|
||||||
"build:front": "umi build",
|
"build:front": "max build",
|
||||||
"build:back": "tsc -p tsconfig.back.json",
|
"build:back": "tsc -p tsconfig.back.json",
|
||||||
"panel": "npm run build:back && node static/build/app.js",
|
"panel": "npm run build:back && node static/build/app.js",
|
||||||
"schedule": "npm run build:back && node static/build/schedule.js",
|
"schedule": "npm run build:back && node static/build/schedule.js",
|
||||||
"public": "npm run build:back && node static/build/public.js",
|
"public": "npm run build:back && node static/build/public.js",
|
||||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||||
"postinstall": "umi generate tmp 2>/dev/null || true",
|
"postinstall": "max setup 2>/dev/null || true",
|
||||||
"test": "umi-test",
|
"test": "umi-test",
|
||||||
"test:coverage": "umi-test --coverage"
|
"test:coverage": "umi-test --coverage"
|
||||||
},
|
},
|
||||||
@@ -28,9 +28,26 @@
|
|||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
|
"ignoreMissing": [
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"antd",
|
||||||
|
"dva",
|
||||||
|
"postcss",
|
||||||
|
"webpack",
|
||||||
|
"eslint",
|
||||||
|
"stylelint",
|
||||||
|
"redux",
|
||||||
|
"@babel/core",
|
||||||
|
"monaco-editor",
|
||||||
|
"rc-field-form",
|
||||||
|
"@types/lodash.merge",
|
||||||
|
"rollup"
|
||||||
|
],
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
"react": "17",
|
"react": "18",
|
||||||
"react-dom": "17"
|
"react-dom": "18",
|
||||||
|
"dva-core": "2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -48,6 +65,7 @@
|
|||||||
"express": "^4.17.3",
|
"express": "^4.17.3",
|
||||||
"express-jwt": "^6.1.1",
|
"express-jwt": "^6.1.1",
|
||||||
"express-urlrewrite": "^1.4.0",
|
"express-urlrewrite": "^1.4.0",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"hpagent": "^0.1.2",
|
"hpagent": "^0.1.2",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
@@ -59,7 +77,7 @@
|
|||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
"p-queue": "7.2.0",
|
"p-queue": "7.2.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sequelize": "^6.20.1",
|
"sequelize": "^6.25.3",
|
||||||
"serve-handler": "^6.1.3",
|
"serve-handler": "^6.1.3",
|
||||||
"sockjs": "^0.3.24",
|
"sockjs": "^0.3.24",
|
||||||
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
|
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
|
||||||
@@ -72,32 +90,31 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
"@ant-design/pro-layout": "^6.33.1",
|
"@ant-design/pro-layout": "^6.33.1",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "4.2.1",
|
||||||
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
"@sentry/react": "^7.12.1",
|
"@sentry/react": "^7.12.1",
|
||||||
"@types/body-parser": "^1.19.2",
|
"@types/body-parser": "^1.19.2",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/express-jwt": "^6.0.4",
|
"@types/express-jwt": "^6.0.4",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"@types/lodash": "^4.14.179",
|
"@types/lodash": "^4.14.185",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/nedb": "^1.8.12",
|
"@types/nedb": "^1.8.12",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^17.0.21",
|
||||||
"@types/node-schedule": "^1.3.2",
|
"@types/node-schedule": "^1.3.2",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/qrcode.react": "^1.0.2",
|
"@types/qrcode.react": "^1.0.2",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^18.0.20",
|
||||||
"@types/react-dom": "^17.0.13",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@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.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@umijs/plugin-antd": "^0.15.0",
|
"@umijs/max": "^4.0.21",
|
||||||
"@umijs/plugin-esbuild": "^1.4.1",
|
|
||||||
"@umijs/ssr-darkreader": "^4.9.45",
|
"@umijs/ssr-darkreader": "^4.9.45",
|
||||||
"@umijs/test": "^3.5.21",
|
|
||||||
"ansi-to-react": "^6.1.6",
|
"ansi-to-react": "^6.1.6",
|
||||||
"antd": "^4.20.5",
|
"antd": "^4.23.0",
|
||||||
"antd-img-crop": "^4.2.3",
|
"antd-img-crop": "^4.2.3",
|
||||||
"codemirror": "^5.65.2",
|
"codemirror": "^5.65.2",
|
||||||
"compression-webpack-plugin": "9.2.0",
|
"compression-webpack-plugin": "9.2.0",
|
||||||
@@ -107,19 +124,19 @@
|
|||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"qiniu": "^7.4.0",
|
"qiniu": "^7.4.0",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
|
"query-string": "^7.1.1",
|
||||||
"rc-tween-one": "^3.0.6",
|
"rc-tween-one": "^3.0.6",
|
||||||
"react": "17.x",
|
"react": "18.2.0",
|
||||||
"react-codemirror2": "^7.2.1",
|
"react-codemirror2": "^7.2.1",
|
||||||
"react-diff-viewer": "^3.1.1",
|
"react-diff-viewer": "^3.1.1",
|
||||||
"react-dnd": "^14.0.2",
|
"react-dnd": "^14.0.2",
|
||||||
"react-dnd-html5-backend": "^14.0.0",
|
"react-dnd-html5-backend": "^14.0.0",
|
||||||
"react-dom": "17.x",
|
"react-dom": "18.2.0",
|
||||||
"react-split-pane": "^0.1.92",
|
"react-split-pane": "^0.1.92",
|
||||||
"sockjs-client": "^1.6.0",
|
"sockjs-client": "^1.6.0",
|
||||||
"ts-node": "^10.6.0",
|
"ts-node": "^10.6.0",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.6.2",
|
"typescript": "^4.6.2",
|
||||||
"umi": "^3.5.21",
|
|
||||||
"umi-request": "^1.4.0",
|
"umi-request": "^1.4.0",
|
||||||
"vh-check": "^2.0.5",
|
"vh-check": "^2.0.5",
|
||||||
"webpack": "^5.70.0",
|
"webpack": "^5.70.0",
|
||||||
|
|||||||
+17
-3
@@ -19,6 +19,11 @@ RepoFileExtensions="js py"
|
|||||||
## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890
|
## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890
|
||||||
ProxyUrl=""
|
ProxyUrl=""
|
||||||
|
|
||||||
|
## 资源告警阙值,默认CPU 80%、内存80%、磁盘90%
|
||||||
|
CpuWarn=80
|
||||||
|
MemoryWarn=80
|
||||||
|
DiskWarn=90
|
||||||
|
|
||||||
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||||
CommandTimeoutTime="1h"
|
CommandTimeoutTime="1h"
|
||||||
|
|
||||||
@@ -46,10 +51,10 @@ AutoStartBot=""
|
|||||||
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
||||||
BotRepoUrl=""
|
BotRepoUrl=""
|
||||||
|
|
||||||
## 安装python依赖时指定pip源,默认使用清华源
|
## 安装python依赖时指定pip源
|
||||||
PipMirror="https://pypi.doubanio.com/simple/"
|
PipMirror="https://pypi.doubanio.com/simple/"
|
||||||
|
|
||||||
## 安装node依赖时指定pip源,默认使用清华源
|
## 安装node依赖时指定npm源
|
||||||
NpmMirror="https://registry.npmmirror.com"
|
NpmMirror="https://registry.npmmirror.com"
|
||||||
|
|
||||||
## 通知环境变量
|
## 通知环境变量
|
||||||
@@ -62,7 +67,7 @@ export PUSH_KEY=""
|
|||||||
## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123
|
## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123
|
||||||
export BARK_PUSH=""
|
export BARK_PUSH=""
|
||||||
## 下方填写推送图标设置,自定义推送图标(需iOS15或以上)
|
## 下方填写推送图标设置,自定义推送图标(需iOS15或以上)
|
||||||
export BARK_ICON="http://qn.whyour.cn/logo.png"
|
export BARK_ICON="https://qn.whyour.cn/logo.png"
|
||||||
## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声
|
## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声
|
||||||
export BARK_SOUND=""
|
export BARK_SOUND=""
|
||||||
## 下方填写推送消息分组,默认为"QingLong"
|
## 下方填写推送消息分组,默认为"QingLong"
|
||||||
@@ -146,4 +151,13 @@ export DEER_KEY=""
|
|||||||
export CHAT_URL=""
|
export CHAT_URL=""
|
||||||
export CHAT_TOKEN=""
|
export CHAT_TOKEN=""
|
||||||
|
|
||||||
|
## 13. aibotk
|
||||||
|
## 官方说明文档:http://wechat.aibotk.com/oapi/oapi?from=ql
|
||||||
|
## aibotk_key (必填)填写智能微秘书个人中心的apikey
|
||||||
|
export AIBOTK_KEY=""
|
||||||
|
## aibotk_type (必填)填写发送的目标 room 或 contact, 填其他的不生效
|
||||||
|
export AIBOTK_TYPE=""
|
||||||
|
## aibotk_name (必填)填写群名或用户昵称,和上面的type类型要对应
|
||||||
|
export AIBOTK_NAME=""
|
||||||
|
|
||||||
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
||||||
|
|||||||
+189
-154
@@ -49,7 +49,7 @@ let CHAT_TOKEN = '';
|
|||||||
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
||||||
let BARK_PUSH = '';
|
let BARK_PUSH = '';
|
||||||
//BARK app推送图标,自定义推送图标(需iOS15或以上)
|
//BARK app推送图标,自定义推送图标(需iOS15或以上)
|
||||||
let BARK_ICON = 'http://qn.whyour.cn/logo.png';
|
let BARK_ICON = 'https://qn.whyour.cn/logo.png';
|
||||||
//BARK app推送铃声,铃声列表去APP查看复制填写
|
//BARK app推送铃声,铃声列表去APP查看复制填写
|
||||||
let BARK_SOUND = '';
|
let BARK_SOUND = '';
|
||||||
//BARK app推送消息的分组, 默认为"QingLong"
|
//BARK app推送消息的分组, 默认为"QingLong"
|
||||||
@@ -110,6 +110,20 @@ let PUSH_PLUS_USER = '';
|
|||||||
let QQ_SKEY = '';
|
let QQ_SKEY = '';
|
||||||
let QQ_MODE = '';
|
let QQ_MODE = '';
|
||||||
|
|
||||||
|
// =======================================智能微秘书设置区域=======================================
|
||||||
|
//官方文档:http://wechat.aibotk.com/docs/about
|
||||||
|
//AIBOTK_KEY: 填写智能微秘书个人中心的apikey
|
||||||
|
//AIBOTK_TYPE:填写发送的目标 room 或 contact, 填其他的不生效
|
||||||
|
//AIBOTK_NAME: 填写群名或用户昵称,和上面的type类型要对应
|
||||||
|
let AIBOTK_KEY = '';
|
||||||
|
let AIBOTK_TYPE = '';
|
||||||
|
let AIBOTK_NAME = '';
|
||||||
|
|
||||||
|
// =======================================飞书机器人设置区域=======================================
|
||||||
|
//官方文档:https://www.feishu.cn/hc/zh-CN/articles/360024984973
|
||||||
|
//FSKEY 飞书机器人的 FSKEY
|
||||||
|
let FSKEY = '';
|
||||||
|
|
||||||
//==========================云端环境变量的判断与接收=========================
|
//==========================云端环境变量的判断与接收=========================
|
||||||
if (process.env.GOTIFY_URL) {
|
if (process.env.GOTIFY_URL) {
|
||||||
GOTIFY_URL = process.env.GOTIFY_URL;
|
GOTIFY_URL = process.env.GOTIFY_URL;
|
||||||
@@ -220,6 +234,20 @@ if (process.env.PUSH_PLUS_TOKEN) {
|
|||||||
if (process.env.PUSH_PLUS_USER) {
|
if (process.env.PUSH_PLUS_USER) {
|
||||||
PUSH_PLUS_USER = process.env.PUSH_PLUS_USER;
|
PUSH_PLUS_USER = process.env.PUSH_PLUS_USER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.AIBOTK_KEY) {
|
||||||
|
AIBOTK_KEY = process.env.AIBOTK_KEY;
|
||||||
|
}
|
||||||
|
if (process.env.AIBOTK_TYPE) {
|
||||||
|
AIBOTK_TYPE = process.env.AIBOTK_TYPE;
|
||||||
|
}
|
||||||
|
if (process.env.AIBOTK_NAME) {
|
||||||
|
AIBOTK_NAME = process.env.AIBOTK_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.FSKEY) {
|
||||||
|
FSKEY = process.env.FSKEY;
|
||||||
|
}
|
||||||
//==========================云端环境变量的判断与接收=========================
|
//==========================云端环境变量的判断与接收=========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -255,6 +283,8 @@ async function sendNotify(
|
|||||||
gotifyNotify(text, desp), //gotify
|
gotifyNotify(text, desp), //gotify
|
||||||
ChatNotify(text, desp), //synolog chat
|
ChatNotify(text, desp), //synolog chat
|
||||||
PushDeerNotify(text, desp), //PushDeer
|
PushDeerNotify(text, desp), //PushDeer
|
||||||
|
aibotkNotify(text, desp), //智能微秘书
|
||||||
|
fsBotNotify(text, desp), //飞书机器人
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +325,7 @@ function gotifyNotify(text, desp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function gobotNotify(text, desp, time = 2100) {
|
function gobotNotify(text, desp) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (GOBOT_URL) {
|
if (GOBOT_URL) {
|
||||||
const options = {
|
const options = {
|
||||||
@@ -306,38 +336,34 @@ function gobotNotify(text, desp, time = 2100) {
|
|||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
$.post(options, (err, resp, data) => {
|
||||||
$.post(options, (err, resp, data) => {
|
try {
|
||||||
try {
|
if (err) {
|
||||||
if (err) {
|
console.log('发送go-cqhttp通知调用API失败!!\n');
|
||||||
console.log('发送go-cqhttp通知调用API失败!!\n');
|
console.log(err);
|
||||||
console.log(err);
|
} else {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
console.log('go-cqhttp发送通知消息成功🎉\n');
|
||||||
|
} else if (data.retcode === 100) {
|
||||||
|
console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`);
|
||||||
} else {
|
} else {
|
||||||
data = JSON.parse(data);
|
console.log(`go-cqhttp发送通知消息异常\n${JSON.stringify(data)}`);
|
||||||
if (data.retcode === 0) {
|
|
||||||
console.log('go-cqhttp发送通知消息成功🎉\n');
|
|
||||||
} else if (data.retcode === 100) {
|
|
||||||
console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`go-cqhttp发送通知消息异常\n${JSON.stringify(data)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
$.logErr(e, resp);
|
|
||||||
} finally {
|
|
||||||
resolve(data);
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
}, time);
|
$.logErr(e, resp);
|
||||||
|
} finally {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function serverNotify(text, desp, time = 2100) {
|
function serverNotify(text, desp) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (SCKEY) {
|
if (SCKEY) {
|
||||||
//微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换
|
//微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换
|
||||||
@@ -352,33 +378,29 @@ function serverNotify(text, desp, time = 2100) {
|
|||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
$.post(options, (err, resp, data) => {
|
||||||
$.post(options, (err, resp, data) => {
|
try {
|
||||||
try {
|
if (err) {
|
||||||
if (err) {
|
console.log('发送通知调用API失败!!\n');
|
||||||
console.log('发送通知调用API失败!!\n');
|
console.log(err);
|
||||||
console.log(err);
|
} else {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
//server酱和Server酱·Turbo版的返回json格式不太一样
|
||||||
|
if (data.errno === 0 || data.data.errno === 0) {
|
||||||
|
console.log('server酱发送通知消息成功🎉\n');
|
||||||
|
} else if (data.errno === 1024) {
|
||||||
|
// 一分钟内发送相同的内容会触发
|
||||||
|
console.log(`server酱发送通知消息异常: ${data.errmsg}\n`);
|
||||||
} else {
|
} else {
|
||||||
data = JSON.parse(data);
|
console.log(`server酱发送通知消息异常\n${JSON.stringify(data)}`);
|
||||||
//server酱和Server酱·Turbo版的返回json格式不太一样
|
|
||||||
if (data.errno === 0 || data.data.errno === 0) {
|
|
||||||
console.log('server酱发送通知消息成功🎉\n');
|
|
||||||
} else if (data.errno === 1024) {
|
|
||||||
// 一分钟内发送相同的内容会触发
|
|
||||||
console.log(`server酱发送通知消息异常: ${data.errmsg}\n`);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`server酱发送通知消息异常\n${JSON.stringify(data)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
$.logErr(e, resp);
|
|
||||||
} finally {
|
|
||||||
resolve(data);
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
}, time);
|
$.logErr(e, resp);
|
||||||
|
} finally {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -398,35 +420,29 @@ function PushDeerNotify(text, desp) {
|
|||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
$.post(
|
$.post(options, (err, resp, data) => {
|
||||||
options,
|
try {
|
||||||
(err, resp, data) => {
|
if (err) {
|
||||||
try {
|
console.log('发送通知调用API失败!!\n');
|
||||||
if (err) {
|
console.log(err);
|
||||||
console.log('发送通知调用API失败!!\n');
|
} else {
|
||||||
console.log(err);
|
data = JSON.parse(data);
|
||||||
|
// 通过返回的result的长度来判断是否成功
|
||||||
|
if (
|
||||||
|
data.content.result.length !== undefined &&
|
||||||
|
data.content.result.length > 0
|
||||||
|
) {
|
||||||
|
console.log('PushDeer发送通知消息成功🎉\n');
|
||||||
} else {
|
} else {
|
||||||
data = JSON.parse(data);
|
console.log(`PushDeer发送通知消息异常\n${JSON.stringify(data)}`);
|
||||||
// 通过返回的result的长度来判断是否成功
|
|
||||||
if (
|
|
||||||
data.content.result.length !== undefined &&
|
|
||||||
data.content.result.length > 0
|
|
||||||
) {
|
|
||||||
console.log('PushDeer发送通知消息成功🎉\n');
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`PushDeer发送通知消息异常\n${JSON.stringify(data)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
$.logErr(e, resp);
|
|
||||||
} finally {
|
|
||||||
resolve(data);
|
|
||||||
}
|
}
|
||||||
},
|
} catch (e) {
|
||||||
time,
|
$.logErr(e, resp);
|
||||||
);
|
} finally {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -470,83 +486,6 @@ function ChatNotify(text, desp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function CoolPush(text, desp) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
if (QQ_SKEY) {
|
|
||||||
let options = {
|
|
||||||
url: `https://push.xuthus.cc/${QQ_MODE}/${QQ_SKEY}`,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 已知敏感词
|
|
||||||
text = text.replace(/京豆/g, '豆豆');
|
|
||||||
desp = desp.replace(/京豆/g, '');
|
|
||||||
desp = desp.replace(/🐶/g, '');
|
|
||||||
desp = desp.replace(/红包/g, 'H包');
|
|
||||||
|
|
||||||
switch (QQ_MODE) {
|
|
||||||
case 'email':
|
|
||||||
options.json = {
|
|
||||||
t: text,
|
|
||||||
c: desp,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
options.body = `${text}\n\n${desp}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pushMode = function (t) {
|
|
||||||
switch (t) {
|
|
||||||
case 'send':
|
|
||||||
return '个人';
|
|
||||||
case 'group':
|
|
||||||
return 'QQ群';
|
|
||||||
case 'wx':
|
|
||||||
return '微信';
|
|
||||||
case 'ww':
|
|
||||||
return '企业微信';
|
|
||||||
case 'email':
|
|
||||||
return '邮件';
|
|
||||||
default:
|
|
||||||
return '未知方式';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.post(options, (err, resp, data) => {
|
|
||||||
try {
|
|
||||||
if (err) {
|
|
||||||
console.log(`发送${pushMode(QQ_MODE)}通知调用API失败!!\n`);
|
|
||||||
console.log(err);
|
|
||||||
} else {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
if (data.code === 200) {
|
|
||||||
console.log(`酷推发送${pushMode(QQ_MODE)}通知消息成功🎉\n`);
|
|
||||||
} else if (data.code === 400) {
|
|
||||||
console.log(
|
|
||||||
`QQ酷推(Cool Push)发送${pushMode(QQ_MODE)}推送失败:${
|
|
||||||
data.msg
|
|
||||||
}\n`,
|
|
||||||
);
|
|
||||||
} else if (data.code === 503) {
|
|
||||||
console.log(`QQ酷推出错,${data.message}:${data.data}\n`);
|
|
||||||
} else {
|
|
||||||
console.log(`酷推推送异常: ${JSON.stringify(data)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
$.logErr(e, resp);
|
|
||||||
} finally {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function BarkNotify(text, desp, params = {}) {
|
function BarkNotify(text, desp, params = {}) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (BARK_PUSH) {
|
if (BARK_PUSH) {
|
||||||
@@ -649,7 +588,7 @@ function ddBotNotify(text, desp) {
|
|||||||
json: {
|
json: {
|
||||||
msgtype: 'text',
|
msgtype: 'text',
|
||||||
text: {
|
text: {
|
||||||
content: ` ${text}\n\n${desp}`,
|
content: `${text}\n\n${desp}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
@@ -716,7 +655,7 @@ function qywxBotNotify(text, desp) {
|
|||||||
json: {
|
json: {
|
||||||
msgtype: 'text',
|
msgtype: 'text',
|
||||||
text: {
|
text: {
|
||||||
content: ` ${text}\n\n${desp}`,
|
content: `${text}\n\n${desp}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
@@ -983,6 +922,102 @@ function pushPlusNotify(text, desp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function aibotkNotify(text, desp) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (AIBOTK_KEY && AIBOTK_TYPE && AIBOTK_NAME) {
|
||||||
|
let json = {};
|
||||||
|
let url = '';
|
||||||
|
switch (AIBOTK_TYPE) {
|
||||||
|
case 'room':
|
||||||
|
url = 'https://api-bot.aibotk.com/openapi/v1/chat/room';
|
||||||
|
json = {
|
||||||
|
apiKey: `${AIBOTK_KEY}`,
|
||||||
|
roomName: `${AIBOTK_NAME}`,
|
||||||
|
message: {
|
||||||
|
type: 1,
|
||||||
|
content: `【青龙快讯】\n\n${text}\n${desp}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'contact':
|
||||||
|
url = 'https://api-bot.aibotk.com/openapi/v1/chat/contact';
|
||||||
|
json = {
|
||||||
|
apiKey: `${AIBOTK_KEY}`,
|
||||||
|
name: `${AIBOTK_NAME}`,
|
||||||
|
message: {
|
||||||
|
type: 1,
|
||||||
|
content: `【青龙快讯】\n\n${text}\n${desp}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const options = {
|
||||||
|
url: url,
|
||||||
|
json,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
};
|
||||||
|
$.post(options, (err, resp, data) => {
|
||||||
|
try {
|
||||||
|
if (err) {
|
||||||
|
console.log('智能微秘书发送通知消息失败!!\n');
|
||||||
|
console.log(err);
|
||||||
|
} else {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
if (data.code === 0) {
|
||||||
|
console.log('智能微秘书发送通知消息成功🎉。\n');
|
||||||
|
} else {
|
||||||
|
console.log(`${data.error}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.logErr(e, resp);
|
||||||
|
} finally {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fsBotNotify(text, desp) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (FSKEY) {
|
||||||
|
const options = {
|
||||||
|
url: `https://open.feishu.cn/open-apis/bot/v2/hook/${FSKEY}`,
|
||||||
|
json: { msg_type: 'text', content: { text: `${text}\n\n${desp}` } },
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
};
|
||||||
|
$.post(options, (err, resp, data) => {
|
||||||
|
try {
|
||||||
|
if (err) {
|
||||||
|
console.log('发送通知调用API失败!!\n');
|
||||||
|
console.log(err);
|
||||||
|
} else {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
if (data.StatusCode === 0) {
|
||||||
|
console.log('飞书发送通知消息成功🎉\n');
|
||||||
|
} else {
|
||||||
|
console.log(`${data.msg}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.logErr(e, resp);
|
||||||
|
} finally {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sendNotify,
|
sendNotify,
|
||||||
BARK_PUSH,
|
BARK_PUSH,
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ push_config = {
|
|||||||
'TG_PROXY_AUTH': '', # tg 代理认证参数
|
'TG_PROXY_AUTH': '', # tg 代理认证参数
|
||||||
'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST
|
'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST
|
||||||
'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT
|
'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT
|
||||||
|
|
||||||
|
'AIBOTK_KEY': '', # 智能微秘书 个人中心的apikey 文档地址:http://wechat.aibotk.com/docs/about
|
||||||
|
'AIBOTK_TYPE': '', # 智能微秘书 发送目标 room 或 contact
|
||||||
|
'AIBOTK_NAME': '', # 智能微秘书 发送群名 或者好友昵称和type要对应好
|
||||||
}
|
}
|
||||||
notify_function = []
|
notify_function = []
|
||||||
# fmt: on
|
# fmt: on
|
||||||
@@ -521,6 +525,39 @@ def telegram_bot(title: str, content: str) -> None:
|
|||||||
print("tg 推送失败!")
|
print("tg 推送失败!")
|
||||||
|
|
||||||
|
|
||||||
|
def aibotk(title: str, content: str) -> None:
|
||||||
|
"""
|
||||||
|
使用 智能微秘书 推送消息。
|
||||||
|
"""
|
||||||
|
if not push_config.get("AIBOTK_KEY") or not push_config.get("AIBOTK_TYPE") or not push_config.get("AIBOTK_NAME"):
|
||||||
|
print("智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送")
|
||||||
|
return
|
||||||
|
print("智能微秘书 服务启动")
|
||||||
|
|
||||||
|
if push_config.get("AIBOTK_TYPE") == 'room':
|
||||||
|
url = "https://api-bot.aibotk.com/openapi/v1/chat/room"
|
||||||
|
data = {
|
||||||
|
"apiKey": push_config.get("AIBOTK_KEY"),
|
||||||
|
"roomName": push_config.get("AIBOTK_NAME"),
|
||||||
|
"message": {"type": 1, "content": f'【青龙快讯】\n\n${title}\n${content}' }
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
url = "https://api-bot.aibotk.com/openapi/v1/chat/contact"
|
||||||
|
data = {
|
||||||
|
"apiKey": push_config.get("AIBOTK_KEY"),
|
||||||
|
"name": push_config.get("AIBOTK_NAME"),
|
||||||
|
"message": {"type": 1, "content": f'【青龙快讯】\n\n${title}\n${content}' }
|
||||||
|
}
|
||||||
|
body = json.dumps(data).encode(encoding="utf-8")
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
response = requests.post(url=url, data=body, headers=headers).json()
|
||||||
|
print(response)
|
||||||
|
if response["code"] == 0:
|
||||||
|
print("智能微秘书 推送成功!")
|
||||||
|
else:
|
||||||
|
print(f'智能微秘书 推送失败!{response["error"]}')
|
||||||
|
|
||||||
|
|
||||||
def one() -> str:
|
def one() -> str:
|
||||||
"""
|
"""
|
||||||
获取一条一言。
|
获取一条一言。
|
||||||
@@ -561,6 +598,8 @@ if push_config.get("QYWX_KEY"):
|
|||||||
notify_function.append(wecom_bot)
|
notify_function.append(wecom_bot)
|
||||||
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
|
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
|
||||||
notify_function.append(telegram_bot)
|
notify_function.append(telegram_bot)
|
||||||
|
if push_config.get("AIBOTK_KEY") and push_config.get("AIBOTK_TYPE") and push_config.get("AIBOTK_NAME"):
|
||||||
|
notify_function.append(aibotk)
|
||||||
|
|
||||||
|
|
||||||
def send(title: str, content: str) -> None:
|
def send(title: str, content: str) -> None:
|
||||||
|
|||||||
+160
-160
@@ -1,187 +1,187 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
get_token() {
|
get_token() {
|
||||||
token=$(cat $file_auth_token | jq -r .value)
|
token=$(cat $file_auth_token | jq -r .value)
|
||||||
}
|
}
|
||||||
|
|
||||||
add_cron_api() {
|
add_cron_api() {
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
if [[ $# -eq 1 ]]; then
|
if [[ $# -eq 1 ]]; then
|
||||||
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
||||||
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
||||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||||
else
|
else
|
||||||
local schedule=$1
|
local schedule=$1
|
||||||
local command=$2
|
local command=$2
|
||||||
local name=$3
|
local name=$3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||||
-H "Content-Type: application/json;charset=UTF-8" \
|
-H "Content-Type: application/json;charset=UTF-8" \
|
||||||
-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\"}" \
|
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\"}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo $api | jq -r .code)
|
code=$(echo $api | jq -r .code)
|
||||||
message=$(echo $api | jq -r .message)
|
message=$(echo $api | jq -r .message)
|
||||||
if [[ $code == 200 ]]; then
|
if [[ $code == 200 ]]; then
|
||||||
echo -e "$name -> 添加成功"
|
echo -e "$name -> 添加成功"
|
||||||
else
|
else
|
||||||
echo -e "$name -> 添加失败(${message})"
|
echo -e "$name -> 添加失败(${message})"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
update_cron_api() {
|
update_cron_api() {
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
if [[ $# -eq 1 ]]; then
|
if [[ $# -eq 1 ]]; then
|
||||||
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
||||||
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
||||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||||
local id=$(echo "$1" | awk -F ":" '{print $4}')
|
local id=$(echo "$1" | awk -F ":" '{print $4}')
|
||||||
else
|
else
|
||||||
local schedule=$1
|
local schedule=$1
|
||||||
local command=$2
|
local command=$2
|
||||||
local name=$3
|
local name=$3
|
||||||
local id=$4
|
local id=$4
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||||
-H "Content-Type: application/json;charset=UTF-8" \
|
-H "Content-Type: application/json;charset=UTF-8" \
|
||||||
-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)
|
||||||
message=$(echo $api | jq -r .message)
|
message=$(echo $api | jq -r .message)
|
||||||
if [[ $code == 200 ]]; then
|
if [[ $code == 200 ]]; then
|
||||||
echo -e "$name -> 更新成功"
|
echo -e "$name -> 更新成功"
|
||||||
else
|
else
|
||||||
echo -e "$name -> 更新失败(${message})"
|
echo -e "$name -> 更新失败(${message})"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
update_cron_command_api() {
|
update_cron_command_api() {
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
if [[ $# -eq 1 ]]; then
|
if [[ $# -eq 1 ]]; then
|
||||||
local command=$(echo "$1" | awk -F ":" '{print $1}')
|
local command=$(echo "$1" | awk -F ":" '{print $1}')
|
||||||
local id=$(echo "$1" | awk -F ":" '{print $2}')
|
local id=$(echo "$1" | awk -F ":" '{print $2}')
|
||||||
else
|
else
|
||||||
local command=$1
|
local command=$1
|
||||||
local id=$2
|
local id=$2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||||
-H "Content-Type: application/json;charset=UTF-8" \
|
-H "Content-Type: application/json;charset=UTF-8" \
|
||||||
-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)
|
||||||
message=$(echo $api | jq -r .message)
|
message=$(echo $api | jq -r .message)
|
||||||
if [[ $code == 200 ]]; then
|
if [[ $code == 200 ]]; then
|
||||||
echo -e "$command -> 更新成功"
|
echo -e "$command -> 更新成功"
|
||||||
else
|
else
|
||||||
echo -e "$command -> 更新失败(${message})"
|
echo -e "$command -> 更新失败(${message})"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
del_cron_api() {
|
del_cron_api() {
|
||||||
local ids=$1
|
local ids=$1
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-X 'DELETE' \
|
-X 'DELETE' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||||
-H "Content-Type: application/json;charset=UTF-8" \
|
-H "Content-Type: application/json;charset=UTF-8" \
|
||||||
-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 "[$ids]" \
|
--data-raw "[$ids]" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo $api | jq -r .code)
|
code=$(echo $api | jq -r .code)
|
||||||
message=$(echo $api | jq -r .message)
|
message=$(echo $api | jq -r .message)
|
||||||
if [[ $code == 200 ]]; then
|
if [[ $code == 200 ]]; then
|
||||||
echo -e "成功"
|
echo -e "成功"
|
||||||
else
|
else
|
||||||
echo -e "失败(${message})"
|
echo -e "失败(${message})"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
update_cron() {
|
update_cron() {
|
||||||
local ids="$1"
|
local ids="$1"
|
||||||
local status="$2"
|
local status="$2"
|
||||||
local pid="${3:-''}"
|
local pid="${3:-''}"
|
||||||
local logPath="$4"
|
local logPath="$4"
|
||||||
local lastExecutingTime="${5:-0}"
|
local lastExecutingTime="${5:-0}"
|
||||||
local runningTime="${6:-0}"
|
local runningTime="${6:-0}"
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||||
-H "Content-Type: application/json;charset=UTF-8" \
|
-H "Content-Type: application/json;charset=UTF-8" \
|
||||||
-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 "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \
|
--data-raw "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo $api | jq -r .code)
|
code=$(echo $api | jq -r .code)
|
||||||
message=$(echo $api | jq -r .message)
|
message=$(echo $api | jq -r .message)
|
||||||
if [[ $code != 200 ]]; then
|
if [[ $code != 200 ]]; then
|
||||||
echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path
|
echo -e "\n## 更新任务状态失败(${message})\n" >>$dir_log/$log_path
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_api() {
|
notify_api() {
|
||||||
local title=$1
|
local title=$1
|
||||||
local content=$2
|
local content=$2
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||||
-H "Content-Type: application/json;charset=UTF-8" \
|
-H "Content-Type: application/json;charset=UTF-8" \
|
||||||
-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 "{\"title\":\"$title\",\"content\":\"$content\"}" \
|
--data-raw "{\"title\":\"$title\",\"content\":\"$content\"}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo $api | jq -r .code)
|
code=$(echo $api | jq -r .code)
|
||||||
message=$(echo $api | jq -r .message)
|
message=$(echo $api | jq -r .message)
|
||||||
if [[ $code == 200 ]]; then
|
if [[ $code == 200 ]]; then
|
||||||
echo -e "通知发送成功"
|
echo -e "通知发送成功"
|
||||||
else
|
else
|
||||||
echo -e "通知失败(${message})"
|
echo -e "通知失败(${message})"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
get_token
|
get_token
|
||||||
|
|||||||
+4
-5
@@ -35,11 +35,10 @@ fi
|
|||||||
cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
|
cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
|
||||||
|
|
||||||
cd $dir_data
|
cd $dir_data
|
||||||
cat requirements.txt | while read LREAD
|
cat requirements.txt | while read LREAD; do
|
||||||
do
|
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
|
||||||
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
|
pip3 --default-timeout=100 install ${LREAD}
|
||||||
pip3 --default-timeout=100 install ${LREAD}
|
fi
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo -e "\npython3依赖安装成功...\n"
|
echo -e "\npython3依赖安装成功...\n"
|
||||||
|
|||||||
+272
@@ -0,0 +1,272 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
## 导入通用变量与函数
|
||||||
|
dir_shell=$QL_DIR/shell
|
||||||
|
. $dir_shell/share.sh
|
||||||
|
. $dir_shell/api.sh
|
||||||
|
|
||||||
|
random_delay() {
|
||||||
|
local random_delay_max=$RandomDelay
|
||||||
|
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
|
||||||
|
local file_param=$1
|
||||||
|
local file_extensions=${RandomDelayFileExtensions-"js"}
|
||||||
|
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
|
||||||
|
|
||||||
|
if [[ -n $file_extensions ]]; then
|
||||||
|
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
|
||||||
|
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local current_min
|
||||||
|
current_min=$(date "+%-M")
|
||||||
|
for minute in $ignored_minutes; do
|
||||||
|
if [[ $current_min -eq $minute ]]; then
|
||||||
|
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
|
||||||
|
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
|
||||||
|
sleep $delay_second
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
## scripts目录下所有可运行脚本数组
|
||||||
|
gen_array_scripts() {
|
||||||
|
local dir_current=$(pwd)
|
||||||
|
local i="-1"
|
||||||
|
cd $dir_scripts
|
||||||
|
for file in $(ls); do
|
||||||
|
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
|
||||||
|
let i++
|
||||||
|
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
|
||||||
|
array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1)
|
||||||
|
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
cd $dir_current
|
||||||
|
}
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
usage() {
|
||||||
|
gen_array_scripts
|
||||||
|
echo -e "task命令运行本程序自动添加进crontab的脚本,需要输入脚本的绝对路径或去掉 “$dir_scripts/” 目录后的相对路径(定时任务中请写作相对路径),用法为:"
|
||||||
|
echo -e "1.$cmd_task <file_name> # 依次执行,如果设置了随机延迟,将随机延迟一定秒数"
|
||||||
|
echo -e "2.$cmd_task <file_name> now # 依次执行,无论是否设置了随机延迟,均立即运行,前台会输出日志,同时记录在日志文件中"
|
||||||
|
echo -e "3.$cmd_task <file_name> conc <环境变量名称> <账号编号,空格分隔>(可选的) # 并发执行,无论是否设置了随机延迟,均立即运行,前台不产生日志,直接记录在日志文件中,且可指定账号执行"
|
||||||
|
echo -e "4.$cmd_task <file_name> desi <环境变量名称> <账号编号,空格分隔> # 指定账号执行,无论是否设置了随机延迟,均立即运行"
|
||||||
|
if [[ ${#array_scripts[*]} -gt 0 ]]; then
|
||||||
|
echo -e "\n当前有以下脚本可以运行:"
|
||||||
|
for ((i = 0; i < ${#array_scripts[*]}; i++)); do
|
||||||
|
echo -e "$(($i + 1)). ${array_scripts_name[i]}:${array_scripts[i]}"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo -e "\n暂无脚本可以执行"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
## run nohup,$1:文件名,不含路径,带后缀
|
||||||
|
run_nohup() {
|
||||||
|
local file_name=$1
|
||||||
|
nohup node $file_name &>$log_path &
|
||||||
|
}
|
||||||
|
|
||||||
|
check_server() {
|
||||||
|
cpu_use=$(top -b -n 1 | grep CPU | grep -v -E 'grep|PID' | awk '{print $2}' | cut -f 1 -d "%" | head -n 1)
|
||||||
|
|
||||||
|
mem_free=$(free -m | grep "Mem" | awk '{print $3}' | head -n 1)
|
||||||
|
mem_total=$(free -m | grep "Mem" | awk '{print $2}' | head -n 1)
|
||||||
|
mem_use=$(printf "%d%%" $((mem_free * 100 / mem_total)) | cut -f 1 -d "%" | head -n 1)
|
||||||
|
|
||||||
|
disk_use=$(df -P | grep /dev | grep -v -E '(tmp|boot|shm)' | awk '{print $5}' | cut -f 1 -d "%" | head -n 1)
|
||||||
|
|
||||||
|
if [[ $cpu_use -gt $cpu_warn ]] || [[ $mem_free -lt $mem_warn ]] || [[ $disk_use -gt $disk_warn ]]; then
|
||||||
|
local resource=$(top -b -n 1 | grep -v -E 'grep|Mem|idle|Load|tr' | awk '{$2="";$3="";$4="";$5="";$7="";print $0}' | head -n 10 | tr '\n' '|' | sed s/\|/\\\\n/g)
|
||||||
|
notify_api "服务器资源异常警告" "当前CPU占用 $cpu_use% 内存占用 $mem_use% 磁盘占用 $disk_use% \n资源占用详情 \n\n $resource"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_task_before() {
|
||||||
|
begin_time=$(format_time "$time_format" "$time")
|
||||||
|
begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
|
||||||
|
echo -e "## 开始执行... $begin_time\n"
|
||||||
|
|
||||||
|
[[ $is_macos -eq 0 ]] && check_server
|
||||||
|
|
||||||
|
if [[ -s $task_error_log_path ]]; then
|
||||||
|
eval cat $task_error_log_path $cmd
|
||||||
|
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
|
||||||
|
eval echo $cmd
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||||
|
. $file_task_before "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_task_after() {
|
||||||
|
. $file_task_after "$@"
|
||||||
|
|
||||||
|
local etime=$(date "+$time_format")
|
||||||
|
local end_time=$(format_time "$time_format" "$etime")
|
||||||
|
local end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||||
|
local diff_time=$(($end_timestamp - $begin_timestamp))
|
||||||
|
|
||||||
|
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
|
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time 秒"
|
||||||
|
echo -e "\n "
|
||||||
|
}
|
||||||
|
|
||||||
|
## 正常运行单个脚本,$1:传入参数
|
||||||
|
run_normal() {
|
||||||
|
local file_param=$1
|
||||||
|
if [[ $# -eq 1 ]]; then
|
||||||
|
random_delay "$file_param"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $dir_scripts
|
||||||
|
local relative_path="${file_param%/*}"
|
||||||
|
if [[ ${file_param} != /* ]] && [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||||
|
cd ${relative_path}
|
||||||
|
file_param=${file_param/$relative_path\//}
|
||||||
|
fi
|
||||||
|
|
||||||
|
$timeoutCmd $which_program $file_param
|
||||||
|
}
|
||||||
|
|
||||||
|
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
|
||||||
|
run_concurrent() {
|
||||||
|
local file_param="$1"
|
||||||
|
local env_param="$2"
|
||||||
|
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
|
||||||
|
if [[ ! $env_param ]]; then
|
||||||
|
echo -e "\n 缺少并发运行的环境变量参数"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local envs=$(eval echo "\$${env_param}")
|
||||||
|
local array=($(echo $envs | sed 's/&/ /g'))
|
||||||
|
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
|
||||||
|
local runArr=($(eval echo $tempArr))
|
||||||
|
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
|
||||||
|
|
||||||
|
local n=0
|
||||||
|
for i in ${runArr[@]}; do
|
||||||
|
array_run[n]=${array[$i - 1]}
|
||||||
|
let n++
|
||||||
|
done
|
||||||
|
|
||||||
|
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||||
|
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||||
|
|
||||||
|
local envs=$(eval echo "\$${env_param}")
|
||||||
|
local array=($(echo $envs | sed 's/&/ /g'))
|
||||||
|
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
|
||||||
|
|
||||||
|
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
|
||||||
|
for i in "${!array[@]}"; do
|
||||||
|
export ${env_param}=${array[i]}
|
||||||
|
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
|
||||||
|
eval $timeoutCmd $which_program $file_param &>$single_log_path &
|
||||||
|
done
|
||||||
|
|
||||||
|
wait
|
||||||
|
for i in "${!array[@]}"; do
|
||||||
|
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
|
||||||
|
cat $single_log_path
|
||||||
|
[[ -f $single_log_path ]] && rm -f $single_log_path
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
run_designated() {
|
||||||
|
local file_param="$1"
|
||||||
|
local env_param="$2"
|
||||||
|
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
|
||||||
|
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
|
||||||
|
echo -e "\n 缺少单独运行的参数 task xxx.js desi Test 1 3"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local envs=$(eval echo "\$${env_param}")
|
||||||
|
local array=($(echo $envs | sed 's/&/ /g'))
|
||||||
|
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
|
||||||
|
local runArr=($(eval echo $tempArr))
|
||||||
|
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
|
||||||
|
|
||||||
|
local n=0
|
||||||
|
for i in ${runArr[@]}; do
|
||||||
|
array_run[n]=${array[$i - 1]}
|
||||||
|
let n++
|
||||||
|
done
|
||||||
|
|
||||||
|
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||||
|
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||||
|
|
||||||
|
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
|
||||||
|
$timeoutCmd $which_program $file_param
|
||||||
|
}
|
||||||
|
|
||||||
|
## 运行其他命令
|
||||||
|
run_else() {
|
||||||
|
local file_param="$1"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
shift
|
||||||
|
|
||||||
|
$timeoutCmd $which_program $file_param "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
## 命令检测
|
||||||
|
main() {
|
||||||
|
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
||||||
|
case $# in
|
||||||
|
1)
|
||||||
|
run_normal "$1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
case $2 in
|
||||||
|
now)
|
||||||
|
run_normal "$1" "$2"
|
||||||
|
;;
|
||||||
|
conc)
|
||||||
|
run_concurrent "$1" "$3" "$*"
|
||||||
|
;;
|
||||||
|
desi)
|
||||||
|
run_designated "$1" "$3" "$*"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
run_else "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [[ $# -eq 0 ]]; then
|
||||||
|
echo
|
||||||
|
usage
|
||||||
|
else
|
||||||
|
run_else "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_task_before "$@"
|
||||||
|
main "$@"
|
||||||
|
handle_task_after "$@"
|
||||||
+1
-1
@@ -29,4 +29,4 @@ git tag -a "$version" -m "release $version"
|
|||||||
echo -e "提交tag"
|
echo -e "提交tag"
|
||||||
git push --tags
|
git push --tags
|
||||||
|
|
||||||
echo -e "完成发布"
|
echo -e "完成发布"
|
||||||
|
|||||||
+23
-23
@@ -8,35 +8,35 @@ days=$1
|
|||||||
|
|
||||||
## 删除运行js脚本的旧日志
|
## 删除运行js脚本的旧日志
|
||||||
remove_js_log() {
|
remove_js_log() {
|
||||||
local log_full_path_list=$(find $dir_log/ -name "*.log")
|
local log_full_path_list=$(find $dir_log/ -name "*.log")
|
||||||
local diff_time
|
local diff_time
|
||||||
for log in $log_full_path_list; do
|
for log in $log_full_path_list; do
|
||||||
local log_date=$(echo $log | awk -F "/" '{print $NF}' | cut -c1-10) #文件名比文件属性获得的日期要可靠
|
local log_date=$(echo $log | awk -F "/" '{print $NF}' | cut -c1-10) #文件名比文件属性获得的日期要可靠
|
||||||
if [[ $(date +%s -d $log_date 2>/dev/null) ]]; then
|
if [[ $(date +%s -d $log_date 2>/dev/null) ]]; then
|
||||||
if [[ $is_macos -eq 1 ]]; then
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
diff_time=$(($(date +%s) - $(date -j -f "%Y-%m-%d" "$log_date" +%s)))
|
diff_time=$(($(date +%s) - $(date -j -f "%Y-%m-%d" "$log_date" +%s)))
|
||||||
else
|
else
|
||||||
diff_time=$(($(date +%s) - $(date +%s -d "$log_date")))
|
diff_time=$(($(date +%s) - $(date +%s -d "$log_date")))
|
||||||
fi
|
fi
|
||||||
[[ $diff_time -gt $((${days} * 86400)) ]] && rm -vf $log
|
[[ $diff_time -gt $((${days} * 86400)) ]] && rm -vf $log
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
## 删除空文件夹
|
## 删除空文件夹
|
||||||
remove_empty_dir() {
|
remove_empty_dir() {
|
||||||
cd $dir_log
|
cd $dir_log
|
||||||
for dir in $(ls); do
|
for dir in $(ls); do
|
||||||
if [[ -d $dir ]] && [[ -z $(ls $dir) ]]; then
|
if [[ -d $dir ]] && [[ -z $(ls $dir) ]]; then
|
||||||
rm -rf $dir
|
rm -rf $dir
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
## 运行
|
## 运行
|
||||||
if [[ ${days} ]]; then
|
if [[ ${days} ]]; then
|
||||||
echo -e "查找旧日志文件中...\n"
|
echo -e "查找旧日志文件中...\n"
|
||||||
remove_js_log
|
remove_js_log
|
||||||
remove_empty_dir
|
remove_empty_dir
|
||||||
echo -e "删除旧日志执行完毕\n"
|
echo -e "删除旧日志执行完毕\n"
|
||||||
fi
|
fi
|
||||||
|
|||||||
+314
-257
@@ -50,377 +50,434 @@ list_own_drop=$dir_list_tmp/own_drop.list
|
|||||||
|
|
||||||
## 软连接及其原始文件对应关系
|
## 软连接及其原始文件对应关系
|
||||||
link_name=(
|
link_name=(
|
||||||
task
|
task
|
||||||
ql
|
ql
|
||||||
)
|
)
|
||||||
original_name=(
|
original_name=(
|
||||||
task.sh
|
task.sh
|
||||||
update.sh
|
update.sh
|
||||||
)
|
)
|
||||||
|
|
||||||
init_env() {
|
init_env() {
|
||||||
export NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules:/root/.local/share/pnpm/global/5/node_modules
|
export NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules:/root/.local/share/pnpm/global/5/node_modules
|
||||||
export PYTHONUNBUFFERED=1
|
export PYTHONUNBUFFERED=1
|
||||||
}
|
}
|
||||||
|
|
||||||
import_config() {
|
import_config() {
|
||||||
[[ -f $file_config_user ]] && . $file_config_user
|
[[ -f $file_config_user ]] && . $file_config_user
|
||||||
[[ -f $file_env ]] && . $file_env
|
[[ -f $file_env ]] && . $file_env
|
||||||
|
|
||||||
ql_base_url=${QlBaseUrl:-""}
|
ql_base_url=${QlBaseUrl:-""}
|
||||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
||||||
proxy_url=${ProxyUrl:-""}
|
proxy_url=${ProxyUrl:-""}
|
||||||
file_extensions=${RepoFileExtensions:-"js py"}
|
file_extensions=${RepoFileExtensions:-"js py"}
|
||||||
current_branch=${QL_BRANCH}
|
current_branch=${QL_BRANCH}
|
||||||
|
|
||||||
if [[ -n "${DefaultCronRule}" ]]; then
|
if [[ -n "${DefaultCronRule}" ]]; then
|
||||||
default_cron="${DefaultCronRule}"
|
default_cron="${DefaultCronRule}"
|
||||||
else
|
else
|
||||||
default_cron="$(random_range 0 59) $(random_range 0 23) * * *"
|
default_cron="$(random_range 0 59) $(random_range 0 23) * * *"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cpu_warn=${CpuWarn:-80}
|
||||||
|
mem_warn=${MemoryWarn:-80}
|
||||||
|
disk_warn=${DiskWarn:-90}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_proxy() {
|
set_proxy() {
|
||||||
if [[ $proxy_url ]]; then
|
if [[ $proxy_url ]]; then
|
||||||
export http_proxy="${proxy_url}"
|
export http_proxy="${proxy_url}"
|
||||||
export https_proxy="${proxy_url}"
|
export https_proxy="${proxy_url}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
unset_proxy() {
|
unset_proxy() {
|
||||||
unset http_proxy
|
unset http_proxy
|
||||||
unset https_proxy
|
unset https_proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
make_dir() {
|
make_dir() {
|
||||||
local dir=$1
|
local dir=$1
|
||||||
if [[ ! -d $dir ]]; then
|
if [[ ! -d $dir ]]; then
|
||||||
mkdir -p $dir
|
mkdir -p $dir
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_termux() {
|
detect_termux() {
|
||||||
if [[ $PATH == *com.termux* ]]; then
|
if [[ $PATH == *com.termux* ]]; then
|
||||||
is_termux=1
|
is_termux=1
|
||||||
else
|
else
|
||||||
is_termux=0
|
is_termux=0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_macos() {
|
detect_macos() {
|
||||||
[[ $(uname -s) == Darwin ]] && is_macos=1 || is_macos=0
|
[[ $(uname -s) == Darwin ]] && is_macos=1 || is_macos=0
|
||||||
}
|
}
|
||||||
|
|
||||||
gen_random_num() {
|
gen_random_num() {
|
||||||
local divi=$1
|
local divi=$1
|
||||||
echo $((${RANDOM} % $divi))
|
echo $((${RANDOM} % $divi))
|
||||||
}
|
}
|
||||||
|
|
||||||
link_shell_sub() {
|
link_shell_sub() {
|
||||||
local link_path="$1"
|
local link_path="$1"
|
||||||
local original_path="$2"
|
local original_path="$2"
|
||||||
if [[ ! -L $link_path ]] || [[ $(readlink -f $link_path) != $original_path ]]; then
|
if [[ ! -L $link_path ]] || [[ $(readlink -f $link_path) != $original_path ]]; then
|
||||||
rm -f $link_path 2>/dev/null
|
rm -f $link_path 2>/dev/null
|
||||||
ln -sf $original_path $link_path
|
ln -sf $original_path $link_path
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
link_shell() {
|
link_shell() {
|
||||||
if [[ $is_termux -eq 1 ]]; then
|
if [[ $is_termux -eq 1 ]]; then
|
||||||
local path="/data/data/com.termux/files/usr/bin/"
|
local path="/data/data/com.termux/files/usr/bin/"
|
||||||
elif [[ $PATH == */usr/local/bin* ]] && [[ -d /usr/local/bin ]]; then
|
elif [[ $PATH == */usr/local/bin* ]] && [[ -d /usr/local/bin ]]; then
|
||||||
local path="/usr/local/bin/"
|
local path="/usr/local/bin/"
|
||||||
else
|
else
|
||||||
local path=""
|
local path=""
|
||||||
echo -e "脚本功能受限,请自行添加命令的软连接...\n"
|
echo -e "脚本功能受限,请自行添加命令的软连接...\n"
|
||||||
fi
|
fi
|
||||||
if [[ $path ]]; then
|
if [[ $path ]]; then
|
||||||
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
||||||
link_shell_sub "$path${link_name[i]}" "$dir_shell/${original_name[i]}"
|
link_shell_sub "$path${link_name[i]}" "$dir_shell/${original_name[i]}"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
define_cmd() {
|
define_cmd() {
|
||||||
local cmd_prefix cmd_suffix
|
local cmd_prefix cmd_suffix
|
||||||
if type task &>/dev/null; then
|
if type task &>/dev/null; then
|
||||||
cmd_suffix=""
|
cmd_suffix=""
|
||||||
if [[ -f "$dir_shell/task.sh" ]]; then
|
if [[ -f "$dir_shell/task.sh" ]]; then
|
||||||
cmd_prefix=""
|
cmd_prefix=""
|
||||||
else
|
|
||||||
cmd_prefix="bash "
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
cmd_suffix=".sh"
|
cmd_prefix="bash "
|
||||||
if [[ -f "$dir_shell/task.sh" ]]; then
|
|
||||||
cmd_prefix="$dir_shell/"
|
|
||||||
else
|
|
||||||
cmd_prefix="bash $dir_shell/"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
else
|
||||||
export cmd_${link_name[i]}="${cmd_prefix}${link_name[i]}${cmd_suffix}"
|
cmd_suffix=".sh"
|
||||||
done
|
if [[ -f "$dir_shell/task.sh" ]]; then
|
||||||
|
cmd_prefix="$dir_shell/"
|
||||||
|
else
|
||||||
|
cmd_prefix="bash $dir_shell/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
||||||
|
export cmd_${link_name[i]}="${cmd_prefix}${link_name[i]}${cmd_suffix}"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_config() {
|
fix_config() {
|
||||||
make_dir $dir_static
|
make_dir $dir_static
|
||||||
make_dir $dir_data
|
make_dir $dir_data
|
||||||
make_dir $dir_config
|
make_dir $dir_config
|
||||||
make_dir $dir_log
|
make_dir $dir_log
|
||||||
make_dir $dir_db
|
make_dir $dir_db
|
||||||
make_dir $dir_scripts
|
make_dir $dir_scripts
|
||||||
make_dir $dir_list_tmp
|
make_dir $dir_list_tmp
|
||||||
make_dir $dir_repo
|
make_dir $dir_repo
|
||||||
make_dir $dir_raw
|
make_dir $dir_raw
|
||||||
make_dir $dir_update_log
|
make_dir $dir_update_log
|
||||||
make_dir $dir_dep
|
make_dir $dir_dep
|
||||||
|
|
||||||
if [[ ! -s $file_config_user ]]; then
|
if [[ ! -s $file_config_user ]]; then
|
||||||
echo -e "复制一份 $file_config_sample 为 $file_config_user,随后请按注释编辑你的配置文件:$file_config_user\n"
|
echo -e "复制一份 $file_config_sample 为 $file_config_user,随后请按注释编辑你的配置文件:$file_config_user\n"
|
||||||
cp -fv $file_config_sample $file_config_user
|
cp -fv $file_config_sample $file_config_user
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f $file_env ]]; then
|
if [[ ! -f $file_env ]]; then
|
||||||
echo -e "检测到config配置目录下不存在env.sh,创建一个空文件用于初始化...\n"
|
echo -e "检测到config配置目录下不存在env.sh,创建一个空文件用于初始化...\n"
|
||||||
touch $file_env
|
touch $file_env
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f $file_task_before ]]; then
|
if [[ ! -f $file_task_before ]]; then
|
||||||
echo -e "复制一份 $file_task_sample 为 $file_task_before\n"
|
echo -e "复制一份 $file_task_sample 为 $file_task_before\n"
|
||||||
cp -fv $file_task_sample $file_task_before
|
cp -fv $file_task_sample $file_task_before
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f $file_task_after ]]; then
|
if [[ ! -f $file_task_after ]]; then
|
||||||
echo -e "复制一份 $file_task_sample 为 $file_task_after\n"
|
echo -e "复制一份 $file_task_sample 为 $file_task_after\n"
|
||||||
cp -fv $file_task_sample $file_task_after
|
cp -fv $file_task_sample $file_task_after
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f $file_extra_shell ]]; then
|
if [[ ! -f $file_extra_shell ]]; then
|
||||||
echo -e "复制一份 $file_extra_sample 为 $file_extra_shell\n"
|
echo -e "复制一份 $file_extra_sample 为 $file_extra_shell\n"
|
||||||
cp -fv $file_extra_sample $file_extra_shell
|
cp -fv $file_extra_sample $file_extra_shell
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -s $file_auth_user ]]; then
|
if [[ ! -s $file_auth_user ]]; then
|
||||||
echo -e "复制一份 $file_auth_sample 为 $file_auth_user\n"
|
echo -e "复制一份 $file_auth_sample 为 $file_auth_user\n"
|
||||||
cp -fv $file_auth_sample $file_auth_user
|
cp -fv $file_auth_sample $file_auth_user
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -s $file_notify_py ]]; then
|
if [[ ! -s $file_notify_py ]]; then
|
||||||
echo -e "复制一份 $file_notify_py_sample 为 $file_notify_py\n"
|
echo -e "复制一份 $file_notify_py_sample 为 $file_notify_py\n"
|
||||||
cp -fv $file_notify_py_sample $file_notify_py
|
cp -fv $file_notify_py_sample $file_notify_py
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -s $file_notify_js ]]; then
|
if [[ ! -s $file_notify_js ]]; then
|
||||||
echo -e "复制一份 $file_notify_js_sample 为 $file_notify_js\n"
|
echo -e "复制一份 $file_notify_js_sample 为 $file_notify_js\n"
|
||||||
cp -fv $file_notify_js_sample $file_notify_js
|
cp -fv $file_notify_js_sample $file_notify_js
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -s /etc/nginx/conf.d/default.conf ]]; then
|
if [[ -s /etc/nginx/conf.d/default.conf ]]; then
|
||||||
echo -e "检测到默认nginx配置文件,清空...\n"
|
echo -e "检测到默认nginx配置文件,清空...\n"
|
||||||
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
|
if [[ ! -s $dep_notify_js ]]; then
|
||||||
echo -e "复制一份 $file_notify_js_sample 为 $dep_notify_js\n"
|
echo -e "复制一份 $file_notify_js_sample 为 $dep_notify_js\n"
|
||||||
cp -fv $file_notify_js_sample $dep_notify_js
|
cp -fv $file_notify_js_sample $dep_notify_js
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -s $dep_notify_py ]]; then
|
if [[ ! -s $dep_notify_py ]]; then
|
||||||
echo -e "复制一份 $file_notify_py_sample 为 $dep_notify_py\n"
|
echo -e "复制一份 $file_notify_py_sample 为 $dep_notify_py\n"
|
||||||
cp -fv $file_notify_py_sample $dep_notify_py
|
cp -fv $file_notify_py_sample $dep_notify_py
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
npm_install_sub() {
|
npm_install_sub() {
|
||||||
if [ $is_termux -eq 1 ]; then
|
if [ $is_termux -eq 1 ]; then
|
||||||
npm install --production --no-bin-links
|
npm install --production --no-bin-links
|
||||||
elif ! type pnpm &>/dev/null; then
|
elif ! type pnpm &>/dev/null; then
|
||||||
npm install --production
|
npm install --production
|
||||||
else
|
else
|
||||||
pnpm install --loglevel error --production
|
pnpm install --loglevel error --production
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
npm_install_1() {
|
npm_install_1() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
local dir_work=$1
|
local dir_work=$1
|
||||||
|
|
||||||
cd $dir_work
|
cd $dir_work
|
||||||
echo -e "运行 npm install...\n"
|
echo -e "运行 npm install...\n"
|
||||||
npm_install_sub
|
npm_install_sub
|
||||||
[[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 npm install...\n"
|
[[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 npm install...\n"
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
npm_install_2() {
|
npm_install_2() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
local dir_work=$1
|
local dir_work=$1
|
||||||
|
|
||||||
cd $dir_work
|
cd $dir_work
|
||||||
echo -e "检测到 $dir_work 的依赖包有变化,运行 npm install...\n"
|
echo -e "检测到 $dir_work 的依赖包有变化,运行 npm install...\n"
|
||||||
npm_install_sub
|
npm_install_sub
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n"
|
echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n"
|
||||||
npm_install_1 $dir_work
|
npm_install_1 $dir_work
|
||||||
fi
|
fi
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
diff_and_copy() {
|
diff_and_copy() {
|
||||||
local copy_source=$1
|
local copy_source=$1
|
||||||
local copy_to=$2
|
local copy_to=$2
|
||||||
if [[ ! -s $copy_to ]] || [[ $(diff $copy_source $copy_to) ]]; then
|
if [[ ! -s $copy_to ]] || [[ $(diff $copy_source $copy_to) ]]; then
|
||||||
cp -f $copy_source $copy_to
|
cp -f $copy_source $copy_to
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
update_depend() {
|
update_depend() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
|
|
||||||
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
|
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
|
||||||
cp -f $dir_sample/package.json $dir_scripts/package.json
|
cp -f $dir_sample/package.json $dir_scripts/package.json
|
||||||
npm_install_2 $dir_scripts
|
npm_install_2 $dir_scripts
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
git_clone_scripts() {
|
git_clone_scripts() {
|
||||||
local url=$1
|
local url=$1
|
||||||
local dir=$2
|
local dir=$2
|
||||||
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
|
set_proxy
|
||||||
git clone $part_cmd $url $dir
|
git clone $part_cmd $url $dir
|
||||||
exit_status=$?
|
exit_status=$?
|
||||||
unset_proxy
|
unset_proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
git_pull_scripts() {
|
git_pull_scripts() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
local dir_work="$1"
|
local dir_work="$1"
|
||||||
local branch="$2"
|
local branch="$2"
|
||||||
cd $dir_work
|
cd $dir_work
|
||||||
echo -e "开始更新仓库:$dir_work\n"
|
echo -e "开始更新仓库:$dir_work\n"
|
||||||
|
|
||||||
set_proxy
|
set_proxy
|
||||||
git fetch --all
|
git fetch --all
|
||||||
exit_status=$?
|
exit_status=$?
|
||||||
git pull &>/dev/null
|
git pull &>/dev/null
|
||||||
unset_proxy
|
unset_proxy
|
||||||
reset_branch "$branch"
|
reset_branch "$branch"
|
||||||
|
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_romote_url() {
|
reset_romote_url() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
local dir_work=$1
|
local dir_work=$1
|
||||||
local url=$2
|
local url=$2
|
||||||
local branch="$3"
|
local branch="$3"
|
||||||
|
|
||||||
if [[ -d "$dir_work/.git" ]]; then
|
if [[ -d "$dir_work/.git" ]]; then
|
||||||
cd $dir_work
|
cd $dir_work
|
||||||
[[ -f ".git/index.lock" ]] && rm -f .git/index.lock >/dev/null
|
[[ -f ".git/index.lock" ]] && rm -f .git/index.lock >/dev/null
|
||||||
git remote set-url origin $url &>/dev/null
|
git remote set-url origin $url &>/dev/null
|
||||||
|
|
||||||
local part_cmd=""
|
local part_cmd=""
|
||||||
reset_branch "$branch"
|
reset_branch "$branch"
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_branch() {
|
reset_branch() {
|
||||||
local branch="$1"
|
local branch="$1"
|
||||||
if [[ $branch ]]; then
|
if [[ $branch ]]; then
|
||||||
part_cmd="origin/${branch}"
|
part_cmd="origin/${branch}"
|
||||||
git checkout -B "$branch"
|
git checkout -B "$branch" &>/dev/null
|
||||||
git branch --set-upstream-to=$part_cmd $branch
|
git branch --set-upstream-to=$part_cmd $branch &>/dev/null
|
||||||
fi
|
fi
|
||||||
git reset --hard $part_cmd &>/dev/null
|
git reset --hard $part_cmd &>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
random_range() {
|
random_range() {
|
||||||
local beg=$1
|
local beg=$1
|
||||||
local end=$2
|
local end=$2
|
||||||
echo $((RANDOM % ($end - $beg) + $beg))
|
echo $((RANDOM % ($end - $beg) + $beg))
|
||||||
}
|
}
|
||||||
|
|
||||||
reload_pm2() {
|
reload_pm2() {
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
|
|
||||||
echo -e "启动面板服务\n"
|
echo -e "启动面板服务\n"
|
||||||
pm2 delete panel --source-map-support --time &>/dev/null
|
pm2 delete panel --source-map-support --time &>/dev/null
|
||||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null
|
pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null
|
||||||
|
|
||||||
echo -e "启动定时任务服务\n"
|
echo -e "启动定时任务服务\n"
|
||||||
pm2 delete schedule --source-map-support --time &>/dev/null
|
pm2 delete schedule --source-map-support --time &>/dev/null
|
||||||
pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
||||||
|
|
||||||
echo -e "启动公开服务\n"
|
echo -e "启动公开服务\n"
|
||||||
pm2 delete public --source-map-support --time &>/dev/null
|
pm2 delete public --source-map-support --time &>/dev/null
|
||||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
diff_time() {
|
diff_time() {
|
||||||
local format="$1"
|
local format="$1"
|
||||||
local begin_time="$2"
|
local begin_time="$2"
|
||||||
local end_time="$3"
|
local end_time="$3"
|
||||||
|
|
||||||
if [[ $is_macos -eq 1 ]]; then
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
|
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
|
||||||
else
|
else
|
||||||
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
||||||
fi
|
fi
|
||||||
echo "$diff_time"
|
echo "$diff_time"
|
||||||
}
|
}
|
||||||
|
|
||||||
format_time() {
|
format_time() {
|
||||||
local format="$1"
|
local format="$1"
|
||||||
local time="$2"
|
local time="$2"
|
||||||
|
|
||||||
if [[ $is_macos -eq 1 ]]; then
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
|
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
|
||||||
else
|
else
|
||||||
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
format_log_time() {
|
format_log_time() {
|
||||||
local format="$1"
|
local format="$1"
|
||||||
local time="$2"
|
local time="$2"
|
||||||
|
|
||||||
if [[ $is_macos -eq 1 ]]; then
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d-%H-%M-%S")
|
echo $(date -j -f "$format" "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||||
else
|
else
|
||||||
echo $(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
echo $(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
format_timestamp() {
|
format_timestamp() {
|
||||||
local format="$1"
|
local format="$1"
|
||||||
local time="$2"
|
local time="$2"
|
||||||
|
|
||||||
if [[ $is_macos -eq 1 ]]; then
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
echo $(date -j -f "$format" "$time" "+%s")
|
echo $(date -j -f "$format" "$time" "+%s")
|
||||||
else
|
else
|
||||||
echo $(date -d "$time" "+%s")
|
echo $(date -d "$time" "+%s")
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_version() {
|
||||||
|
if [[ $PipMirror ]]; then
|
||||||
|
pip3 config set global.index-url $PipMirror
|
||||||
|
fi
|
||||||
|
if [[ $NpmMirror ]]; then
|
||||||
|
npm config set registry $NpmMirror
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 兼容pnpm@7
|
||||||
|
pnpm setup &>/dev/null
|
||||||
|
source ~/.bashrc
|
||||||
|
pnpm install -g &>/dev/null
|
||||||
|
|
||||||
|
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
||||||
|
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
||||||
|
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
||||||
|
rm -rf $dir_root/db/cookie.db
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! type ts-node &>/dev/null; then
|
||||||
|
pnpm add -g ts-node typescript tslib
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config --global pull.rebase false
|
||||||
|
|
||||||
|
cp -f $dir_root/.env.example $dir_root/.env
|
||||||
|
|
||||||
|
if [[ -d "$dir_root/db" ]]; then
|
||||||
|
echo -e "检测到旧的db目录,拷贝到data目录...\n"
|
||||||
|
cp -rf $dir_root/config $dir_root/data
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$dir_root/scripts" ]]; then
|
||||||
|
echo -e "检测到旧的scripts目录,拷贝到data目录...\n"
|
||||||
|
cp -rf $dir_root/scripts $dir_root/data
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$dir_root/log" ]]; then
|
||||||
|
echo -e "检测到旧的log目录,拷贝到data目录...\n"
|
||||||
|
cp -rf $dir_root/log $dir_root/data
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$dir_root/config" ]]; then
|
||||||
|
echo -e "检测到旧的config目录,拷贝到data目录...\n"
|
||||||
|
cp -rf $dir_root/config $dir_root/data
|
||||||
|
echo
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
init_env
|
init_env
|
||||||
@@ -429,4 +486,4 @@ detect_macos
|
|||||||
define_cmd
|
define_cmd
|
||||||
fix_config
|
fix_config
|
||||||
|
|
||||||
import_config $1 >$task_error_log_path 2>&1
|
import_config $1 2>$task_error_log_path
|
||||||
|
|||||||
+62
-388
@@ -7,401 +7,75 @@ dir_shell=$QL_DIR/shell
|
|||||||
|
|
||||||
## 选择python3还是node
|
## 选择python3还是node
|
||||||
define_program() {
|
define_program() {
|
||||||
local file_param=$1
|
local file_param=$1
|
||||||
if [[ $file_param == *.js ]]; then
|
if [[ $file_param == *.js ]]; then
|
||||||
which_program="node"
|
which_program="node"
|
||||||
elif [[ $file_param == *.py ]] || [[ $file_param == *.pyc ]]; then
|
elif [[ $file_param == *.py ]] || [[ $file_param == *.pyc ]]; then
|
||||||
which_program="python3"
|
which_program="python3"
|
||||||
elif [[ $file_param == *.sh ]]; then
|
elif [[ $file_param == *.sh ]]; then
|
||||||
which_program="bash"
|
which_program="bash"
|
||||||
elif [[ $file_param == *.ts ]]; then
|
elif [[ $file_param == *.ts ]]; then
|
||||||
which_program="ts-node-transpile-only"
|
which_program="ts-node-transpile-only"
|
||||||
|
else
|
||||||
|
which_program=""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_log_path() {
|
||||||
|
local file_param=$1
|
||||||
|
|
||||||
|
if [[ -z $file_param ]];then
|
||||||
|
file_param="task"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local suffix=""
|
||||||
|
if [[ ! -z $ID ]]; then
|
||||||
|
suffix="_${ID}"
|
||||||
|
fi
|
||||||
|
time=$(date "+$time_format")
|
||||||
|
log_time=$(format_log_time "$time_format" "$time")
|
||||||
|
log_dir_tmp="${file_param##*/}"
|
||||||
|
if [[ $file_param =~ "/" ]]; then
|
||||||
|
if [[ $file_param == /* ]]; then
|
||||||
|
log_dir_tmp_path="${file_param:1}"
|
||||||
else
|
else
|
||||||
which_program=""
|
log_dir_tmp_path="${file_param}"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
||||||
|
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||||
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
|
log_dir="${log_dir_tmp%.*}${suffix}"
|
||||||
|
log_path="$log_dir/$log_time.log"
|
||||||
|
cmd=">> $dir_log/$log_path 2>&1"
|
||||||
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
|
make_dir "$dir_log/$log_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
random_delay() {
|
format_params() {
|
||||||
local random_delay_max=$RandomDelay
|
time_format="%Y-%m-%d %H:%M:%S"
|
||||||
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
|
timeoutCmd=""
|
||||||
local file_param=$1
|
if type timeout &>/dev/null; then
|
||||||
local file_extensions=${RandomDelayFileExtensions-"js"}
|
timeoutCmd="timeout -k 10s $command_timeout_time "
|
||||||
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
|
fi
|
||||||
|
params=$(echo "$@" | sed -E 's/([^ ])&([^ ])/\1\\\&\2/g')
|
||||||
if [[ -n $file_extensions ]]; then
|
|
||||||
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
|
|
||||||
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
local current_min
|
|
||||||
current_min=$(date "+%-M")
|
|
||||||
for minute in $ignored_minutes; do
|
|
||||||
if [[ $current_min -eq $minute ]]; then
|
|
||||||
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
|
|
||||||
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
|
|
||||||
sleep $delay_second
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## scripts目录下所有可运行脚本数组
|
show_log="false"
|
||||||
gen_array_scripts() {
|
while getopts ":l" opt; do
|
||||||
local dir_current=$(pwd)
|
case $opt in
|
||||||
local i="-1"
|
l)
|
||||||
cd $dir_scripts
|
show_log="true"
|
||||||
for file in $(ls); do
|
;;
|
||||||
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
|
esac
|
||||||
let i++
|
done
|
||||||
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
|
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
||||||
array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1)
|
|
||||||
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
cd $dir_current
|
|
||||||
}
|
|
||||||
|
|
||||||
## 使用说明
|
format_params "$@"
|
||||||
usage() {
|
define_program "$@"
|
||||||
define_cmd
|
handle_log_path "$@"
|
||||||
gen_array_scripts
|
|
||||||
echo -e "task命令运行本程序自动添加进crontab的脚本,需要输入脚本的绝对路径或去掉 “$dir_scripts/” 目录后的相对路径(定时任务中请写作相对路径),用法为:"
|
|
||||||
echo -e "1.$cmd_task <file_name> # 依次执行,如果设置了随机延迟,将随机延迟一定秒数"
|
|
||||||
echo -e "2.$cmd_task <file_name> now # 依次执行,无论是否设置了随机延迟,均立即运行,前台会输出日志,同时记录在日志文件中"
|
|
||||||
echo -e "3.$cmd_task <file_name> conc <环境变量名称> <账号编号,空格分隔>(可选的) # 并发执行,无论是否设置了随机延迟,均立即运行,前台不产生日志,直接记录在日志文件中,且可指定账号执行"
|
|
||||||
echo -e "4.$cmd_task <file_name> desi <环境变量名称> <账号编号,空格分隔> # 指定账号执行,无论是否设置了随机延迟,均立即运行"
|
|
||||||
if [[ ${#array_scripts[*]} -gt 0 ]]; then
|
|
||||||
echo -e "\n当前有以下脚本可以运行:"
|
|
||||||
for ((i = 0; i < ${#array_scripts[*]}; i++)); do
|
|
||||||
echo -e "$(($i + 1)). ${array_scripts_name[i]}:${array_scripts[i]}"
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo -e "\n暂无脚本可以执行"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
## run nohup,$1:文件名,不含路径,带后缀
|
eval . $dir_shell/otask.sh "$params" "$cmd"
|
||||||
run_nohup() {
|
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
|
||||||
local file_name=$1
|
|
||||||
nohup node $file_name &>$log_path &
|
|
||||||
}
|
|
||||||
|
|
||||||
## 正常运行单个脚本,$1:传入参数
|
|
||||||
run_normal() {
|
|
||||||
local file_param=$1
|
|
||||||
define_program "$file_param"
|
|
||||||
if [[ $# -eq 1 ]]; then
|
|
||||||
random_delay "$file_param"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local time=$(date "+$time_format")
|
|
||||||
log_time=$(format_log_time "$time_format" "$time")
|
|
||||||
log_dir_tmp="${file_param##*/}"
|
|
||||||
if [[ $file_param =~ "/" ]]; then
|
|
||||||
if [[ $file_param == /* ]]; then
|
|
||||||
log_dir_tmp_path="${file_param:1}"
|
|
||||||
else
|
|
||||||
log_dir_tmp_path="${file_param}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
|
||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
|
||||||
log_dir="${log_dir_tmp%.*}"
|
|
||||||
log_path="$log_dir/$log_time.log"
|
|
||||||
cmd=">> $dir_log/$log_path 2>&1"
|
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
|
||||||
make_dir "$dir_log/$log_dir"
|
|
||||||
|
|
||||||
local begin_time=$(format_time "$time_format" "$time")
|
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
|
||||||
|
|
||||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
|
||||||
eval . $file_task_before "$@" $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 $timeoutCmd $which_program $file_param $cmd
|
|
||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
local end_timestamp=$(date "+%s")
|
|
||||||
local diff_time=$(expr $end_timestamp - $begin_timestamp)
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
|
|
||||||
run_concurrent() {
|
|
||||||
local file_param="$1"
|
|
||||||
local env_param="$2"
|
|
||||||
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
|
|
||||||
if [[ ! $env_param ]]; then
|
|
||||||
echo -e "\n 缺少并发运行的环境变量参数"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local envs=$(eval echo "\$${env_param}")
|
|
||||||
local array=($(echo $envs | sed 's/&/ /g'))
|
|
||||||
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
|
|
||||||
local runArr=($(eval echo $tempArr))
|
|
||||||
runArr=($(awk -v RS=' ' '!a[$1]++' <<< ${runArr[@]}))
|
|
||||||
|
|
||||||
local n=0
|
|
||||||
for i in ${runArr[@]}; do
|
|
||||||
array_run[n]=${array[$i - 1]}
|
|
||||||
let n++
|
|
||||||
done
|
|
||||||
|
|
||||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
|
||||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
|
||||||
|
|
||||||
define_program "$file_param"
|
|
||||||
local time=$(date "+$time_format")
|
|
||||||
log_time=$(format_log_time "$time_format" "$time")
|
|
||||||
log_dir_tmp="${file_param##*/}"
|
|
||||||
if [[ $file_param =~ "/" ]]; then
|
|
||||||
if [[ $file_param == /* ]]; then
|
|
||||||
log_dir_tmp_path="${file_param:1}"
|
|
||||||
else
|
|
||||||
log_dir_tmp_path="${file_param}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
|
||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
|
||||||
log_dir="${log_dir_tmp%.*}"
|
|
||||||
log_path="$log_dir/$log_time.log"
|
|
||||||
cmd=">> $dir_log/$log_path 2>&1"
|
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
|
||||||
make_dir "$dir_log/$log_dir"
|
|
||||||
|
|
||||||
local begin_time=$(format_time "$time_format" "$time")
|
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
|
||||||
|
|
||||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
|
||||||
eval . $file_task_before "$@" $cmd
|
|
||||||
|
|
||||||
local envs=$(eval echo "\$${env_param}")
|
|
||||||
local array=($(echo $envs | sed 's/&/ /g'))
|
|
||||||
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
|
|
||||||
|
|
||||||
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
|
|
||||||
for i in "${!array[@]}"; do
|
|
||||||
export ${env_param}=${array[i]}
|
|
||||||
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
|
|
||||||
eval $timeoutCmd $which_program $file_param &>$single_log_path &
|
|
||||||
done
|
|
||||||
|
|
||||||
wait
|
|
||||||
for i in "${!array[@]}"; do
|
|
||||||
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
|
|
||||||
eval cat $single_log_path $cmd
|
|
||||||
[[ -f $single_log_path ]] && rm -f $single_log_path
|
|
||||||
done
|
|
||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
local end_timestamp=$(date "+%s")
|
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
run_designated() {
|
|
||||||
local file_param="$1"
|
|
||||||
local env_param="$2"
|
|
||||||
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
|
|
||||||
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
|
|
||||||
echo -e "\n 缺少单独运行的参数 task xxx.js desi Test 1 3"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
define_program "$file_param"
|
|
||||||
local time=$(date "+$time_format")
|
|
||||||
log_time=$(format_log_time "$time_format" "$time")
|
|
||||||
log_dir_tmp="${file_param##*/}"
|
|
||||||
if [[ $file_param =~ "/" ]]; then
|
|
||||||
if [[ $file_param == /* ]]; then
|
|
||||||
log_dir_tmp_path="${file_param:1}"
|
|
||||||
else
|
|
||||||
log_dir_tmp_path="${file_param}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
|
||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
|
||||||
log_dir="${log_dir_tmp%.*}"
|
|
||||||
log_path="$log_dir/$log_time.log"
|
|
||||||
cmd=">> $dir_log/$log_path 2>&1"
|
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
|
||||||
make_dir "$dir_log/$log_dir"
|
|
||||||
|
|
||||||
local begin_time=$(format_time "$time_format" "$time")
|
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
|
||||||
|
|
||||||
local envs=$(eval echo "\$${env_param}")
|
|
||||||
local array=($(echo $envs | sed 's/&/ /g'))
|
|
||||||
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
|
|
||||||
local runArr=($(eval echo $tempArr))
|
|
||||||
runArr=($(awk -v RS=' ' '!a[$1]++' <<< ${runArr[@]}))
|
|
||||||
|
|
||||||
local n=0
|
|
||||||
for i in ${runArr[@]}; do
|
|
||||||
array_run[n]=${array[$i - 1]}
|
|
||||||
let n++
|
|
||||||
done
|
|
||||||
|
|
||||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
|
||||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
|
||||||
|
|
||||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
|
||||||
eval . $file_task_before "$@" $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 $timeoutCmd $which_program $file_param $cmd
|
|
||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
local end_timestamp=$(date "+%s")
|
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
## 运行其他命令
|
|
||||||
run_else() {
|
|
||||||
local file_param="$1"
|
|
||||||
define_program "$file_param"
|
|
||||||
local time=$(date "+$time_format")
|
|
||||||
log_time=$(format_log_time "$time_format" "$time")
|
|
||||||
log_dir_tmp="${file_param##*/}"
|
|
||||||
if [[ $file_param =~ "/" ]]; then
|
|
||||||
if [[ $file_param == /* ]]; then
|
|
||||||
log_dir_tmp_path="${file_param:1}"
|
|
||||||
else
|
|
||||||
log_dir_tmp_path="${file_param}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
|
||||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
|
||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
|
||||||
log_dir="${log_dir_tmp%.*}"
|
|
||||||
log_path="$log_dir/$log_time.log"
|
|
||||||
cmd=">> $dir_log/$log_path 2>&1"
|
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
|
||||||
make_dir "$dir_log/$log_dir"
|
|
||||||
|
|
||||||
local begin_time=$(format_time "$time_format" "$time")
|
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
|
||||||
|
|
||||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $@" | perl -pe "s|.*ID=(.*) $cmd_task $@\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
|
||||||
eval . $file_task_before "$@" $cmd
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
shift
|
|
||||||
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
|
|
||||||
|
|
||||||
eval . $file_task_after "$file_param" "$@" $cmd
|
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
local end_timestamp=$(date "+%s")
|
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
## 命令检测
|
|
||||||
main() {
|
|
||||||
show_log="false"
|
|
||||||
while getopts ":l" opt
|
|
||||||
do
|
|
||||||
case $opt in
|
|
||||||
l)
|
|
||||||
show_log="true"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
|
||||||
|
|
||||||
timeoutCmd=""
|
|
||||||
if type timeout &>/dev/null; then
|
|
||||||
timeoutCmd="timeout -k 10s $command_timeout_time "
|
|
||||||
fi
|
|
||||||
|
|
||||||
time_format="%Y-%m-%d %H:%M:%S"
|
|
||||||
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
|
||||||
case $# in
|
|
||||||
1)
|
|
||||||
run_normal "$1"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
case $2 in
|
|
||||||
now)
|
|
||||||
run_normal "$1" "$2"
|
|
||||||
;;
|
|
||||||
conc)
|
|
||||||
run_concurrent "$1" "$3" "$*"
|
|
||||||
;;
|
|
||||||
desi)
|
|
||||||
run_designated "$1" "$3" "$*"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
run_else "$@"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
|
|
||||||
elif [[ $# -eq 0 ]]; then
|
|
||||||
echo
|
|
||||||
usage
|
|
||||||
else
|
|
||||||
run_else "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
+410
-442
@@ -8,552 +8,520 @@ send_mark=$dir_shell/send_mark
|
|||||||
|
|
||||||
## 检测cron的差异,$1:脚本清单文件路径,$2:cron任务清单文件路径,$3:增加任务清单文件路径,$4:删除任务清单文件路径
|
## 检测cron的差异,$1:脚本清单文件路径,$2:cron任务清单文件路径,$3:增加任务清单文件路径,$4:删除任务清单文件路径
|
||||||
diff_cron() {
|
diff_cron() {
|
||||||
local list_scripts="$1"
|
local list_scripts="$1"
|
||||||
local list_task="$2"
|
local list_task="$2"
|
||||||
local list_add="$3"
|
local list_add="$3"
|
||||||
local list_drop="$4"
|
local list_drop="$4"
|
||||||
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
|
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||||
grep -vwf $list_task $list_scripts >$list_add
|
grep -vwf $list_task $list_scripts >$list_add
|
||||||
grep -vwf $list_scripts $list_task >$list_drop
|
grep -vwf $list_scripts $list_task >$list_drop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||||
cp -f $list_scripts $list_add
|
cp -f $list_scripts $list_add
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
|
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
|
||||||
cp -f $list_task $list_drop
|
cp -f $list_task $list_drop
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## 检测配置文件版本
|
## 检测配置文件版本
|
||||||
detect_config_version() {
|
detect_config_version() {
|
||||||
## 识别出两个文件的版本号
|
## 识别出两个文件的版本号
|
||||||
ver_config_sample=$(grep " Version: " $file_config_sample | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
ver_config_sample=$(grep " Version: " $file_config_sample | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
||||||
[[ -f $file_config_user ]] && ver_config_user=$(grep " Version: " $file_config_user | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
[[ -f $file_config_user ]] && ver_config_user=$(grep " Version: " $file_config_user | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
||||||
|
|
||||||
## 删除旧的发送记录文件
|
## 删除旧的发送记录文件
|
||||||
[[ -f $send_mark ]] && [[ $(cat $send_mark) != $ver_config_sample ]] && rm -f $send_mark
|
[[ -f $send_mark ]] && [[ $(cat $send_mark) != $ver_config_sample ]] && rm -f $send_mark
|
||||||
|
|
||||||
## 识别出更新日期和更新内容
|
## 识别出更新日期和更新内容
|
||||||
update_date=$(grep " Date: " $file_config_sample | awk -F ": " '{print $2}')
|
update_date=$(grep " Date: " $file_config_sample | awk -F ": " '{print $2}')
|
||||||
update_content=$(grep " Update Content: " $file_config_sample | awk -F ": " '{print $2}')
|
update_content=$(grep " Update Content: " $file_config_sample | awk -F ": " '{print $2}')
|
||||||
|
|
||||||
## 如果是今天,并且版本号不一致,则发送通知
|
## 如果是今天,并且版本号不一致,则发送通知
|
||||||
if [[ -f $file_config_user ]] && [[ $ver_config_user != $ver_config_sample ]] && [[ $update_date == $(date "+%Y-%m-%d") ]]; then
|
if [[ -f $file_config_user ]] && [[ $ver_config_user != $ver_config_sample ]] && [[ $update_date == $(date "+%Y-%m-%d") ]]; then
|
||||||
if [[ ! -f $send_mark ]]; then
|
if [[ ! -f $send_mark ]]; then
|
||||||
local notify_title="配置文件更新通知"
|
local notify_title="配置文件更新通知"
|
||||||
local notify_content="更新日期: $update_date\n用户版本: $ver_config_user\n新的版本: $ver_config_sample\n更新内容: $update_content\n更新说明: 如需使用新功能请对照config.sample.sh,将相关新参数手动增加到你自己的config.sh中,否则请无视本消息。本消息只在该新版本配置文件更新当天发送一次。\n"
|
local notify_content="更新日期: $update_date\n用户版本: $ver_config_user\n新的版本: $ver_config_sample\n更新内容: $update_content\n更新说明: 如需使用新功能请对照config.sample.sh,将相关新参数手动增加到你自己的config.sh中,否则请无视本消息。本消息只在该新版本配置文件更新当天发送一次。\n"
|
||||||
echo -e $notify_content
|
echo -e $notify_content
|
||||||
notify_api "$notify_title" "$notify_content"
|
notify_api "$notify_title" "$notify_content"
|
||||||
[[ $? -eq 0 ]] && echo $ver_config_sample >$send_mark
|
[[ $? -eq 0 ]] && echo $ver_config_sample >$send_mark
|
||||||
fi
|
|
||||||
else
|
|
||||||
[[ -f $send_mark ]] && rm -f $send_mark
|
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
[[ -f $send_mark ]] && rm -f $send_mark
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## 输出是否有新的或失效的定时任务,$1:新的或失效的任务清单文件路径,$2:新/失效
|
## 输出是否有新的或失效的定时任务,$1:新的或失效的任务清单文件路径,$2:新/失效
|
||||||
output_list_add_drop() {
|
output_list_add_drop() {
|
||||||
local list=$1
|
local list=$1
|
||||||
local type=$2
|
local type=$2
|
||||||
if [[ -s $list ]]; then
|
if [[ -s $list ]]; then
|
||||||
echo -e "检测到有$type的定时任务:\n"
|
echo -e "检测到有$type的定时任务:\n"
|
||||||
cat $list
|
cat $list
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## 自动删除失效的脚本与定时任务,需要:1.AutoDelCron 设置为 true;2.正常更新js脚本,没有报错;3.存在失效任务
|
## 自动删除失效的脚本与定时任务,需要:1.AutoDelCron 设置为 true;2.正常更新js脚本,没有报错;3.存在失效任务
|
||||||
## $1:失效任务清单文件路径
|
## $1:失效任务清单文件路径
|
||||||
del_cron() {
|
del_cron() {
|
||||||
local list_drop=$1
|
local list_drop=$1
|
||||||
local path=$2
|
local path=$2
|
||||||
local detail=""
|
local detail=""
|
||||||
local ids=""
|
local ids=""
|
||||||
echo -e "开始尝试自动删除失效的定时任务...\n"
|
echo -e "开始尝试自动删除失效的定时任务...\n"
|
||||||
for cron in $(cat $list_drop); do
|
for cron in $(cat $list_drop); do
|
||||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $cron" | perl -pe "s|.*ID=(.*) $cmd_task $cron\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
local id=$(cat $list_crontab_user | grep -E "$cmd_task $cron" | perl -pe "s|.*ID=(.*) $cmd_task $cron\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
||||||
if [[ $ids ]]; then
|
|
||||||
ids="$ids,\"$id\""
|
|
||||||
else
|
|
||||||
ids="\"$id\""
|
|
||||||
fi
|
|
||||||
cron_file="$dir_scripts/${cron}"
|
|
||||||
if [[ -f $cron_file ]]; then
|
|
||||||
cron_name=$(grep "new Env" $cron_file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
|
||||||
rm -f $cron_file
|
|
||||||
fi
|
|
||||||
[[ -z $cron_name ]] && cron_name="$cron"
|
|
||||||
if [[ $detail ]]; then
|
|
||||||
detail="${detail}\n${cron_name}"
|
|
||||||
else
|
|
||||||
detail="${cron_name}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [[ $ids ]]; then
|
if [[ $ids ]]; then
|
||||||
result=$(del_cron_api "$ids")
|
ids="$ids,\"$id\""
|
||||||
notify_api "$path 删除任务${result}" "$detail"
|
else
|
||||||
|
ids="\"$id\""
|
||||||
fi
|
fi
|
||||||
|
cron_file="$dir_scripts/${cron}"
|
||||||
|
if [[ -f $cron_file ]]; then
|
||||||
|
cron_name=$(grep "new Env" $cron_file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||||
|
rm -f $cron_file
|
||||||
|
fi
|
||||||
|
[[ -z $cron_name ]] && cron_name="$cron"
|
||||||
|
if [[ $detail ]]; then
|
||||||
|
detail="${detail}\n${cron_name}"
|
||||||
|
else
|
||||||
|
detail="${cron_name}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ $ids ]]; then
|
||||||
|
result=$(del_cron_api "$ids")
|
||||||
|
notify_api "$path 删除任务${result}" "$detail"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## 自动增加定时任务,需要:1.AutoAddCron 设置为 true;2.正常更新js脚本,没有报错;3.存在新任务;4.crontab.list存在并且不为空
|
## 自动增加定时任务,需要:1.AutoAddCron 设置为 true;2.正常更新js脚本,没有报错;3.存在新任务;4.crontab.list存在并且不为空
|
||||||
## $1:新任务清单文件路径
|
## $1:新任务清单文件路径
|
||||||
add_cron() {
|
add_cron() {
|
||||||
local list_add=$1
|
local list_add=$1
|
||||||
local path=$2
|
local path=$2
|
||||||
echo -e "开始尝试自动添加定时任务...\n"
|
echo -e "开始尝试自动添加定时任务...\n"
|
||||||
local detail=""
|
local detail=""
|
||||||
cd $dir_scripts
|
cd $dir_scripts
|
||||||
for file in $(cat $list_add); do
|
for file in $(cat $list_add); do
|
||||||
local file_name=${file/${path}\//}
|
local file_name=${file/${path}\//}
|
||||||
file_name=${file_name/${path}\_/}
|
file_name=${file_name/${path}\_/}
|
||||||
if [[ -f $file ]]; then
|
if [[ -f $file ]]; then
|
||||||
cron_line=$(
|
cron_line=$(
|
||||||
perl -ne "{
|
perl -ne "{
|
||||||
print if /.*([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*]( |,|\").*$file_name/
|
print if /.*([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*]( |,|\").*$file_name/
|
||||||
}" $file |
|
}" $file |
|
||||||
perl -pe "{
|
perl -pe "{
|
||||||
s|[^\d\*]*(([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*])( \|,\|\").*/?$file_name.*|\1|g;
|
s|[^\d\*]*(([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*])( \|,\|\").*/?$file_name.*|\1|g;
|
||||||
s|\*([\d\*])(.*)|\1\2|g;
|
s|\*([\d\*])(.*)|\1\2|g;
|
||||||
s| | |g;
|
s| | |g;
|
||||||
}" | sort -u | head -1
|
}" | sort -u | head -1
|
||||||
)
|
)
|
||||||
cron_name=$(grep "new Env" $file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
cron_name=$(grep "new Env" $file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||||
[[ -z $cron_name ]] && cron_name="$file_name"
|
[[ -z $cron_name ]] && cron_name="$file_name"
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $file | awk -F ":" '{print $2}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $file | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $file | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron " $file | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||||
[[ -z $cron_line ]] && cron_line="$default_cron"
|
[[ -z $cron_line ]] && cron_line="$default_cron"
|
||||||
result=$(add_cron_api "$cron_line:$cmd_task $file:$cron_name")
|
result=$(add_cron_api "$cron_line:$cmd_task $file:$cron_name")
|
||||||
echo -e "$result"
|
echo -e "$result"
|
||||||
if [[ $detail ]]; then
|
if [[ $detail ]]; then
|
||||||
detail="${detail}${result}\n"
|
detail="${detail}${result}\n"
|
||||||
else
|
else
|
||||||
detail="${result}\n"
|
detail="${result}\n"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
notify_api "$path 新增任务" "$detail"
|
notify_api "$path 新增任务" "$detail"
|
||||||
}
|
}
|
||||||
|
|
||||||
## 更新仓库
|
## 更新仓库
|
||||||
update_repo() {
|
update_repo() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local path="$2"
|
local path="$2"
|
||||||
local blackword="$3"
|
local blackword="$3"
|
||||||
local dependence="$4"
|
local dependence="$4"
|
||||||
local branch="$5"
|
local branch="$5"
|
||||||
local extensions="$6"
|
local extensions="$6"
|
||||||
local tmp="${url%/*}"
|
local tmp="${url%/*}"
|
||||||
local authorTmp1="${tmp##*/}"
|
local authorTmp1="${tmp##*/}"
|
||||||
local authorTmp2="${authorTmp1##*:}"
|
local authorTmp2="${authorTmp1##*:}"
|
||||||
local author="${authorTmp2##*.}"
|
local author="${authorTmp2##*.}"
|
||||||
|
|
||||||
local repo_path="${dir_repo}/${uniq_path}"
|
local repo_path="${dir_repo}/${uniq_path}"
|
||||||
|
|
||||||
make_dir "${dir_scripts}/${uniq_path}"
|
make_dir "${dir_scripts}/${uniq_path}"
|
||||||
|
|
||||||
local formatUrl="$url"
|
local formatUrl="$url"
|
||||||
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}"
|
||||||
else
|
else
|
||||||
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}"
|
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}"
|
||||||
fi
|
fi
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
echo -e "\n更新${repo_path}成功...\n"
|
echo -e "\n更新${repo_path}成功...\n"
|
||||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
||||||
else
|
else
|
||||||
echo -e "\n更新${repo_path}失败,请检查网络...\n"
|
echo -e "\n更新${repo_path}失败,请检查网络...\n"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## 更新所有 raw 文件
|
## 更新所有 raw 文件
|
||||||
update_raw() {
|
update_raw() {
|
||||||
echo -e "--------------------------------------------------------------\n"
|
echo -e "--------------------------------------------------------------\n"
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local raw_url="$url"
|
local raw_url="$url"
|
||||||
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
|
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
|
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"
|
||||||
cd $dir_raw
|
cd $dir_raw
|
||||||
local filename="raw_${raw_file_name}"
|
local filename="raw_${raw_file_name}"
|
||||||
local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task $filename" | perl -pe "s|.*ID=(.*) $cmd_task $filename\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task $filename" | perl -pe "s|.*ID=(.*) $cmd_task $filename\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
||||||
cp -f $raw_file_name $dir_scripts/${filename}
|
cp -f $raw_file_name $dir_scripts/${filename}
|
||||||
cron_line=$(
|
cron_line=$(
|
||||||
perl -ne "{
|
perl -ne "{
|
||||||
print if /.*([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*]( |,|\").*$raw_file_name/
|
print if /.*([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*]( |,|\").*$raw_file_name/
|
||||||
}" $raw_file_name |
|
}" $raw_file_name |
|
||||||
perl -pe "{
|
perl -pe "{
|
||||||
s|[^\d\*]*(([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*])( \|,\|\").*/?$raw_file_name.*|\1|g;
|
s|[^\d\*]*(([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*])( \|,\|\").*/?$raw_file_name.*|\1|g;
|
||||||
s|\*([\d\*])(.*)|\1\2|g;
|
s|\*([\d\*])(.*)|\1\2|g;
|
||||||
s| | |g;
|
s| | |g;
|
||||||
}" | sort -u | head -1
|
}" | sort -u | head -1
|
||||||
)
|
)
|
||||||
cron_name=$(grep "new Env" $raw_file_name | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
cron_name=$(grep "new Env" $raw_file_name | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||||
[[ -z $cron_name ]] && cron_name="$raw_file_name"
|
[[ -z $cron_name ]] && cron_name="$raw_file_name"
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $raw_file_name | awk -F ":" '{print $2}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $raw_file_name | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $raw_file_name | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron " $raw_file_name | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||||
[[ -z $cron_line ]] && cron_line="$default_cron"
|
[[ -z $cron_line ]] && cron_line="$default_cron"
|
||||||
if [[ -z $cron_id ]]; then
|
if [[ -z $cron_id ]]; then
|
||||||
result=$(add_cron_api "$cron_line:$cmd_task $filename:$cron_name")
|
result=$(add_cron_api "$cron_line:$cmd_task $filename:$cron_name")
|
||||||
echo -e "$result\n"
|
echo -e "$result\n"
|
||||||
notify_api "新增任务通知" "\n$result"
|
notify_api "新增任务通知" "\n$result"
|
||||||
# update_cron_api "$cron_line:$cmd_task $filename:$cron_name:$cron_id"
|
# update_cron_api "$cron_line:$cmd_task $filename:$cron_name:$cron_id"
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "下载 ${raw_file_name} 失败,保留之前正常下载的版本...\n"
|
|
||||||
[[ -f "$dir_raw/${raw_file_name}.new" ]] && rm -f "$dir_raw/${raw_file_name}.new"
|
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo -e "下载 ${raw_file_name} 失败,保留之前正常下载的版本...\n"
|
||||||
|
[[ -f "$dir_raw/${raw_file_name}.new" ]] && rm -f "$dir_raw/${raw_file_name}.new"
|
||||||
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## 调用用户自定义的extra.sh
|
## 调用用户自定义的extra.sh
|
||||||
run_extra_shell() {
|
run_extra_shell() {
|
||||||
if [[ ${EnableExtraShell} == true ]]; then
|
if [[ ${EnableExtraShell} == true ]]; then
|
||||||
if [[ -f $file_extra_shell ]]; then
|
if [[ -f $file_extra_shell ]]; then
|
||||||
echo -e "--------------------------------------------------------------\n"
|
echo -e "--------------------------------------------------------------\n"
|
||||||
. $file_extra_shell
|
. $file_extra_shell
|
||||||
else
|
else
|
||||||
echo -e "$file_extra_shell文件不存在,跳过执行...\n"
|
echo -e "$file_extra_shell文件不存在,跳过执行...\n"
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## 脚本用法
|
## 脚本用法
|
||||||
usage() {
|
usage() {
|
||||||
echo -e "本脚本用法:"
|
echo -e "ql命令使用方法:"
|
||||||
echo -e "1. $cmd_update update # 更新并重启青龙"
|
echo -e "1. $cmd_update update # 更新并重启青龙"
|
||||||
echo -e "2. $cmd_update extra # 运行自定义脚本"
|
echo -e "2. $cmd_update extra # 运行自定义脚本"
|
||||||
echo -e "3. $cmd_update raw <fileurl> # 更新单个脚本文件"
|
echo -e "3. $cmd_update raw <fileurl> # 更新单个脚本文件"
|
||||||
echo -e "4. $cmd_update repo <repourl> <path> <blacklist> <dependence> <branch> <extensions> # 更新单个仓库的脚本"
|
echo -e "4. $cmd_update repo <repourl> <path> <blacklist> <dependence> <branch> <extensions> # 更新单个仓库的脚本"
|
||||||
echo -e "5. $cmd_update rmlog <days> # 删除旧日志"
|
echo -e "5. $cmd_update rmlog <days> # 删除旧日志"
|
||||||
echo -e "6. $cmd_update bot # 启动tg-bot"
|
echo -e "6. $cmd_update bot # 启动tg-bot"
|
||||||
echo -e "7. $cmd_update check # 检测青龙环境并修复"
|
echo -e "7. $cmd_update check # 检测青龙环境并修复"
|
||||||
echo -e "8. $cmd_update resetlet # 重置登录错误次数"
|
echo -e "8. $cmd_update resetlet # 重置登录错误次数"
|
||||||
echo -e "9. $cmd_update resettfa # 禁用两步登录"
|
echo -e "9. $cmd_update resettfa # 禁用两步登录"
|
||||||
}
|
}
|
||||||
|
|
||||||
## 更新qinglong
|
## 更新qinglong
|
||||||
update_qinglong() {
|
update_qinglong() {
|
||||||
patch_version
|
patch_version &>/dev/null
|
||||||
|
|
||||||
local no_restart="$1"
|
export isFirstStartServer=false
|
||||||
local all_branch=$(git branch -a)
|
|
||||||
local primary_branch="master"
|
|
||||||
if [[ "${all_branch}" =~ "${current_branch}" ]]; then
|
|
||||||
primary_branch="${current_branch}"
|
|
||||||
fi
|
|
||||||
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
|
||||||
reset_romote_url ${dir_root} "https://github.com/whyour/qinglong.git" ${primary_branch}
|
|
||||||
git_pull_scripts $dir_root ${primary_branch}
|
|
||||||
|
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
local all_branch=$(git branch -a)
|
||||||
echo -e "\n更新青龙源文件成功...\n"
|
local primary_branch="master"
|
||||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
if [[ "${all_branch}" =~ "${current_branch}" ]]; then
|
||||||
detect_config_version
|
primary_branch="${current_branch}"
|
||||||
update_depend
|
fi
|
||||||
|
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
||||||
|
reset_romote_url ${dir_root} "https://github.com/whyour/qinglong.git" ${primary_branch}
|
||||||
|
git_pull_scripts $dir_root ${primary_branch}
|
||||||
|
|
||||||
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
[[ "$ql_depend_old" != "$ql_depend_new" ]] && npm_install_2 $dir_root
|
echo -e "\n更新青龙源文件成功...\n"
|
||||||
else
|
cp -f $file_config_sample $dir_config/config.sample.sh
|
||||||
echo -e "\n更新青龙源文件失败,请检查原因...\n"
|
detect_config_version
|
||||||
fi
|
update_depend
|
||||||
|
|
||||||
local url="https://github.com/whyour/qinglong-static.git"
|
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
||||||
if [[ -d ${ql_static_repo}/.git ]]; then
|
[[ "$ql_depend_old" != "$ql_depend_new" ]] && npm_install_2 $dir_root
|
||||||
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
|
||||||
git_pull_scripts ${ql_static_repo} ${primary_branch}
|
|
||||||
else
|
|
||||||
git_clone_scripts ${url} ${ql_static_repo} ${primary_branch}
|
|
||||||
fi
|
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
|
||||||
echo -e "\n更新青龙静态资源成功...\n"
|
|
||||||
local static_version=$(cat $dir_root/src/version.ts | perl -pe "s|.*\'(.*)\';\.*|\1|" | head -1)
|
|
||||||
echo -e "\n当前版本 $static_version...\n"
|
|
||||||
|
|
||||||
rm -rf $dir_static/*
|
|
||||||
cp -rf $ql_static_repo/* $dir_static
|
|
||||||
if [[ $no_restart != "no-restart" ]]; then
|
|
||||||
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
|
||||||
echo -e "重启面板中..."
|
|
||||||
sleep 3
|
|
||||||
reload_pm2
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "\n更新青龙静态资源失败,请检查原因...\n"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
update_qinglong_static "$1" "$primary_branch"
|
||||||
|
else
|
||||||
|
echo -e "\n更新青龙源文件失败,请检查网络...\n"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
patch_version() {
|
update_qinglong_static() {
|
||||||
# 兼容pnpm@7
|
local no_restart="$1"
|
||||||
pnpm setup &>/dev/null
|
local primary_branch="$2"
|
||||||
source ~/.bashrc
|
local url="https://github.com/whyour/qinglong-static.git"
|
||||||
pnpm install -g &>/dev/null
|
if [[ -d ${ql_static_repo}/.git ]]; then
|
||||||
|
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
||||||
|
git_pull_scripts ${ql_static_repo} ${primary_branch}
|
||||||
|
else
|
||||||
|
git_clone_scripts ${url} ${ql_static_repo} ${primary_branch}
|
||||||
|
fi
|
||||||
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
|
echo -e "\n更新青龙静态资源成功...\n"
|
||||||
|
local static_version=$(cat $dir_root/src/version.ts | perl -pe "s|.*\'(.*)\';\.*|\1|" | head -1)
|
||||||
|
echo -e "\n当前版本 $static_version...\n"
|
||||||
|
|
||||||
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
rm -rf $dir_static/*
|
||||||
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
cp -rf $ql_static_repo/* $dir_static
|
||||||
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
if [[ $no_restart != "no-restart" ]]; then
|
||||||
rm -rf $dir_root/db/cookie.db
|
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
||||||
echo
|
echo -e "重启面板中..."
|
||||||
|
sleep 3
|
||||||
|
reload_pm2
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
if ! type ts-node &>/dev/null; then
|
echo -e "\n更新青龙静态资源失败,请检查网络...\n"
|
||||||
pnpm add -g ts-node typescript tslib
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
git config --global pull.rebase false
|
|
||||||
|
|
||||||
cp -f $dir_root/.env.example $dir_root/.env
|
|
||||||
|
|
||||||
if [[ -d "$dir_root/db" ]]; then
|
|
||||||
echo -e "检测到旧的db目录,拷贝到data目录...\n"
|
|
||||||
cp -rf $dir_root/config $dir_root/data
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "$dir_root/scripts" ]]; then
|
|
||||||
echo -e "检测到旧的scripts目录,拷贝到data目录...\n"
|
|
||||||
cp -rf $dir_root/scripts $dir_root/data
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "$dir_root/log" ]]; then
|
|
||||||
echo -e "检测到旧的log目录,拷贝到data目录...\n"
|
|
||||||
cp -rf $dir_root/log $dir_root/data
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "$dir_root/config" ]]; then
|
|
||||||
echo -e "检测到旧的config目录,拷贝到data目录...\n"
|
|
||||||
cp -rf $dir_root/config $dir_root/data
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $PipMirror ]]; then
|
|
||||||
pip3 config set global.index-url $PipMirror
|
|
||||||
fi
|
|
||||||
if [[ $NpmMirror ]]; then
|
|
||||||
npm config set registry $NpmMirror
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## 对比脚本
|
## 对比脚本
|
||||||
diff_scripts() {
|
diff_scripts() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
local repo_path="$1"
|
local repo_path="$1"
|
||||||
local author="$2"
|
local author="$2"
|
||||||
local path="$3"
|
local path="$3"
|
||||||
local blackword="$4"
|
local blackword="$4"
|
||||||
local dependence="$5"
|
local dependence="$5"
|
||||||
local extensions="$6"
|
local extensions="$6"
|
||||||
|
|
||||||
gen_list_repo "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
gen_list_repo "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
||||||
|
|
||||||
local list_add="$dir_list_tmp/${uniq_path}_add.list"
|
local list_add="$dir_list_tmp/${uniq_path}_add.list"
|
||||||
local list_drop="$dir_list_tmp/${uniq_path}_drop.list"
|
local list_drop="$dir_list_tmp/${uniq_path}_drop.list"
|
||||||
diff_cron "$dir_list_tmp/${uniq_path}_scripts.list" "$dir_list_tmp/${uniq_path}_user.list" $list_add $list_drop
|
diff_cron "$dir_list_tmp/${uniq_path}_scripts.list" "$dir_list_tmp/${uniq_path}_user.list" $list_add $list_drop
|
||||||
|
|
||||||
if [[ -s $list_drop ]]; then
|
if [[ -s $list_drop ]]; then
|
||||||
output_list_add_drop $list_drop "失效"
|
output_list_add_drop $list_drop "失效"
|
||||||
if [[ ${AutoDelCron} == true ]]; then
|
if [[ ${AutoDelCron} == true ]]; then
|
||||||
del_cron $list_drop $uniq_path
|
del_cron $list_drop $uniq_path
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
if [[ -s $list_add ]]; then
|
fi
|
||||||
output_list_add_drop $list_add "新"
|
if [[ -s $list_add ]]; then
|
||||||
if [[ ${AutoAddCron} == true ]]; then
|
output_list_add_drop $list_add "新"
|
||||||
add_cron $list_add $uniq_path
|
if [[ ${AutoAddCron} == true ]]; then
|
||||||
fi
|
add_cron $list_add $uniq_path
|
||||||
fi
|
fi
|
||||||
cd $dir_current
|
fi
|
||||||
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
## 生成脚本的路径清单文件
|
## 生成脚本的路径清单文件
|
||||||
gen_list_repo() {
|
gen_list_repo() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
local repo_path="$1"
|
local repo_path="$1"
|
||||||
local author="$2"
|
local author="$2"
|
||||||
local path="$3"
|
local path="$3"
|
||||||
local blackword="$4"
|
local blackword="$4"
|
||||||
local dependence="$5"
|
local dependence="$5"
|
||||||
|
|
||||||
rm -f $dir_list_tmp/${uniq_path}*.list &>/dev/null
|
rm -f $dir_list_tmp/${uniq_path}*.list &>/dev/null
|
||||||
|
|
||||||
|
cd ${repo_path}
|
||||||
|
|
||||||
|
local cmd="find ."
|
||||||
|
local index=0
|
||||||
|
if [[ $6 ]]; then
|
||||||
|
file_extensions="$6"
|
||||||
|
if [[ $file_extensions =~ "|" ]]; then
|
||||||
|
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
for extension in $file_extensions; do
|
||||||
|
if [[ $index -eq 0 ]]; then
|
||||||
|
cmd="${cmd} -name \"*.${extension}\""
|
||||||
|
else
|
||||||
|
cmd="${cmd} -o -name \"*.${extension}\""
|
||||||
|
fi
|
||||||
|
let index+=1
|
||||||
|
done
|
||||||
|
files=$(eval $cmd | sed 's/^..//')
|
||||||
|
if [[ $path ]]; then
|
||||||
|
files=$(echo "$files" | egrep $path)
|
||||||
|
fi
|
||||||
|
if [[ $blackword ]]; then
|
||||||
|
files=$(echo "$files" | egrep -v $blackword)
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -f $file_notify_js "${dir_scripts}/${uniq_path}"
|
||||||
|
cp -f $file_notify_py "${dir_scripts}/${uniq_path}"
|
||||||
|
|
||||||
|
if [[ $dependence ]]; then
|
||||||
cd ${repo_path}
|
cd ${repo_path}
|
||||||
|
results=$(eval $cmd | sed 's/^..//' | egrep $dependence)
|
||||||
local cmd="find ."
|
for _file in ${results}; do
|
||||||
local index=0
|
file_path=$(dirname $_file)
|
||||||
if [[ $6 ]]; then
|
make_dir "${dir_scripts}/${uniq_path}/${file_path}"
|
||||||
file_extensions="$6"
|
cp -f $_file "${dir_scripts}/${uniq_path}/${file_path}"
|
||||||
if [[ $file_extensions =~ "|" ]]; then
|
|
||||||
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
for extension in $file_extensions; do
|
|
||||||
if [[ $index -eq 0 ]]; then
|
|
||||||
cmd="${cmd} -name \"*.${extension}\""
|
|
||||||
else
|
|
||||||
cmd="${cmd} -o -name \"*.${extension}\""
|
|
||||||
fi
|
|
||||||
let index+=1
|
|
||||||
done
|
done
|
||||||
files=$(eval $cmd | sed 's/^..//')
|
fi
|
||||||
if [[ $path ]]; then
|
|
||||||
files=$(echo "$files" | egrep $path)
|
|
||||||
fi
|
|
||||||
if [[ $blackword ]]; then
|
|
||||||
files=$(echo "$files" | egrep -v $blackword)
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp -f $file_notify_js "${dir_scripts}/${uniq_path}"
|
if [[ -d $dir_dep ]]; then
|
||||||
cp -f $file_notify_py "${dir_scripts}/${uniq_path}"
|
cp -rf $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $dependence ]]; then
|
for file in ${files}; do
|
||||||
cd ${repo_path}
|
filename=$(basename $file)
|
||||||
results=$(eval $cmd | sed 's/^..//' | egrep $dependence)
|
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
||||||
for _file in ${results}; do
|
echo "${uniq_path}/${filename}" >>"$dir_list_tmp/${uniq_path}_scripts.list"
|
||||||
file_path=$(dirname $_file)
|
cron_id=$(cat $list_crontab_user | grep -E "$cmd_task ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||||
make_dir "${dir_scripts}/${uniq_path}/${file_path}"
|
if [[ $cron_id ]]; then
|
||||||
cp -f $_file "${dir_scripts}/${uniq_path}/${file_path}"
|
result=$(update_cron_command_api "$cmd_task ${uniq_path}/${filename}:$cron_id")
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
if [[ -d $dir_dep ]]; then
|
grep -E "${cmd_task} ${uniq_path}" ${list_crontab_user} | perl -pe "s|.*ID=(.*) ${cmd_task} (${uniq_path}.*)\.*|\2|" | awk -F " " '{print $1}' | sort -u >"$dir_list_tmp/${uniq_path}_user.list"
|
||||||
cp -rf $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null
|
cd $dir_current
|
||||||
fi
|
|
||||||
|
|
||||||
for file in ${files}; do
|
|
||||||
filename=$(basename $file)
|
|
||||||
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
|
||||||
echo "${uniq_path}/${filename}" >>"$dir_list_tmp/${uniq_path}_scripts.list"
|
|
||||||
cron_id=$(cat $list_crontab_user | grep -E "$cmd_task ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
|
||||||
if [[ $cron_id ]]; then
|
|
||||||
result=$(update_cron_command_api "$cmd_task ${uniq_path}/${filename}:$cron_id")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
grep -E "${cmd_task} ${uniq_path}" ${list_crontab_user} | perl -pe "s|.*ID=(.*) ${cmd_task} (${uniq_path}.*)\.*|\2|" | awk -F " " '{print $1}' | sort -u >"$dir_list_tmp/${uniq_path}_user.list"
|
|
||||||
cd $dir_current
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_uniq_path() {
|
get_uniq_path() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local branch="$2"
|
local branch="$2"
|
||||||
local urlTmp="${url%*/}"
|
local urlTmp="${url%*/}"
|
||||||
local repoTmp="${urlTmp##*/}"
|
local repoTmp="${urlTmp##*/}"
|
||||||
local repo="${repoTmp%.*}"
|
local repo="${repoTmp%.*}"
|
||||||
local tmp="${url%/*}"
|
local tmp="${url%/*}"
|
||||||
local authorTmp1="${tmp##*/}"
|
local authorTmp1="${tmp##*/}"
|
||||||
local authorTmp2="${authorTmp1##*:}"
|
local authorTmp2="${authorTmp1##*:}"
|
||||||
local author="${authorTmp2##*.}"
|
local author="${authorTmp2##*.}"
|
||||||
|
|
||||||
uniq_path="${author}_${repo}"
|
uniq_path="${author}_${repo}"
|
||||||
[[ $branch ]] && uniq_path="${uniq_path}_${branch}"
|
[[ $branch ]] && uniq_path="${uniq_path}_${branch}"
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
## for ql update
|
## for ql update
|
||||||
show_log="false"
|
show_log="false"
|
||||||
while getopts ":l" opt
|
while getopts ":l" opt; do
|
||||||
do
|
case $opt in
|
||||||
case $opt in
|
l)
|
||||||
l)
|
show_log="true"
|
||||||
show_log="true"
|
;;
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
|
||||||
|
|
||||||
local p1=$1
|
|
||||||
local p2=$2
|
|
||||||
local p3=$3
|
|
||||||
local p4=$4
|
|
||||||
local p5=$5
|
|
||||||
local p6=$6
|
|
||||||
local p7=$7
|
|
||||||
local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
|
||||||
local log_path="$dir_log/update/${log_time}_$p1.log"
|
|
||||||
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
case $p1 in
|
|
||||||
update)
|
|
||||||
cmd=">> $log_path 2>&1"
|
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
|
||||||
eval echo -e "## 开始执行... $begin_time\n" $cmd
|
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
|
||||||
eval update_qinglong "$2" $cmd
|
|
||||||
;;
|
|
||||||
extra)
|
|
||||||
echo -e "## 开始执行... $begin_time\n" >>$log_path
|
|
||||||
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
|
|
||||||
run_extra_shell >>$log_path
|
|
||||||
;;
|
|
||||||
repo)
|
|
||||||
get_uniq_path "$p2" "$p6"
|
|
||||||
if [[ -n $p2 ]]; then
|
|
||||||
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
|
|
||||||
else
|
|
||||||
echo -e "命令输入错误...\n"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
raw)
|
|
||||||
get_uniq_path "$p2"
|
|
||||||
if [[ -n $p2 ]]; then
|
|
||||||
update_raw "$p2"
|
|
||||||
else
|
|
||||||
echo -e "命令输入错误...\n"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
rmlog)
|
|
||||||
echo -e "## 开始执行... $begin_time\n" >>$log_path
|
|
||||||
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
|
|
||||||
. $dir_shell/rmlog.sh "$p2" >>$log_path
|
|
||||||
;;
|
|
||||||
bot)
|
|
||||||
echo -e "## 开始执行... $begin_time\n" >>$log_path
|
|
||||||
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
|
|
||||||
. $dir_shell/bot.sh >>$log_path
|
|
||||||
;;
|
|
||||||
check)
|
|
||||||
echo -e "## 开始执行... $begin_time\n" >>$log_path
|
|
||||||
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
|
|
||||||
. $dir_shell/check.sh >>$log_path
|
|
||||||
;;
|
|
||||||
resetlet)
|
|
||||||
echo -e "## 开始执行... $begin_time\n" >>$log_path
|
|
||||||
auth_value=$(cat $file_auth_user | jq '.retries =0' -c)
|
|
||||||
echo -e "重置登录错误次数成功 \n $auth_value" >>$log_path
|
|
||||||
echo "$auth_value" >$file_auth_user
|
|
||||||
;;
|
|
||||||
resettfa)
|
|
||||||
echo -e "## 开始执行... $begin_time\n" >>$log_path
|
|
||||||
auth_value=$(cat $file_auth_user | jq '.twoFactorActivated =false' | jq '.twoFactorActived =false' -c)
|
|
||||||
echo -e "禁用两步验证成功 \n $auth_value" >>$log_path
|
|
||||||
echo "$auth_value" >$file_auth_user
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo -e "命令输入错误...\n"
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
done
|
||||||
local diff_time=$(diff_time "%Y-%m-%d %H:%M:%S" "$begin_time" "$end_time")
|
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
||||||
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
|
|
||||||
echo -e "\n## 执行结束... $end_time 耗时 $diff_time 秒" >>$log_path
|
local p1=$1
|
||||||
cat $log_path
|
local p2=$2
|
||||||
|
local p3=$3
|
||||||
|
local p4=$4
|
||||||
|
local p5=$5
|
||||||
|
local p6=$6
|
||||||
|
local p7=$7
|
||||||
|
local log_dir="${p1}"
|
||||||
|
make_dir "$dir_log/$log_dir"
|
||||||
|
local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||||
|
local log_path="${log_dir}/${log_time}.log"
|
||||||
|
local file_path="$dir_log/$log_path"
|
||||||
|
|
||||||
|
cmd=">> $file_path 2>&1"
|
||||||
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
|
|
||||||
|
local time_format="%Y-%m-%d %H:%M:%S"
|
||||||
|
local time=$(date "+$time_format")
|
||||||
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||||
|
|
||||||
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
|
|
||||||
|
if [[ -s $task_error_log_path ]]; then
|
||||||
|
eval cat $task_error_log_path $cmd
|
||||||
|
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
|
||||||
|
eval echo $cmd
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
|
||||||
|
eval echo -e "请移除 -l 参数" $cmd
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $p1 in
|
||||||
|
update)
|
||||||
|
eval update_qinglong "$2" $cmd
|
||||||
|
;;
|
||||||
|
extra)
|
||||||
|
eval run_extra_shell $cmd
|
||||||
|
;;
|
||||||
|
repo)
|
||||||
|
get_uniq_path "$p2" "$p6"
|
||||||
|
if [[ -n $p2 ]]; then
|
||||||
|
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
|
||||||
|
else
|
||||||
|
eval echo -e "命令输入错误...\\\n" $cmd
|
||||||
|
eval usage $cmd
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
raw)
|
||||||
|
get_uniq_path "$p2"
|
||||||
|
if [[ -n $p2 ]]; then
|
||||||
|
update_raw "$p2"
|
||||||
|
else
|
||||||
|
eval echo -e "命令输入错误...\\\n" $cmd
|
||||||
|
eval usage $cmd
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
rmlog)
|
||||||
|
eval . $dir_shell/rmlog.sh "$p2" $cmd
|
||||||
|
;;
|
||||||
|
bot)
|
||||||
|
eval . $dir_shell/bot.sh $cmd
|
||||||
|
;;
|
||||||
|
check)
|
||||||
|
eval . $dir_shell/check.sh $cmd
|
||||||
|
;;
|
||||||
|
resetlet)
|
||||||
|
auth_value=$(cat $file_auth_user | jq '.retries =0' -c)
|
||||||
|
echo "$auth_value" >$file_auth_user
|
||||||
|
echo -e "重置登录错误次数成功" $cmd
|
||||||
|
;;
|
||||||
|
resettfa)
|
||||||
|
auth_value=$(cat $file_auth_user | jq '.twoFactorActivated =false' | jq '.twoFactorActived =false' -c)
|
||||||
|
echo "$auth_value" >$file_auth_user
|
||||||
|
eval echo -e "禁用两步验证成功" $cmd
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
eval echo -e "命令输入错误...\\\n" $cmd
|
||||||
|
eval usage $cmd
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local etime=$(date "+$time_format")
|
||||||
|
local end_time=$(format_time "$time_format" "$etime")
|
||||||
|
local end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||||
|
local diff_time=$(($end_timestamp - $begin_timestamp))
|
||||||
|
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||||
|
|
||||||
|
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
|
eval echo -e "\\\n " $cmd
|
||||||
|
|
||||||
|
if [[ -f $file_path ]]; then
|
||||||
|
cat $file_path
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const EditableTagGroup = ({
|
|||||||
{tag}
|
{tag}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span key={tag} style={{ display: 'inline-block', marginBottom: 8 }}>
|
<span key={tag} style={{ display: 'inline-block', marginBottom: 8 }}>
|
||||||
{tagElem}
|
{tagElem}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export default (
|
||||||
|
treeData: any[],
|
||||||
|
searchValue: string,
|
||||||
|
{
|
||||||
|
treeNodeFilterProp,
|
||||||
|
}: {
|
||||||
|
treeNodeFilterProp: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
return useMemo(() => {
|
||||||
|
const keys: string[] = [];
|
||||||
|
|
||||||
|
if (!searchValue) {
|
||||||
|
return { treeData, keys };
|
||||||
|
}
|
||||||
|
|
||||||
|
const upperStr = searchValue.toUpperCase();
|
||||||
|
function filterOptionFunc(_: string, dataNode: any[]) {
|
||||||
|
const value = dataNode[treeNodeFilterProp as any];
|
||||||
|
|
||||||
|
return String(value).toUpperCase().includes(upperStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dig(list: any[], keepAll: boolean = false): any[] {
|
||||||
|
return list
|
||||||
|
.map((dataNode) => {
|
||||||
|
const children = dataNode.children;
|
||||||
|
|
||||||
|
const match = keepAll || filterOptionFunc!(searchValue, dataNode);
|
||||||
|
const childList = dig(children || [], match);
|
||||||
|
|
||||||
|
if (match || childList.length) {
|
||||||
|
childList.length && keys.push(dataNode.key);
|
||||||
|
return {
|
||||||
|
...dataNode,
|
||||||
|
children: childList,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((node) => node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { treeData: dig(treeData), keys };
|
||||||
|
}, [treeData, searchValue, treeNodeFilterProp]);
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { MutableRefObject, useLayoutEffect, useState } from 'react';
|
||||||
|
import useResizeObserver from '@react-hook/resize-observer'
|
||||||
|
import { getTableScroll } from '@/utils';
|
||||||
|
|
||||||
|
export default <T extends HTMLElement>(target: MutableRefObject<T>, extraHeight?: number) => {
|
||||||
|
const [height, setHeight] = useState<number>()
|
||||||
|
|
||||||
|
useResizeObserver(target, (entry) => {
|
||||||
|
let _targe = entry.target as any
|
||||||
|
if (!_targe.classList.contains('ant-table-wrapper')) {
|
||||||
|
_targe = entry.target.querySelector('.ant-table-wrapper')
|
||||||
|
}
|
||||||
|
setHeight(getTableScroll({ extraHeight, target: _targe as HTMLElement }))
|
||||||
|
})
|
||||||
|
return height
|
||||||
|
}
|
||||||
@@ -68,18 +68,18 @@ export default {
|
|||||||
icon: <IconFont type="ql-icon-dependence" />,
|
icon: <IconFont type="ql-icon-dependence" />,
|
||||||
component: '@/pages/dependence/index',
|
component: '@/pages/dependence/index',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/log',
|
||||||
|
name: '日志管理',
|
||||||
|
icon: <IconFont type="ql-icon-log" />,
|
||||||
|
component: '@/pages/log/index',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/diff',
|
path: '/diff',
|
||||||
name: '对比工具',
|
name: '对比工具',
|
||||||
icon: <IconFont type="ql-icon-diff" />,
|
icon: <IconFont type="ql-icon-diff" />,
|
||||||
component: '@/pages/diff/index',
|
component: '@/pages/diff/index',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/log',
|
|
||||||
name: '任务日志',
|
|
||||||
icon: <IconFont type="ql-icon-log" />,
|
|
||||||
component: '@/pages/log/index',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/setting',
|
path: '/setting',
|
||||||
name: '系统设置',
|
name: '系统设置',
|
||||||
|
|||||||
+28
-30
@@ -161,7 +161,7 @@
|
|||||||
&.env-wrapper,
|
&.env-wrapper,
|
||||||
&.config-wrapper {
|
&.config-wrapper {
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
width: calc(100vw - 80px);
|
width: calc(100vw - 24px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,8 +179,8 @@
|
|||||||
|
|
||||||
.Resizer {
|
.Resizer {
|
||||||
background: @component-background;
|
background: @component-background;
|
||||||
opacity: 0.2;
|
opacity: 0.8;
|
||||||
z-index: 1;
|
z-index: 100;
|
||||||
-moz-box-sizing: border-box;
|
-moz-box-sizing: border-box;
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -243,31 +243,28 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-dark='true'] {
|
/* Change autocomplete styles in WebKit */
|
||||||
/* Change autocomplete styles in WebKit */
|
input:-webkit-autofill,
|
||||||
input:-webkit-autofill,
|
input:-webkit-autofill:hover,
|
||||||
input:-webkit-autofill:hover,
|
input:-webkit-autofill:focus,
|
||||||
input:-webkit-autofill:focus,
|
textarea:-webkit-autofill,
|
||||||
textarea:-webkit-autofill,
|
textarea:-webkit-autofill:hover,
|
||||||
textarea:-webkit-autofill:hover,
|
textarea:-webkit-autofill:focus,
|
||||||
textarea:-webkit-autofill:focus,
|
select:-webkit-autofill,
|
||||||
select:-webkit-autofill,
|
select:-webkit-autofill:hover,
|
||||||
select:-webkit-autofill:hover,
|
select:-webkit-autofill:focus {
|
||||||
select:-webkit-autofill:focus {
|
border: none;
|
||||||
border: 1px solid @border-color-base;
|
box-shadow: none;
|
||||||
box-shadow: none;
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
transition: background-color 5000s ease-in-out 0s;
|
-webkit-text-fill-color: @text-color;
|
||||||
-webkit-text-fill-color: @text-color;
|
}
|
||||||
caret-color: #e8e6f3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
opacity: 0.5 !important;
|
opacity: 0.5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-select-selection-placeholder {
|
.ant-select-selection-placeholder {
|
||||||
opacity: 0.5 !important;
|
opacity: 0.5 !important;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-basicLayout-content {
|
.ant-pro-basicLayout-content {
|
||||||
@@ -326,10 +323,6 @@
|
|||||||
background-color: #373739;
|
background-color: #373739;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-layout-sider {
|
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-pro-sider-logo {
|
.ant-pro-sider-logo {
|
||||||
padding: 16px 8px !important;
|
padding: 16px 8px !important;
|
||||||
|
|
||||||
@@ -349,3 +342,8 @@
|
|||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
word-break: break-all !important;
|
||||||
|
white-space: break-spaces !important;
|
||||||
|
}
|
||||||
|
|||||||
+94
-52
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react';
|
|||||||
import ProLayout, { PageLoading } from '@ant-design/pro-layout';
|
import ProLayout, { PageLoading } from '@ant-design/pro-layout';
|
||||||
import * as DarkReader from '@umijs/ssr-darkreader';
|
import * as DarkReader from '@umijs/ssr-darkreader';
|
||||||
import defaultProps from './defaultProps';
|
import defaultProps from './defaultProps';
|
||||||
import { Link, history } from 'umi';
|
import { Link, history, Outlet, useLocation } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
@@ -15,21 +15,56 @@ 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, Image } from 'antd';
|
import {
|
||||||
|
message,
|
||||||
|
Badge,
|
||||||
|
Modal,
|
||||||
|
Avatar,
|
||||||
|
Dropdown,
|
||||||
|
Menu,
|
||||||
|
Image,
|
||||||
|
Popover,
|
||||||
|
Descriptions,
|
||||||
|
Tooltip,
|
||||||
|
} 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 { init } from '../utils/init';
|
import { init } from '../utils/init';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import 'codemirror/mode/python/python';
|
||||||
|
import 'codemirror/mode/shell/shell';
|
||||||
|
|
||||||
export default function (props: any) {
|
export interface SharedContext {
|
||||||
|
headerStyle: React.CSSProperties;
|
||||||
|
isPhone: boolean;
|
||||||
|
theme: 'vs' | 'vs-dark';
|
||||||
|
user: any;
|
||||||
|
reloadUser: (needLoading?: boolean) => void;
|
||||||
|
reloadTheme: () => void;
|
||||||
|
socketMessage: any;
|
||||||
|
systemInfo: TSystemInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TSystemInfo {
|
||||||
|
branch: 'develop' | 'master';
|
||||||
|
isInitialized: boolean;
|
||||||
|
lastCommitId: string;
|
||||||
|
lastCommitTime: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const location = useLocation();
|
||||||
const ctx = useCtx();
|
const ctx = useCtx();
|
||||||
const { theme, reloadTheme } = useTheme();
|
const { theme, reloadTheme } = useTheme();
|
||||||
const [user, setUser] = useState<any>({});
|
const [user, setUser] = useState<any>({});
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [systemInfo, setSystemInfo] = useState<{ isInitialized: boolean }>();
|
const [systemInfo, setSystemInfo] = useState<TSystemInfo>();
|
||||||
const ws = useRef<any>(null);
|
const ws = useRef<any>(null);
|
||||||
const [socketMessage, setSocketMessage] = useState<any>();
|
const [socketMessage, setSocketMessage] = useState<any>();
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const [initLoading, setInitLoading] = useState<boolean>(true);
|
||||||
const {
|
const {
|
||||||
enable: enableDarkMode,
|
enable: enableDarkMode,
|
||||||
disable: disableDarkMode,
|
disable: disableDarkMode,
|
||||||
@@ -53,17 +88,15 @@ export default function (props: any) {
|
|||||||
setSystemInfo(data);
|
setSystemInfo(data);
|
||||||
if (!data.isInitialized) {
|
if (!data.isInitialized) {
|
||||||
history.push('/initialization');
|
history.push('/initialization');
|
||||||
setLoading(false);
|
|
||||||
} else {
|
} else {
|
||||||
getUser();
|
getUser();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
})
|
||||||
|
.finally(() => setInitLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUser = (needLoading = true) => {
|
const getUser = (needLoading = true) => {
|
||||||
@@ -73,11 +106,9 @@ export default function (props: any) {
|
|||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200 && data.username) {
|
if (code === 200 && data.username) {
|
||||||
setUser(data);
|
setUser(data);
|
||||||
if (props.location.pathname === '/') {
|
if (location.pathname === '/') {
|
||||||
history.push('/crontab');
|
history.push('/crontab');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
needLoading && setLoading(false);
|
needLoading && setLoading(false);
|
||||||
})
|
})
|
||||||
@@ -94,7 +125,7 @@ export default function (props: any) {
|
|||||||
if (systemInfo && systemInfo.isInitialized && !user) {
|
if (systemInfo && systemInfo.isInitialized && !user) {
|
||||||
getUser();
|
getUser();
|
||||||
}
|
}
|
||||||
}, [props.location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!systemInfo) {
|
if (!systemInfo) {
|
||||||
@@ -136,9 +167,11 @@ export default function (props: any) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) return;
|
if (!user || !user.username) return;
|
||||||
ws.current = new SockJS(
|
ws.current = new SockJS(
|
||||||
`${location.origin}/api/ws?token=${localStorage.getItem(config.authKey)}`,
|
`${window.location.origin}/api/ws?token=${localStorage.getItem(
|
||||||
|
config.authKey,
|
||||||
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
ws.current.onmessage = (e: any) => {
|
ws.current.onmessage = (e: any) => {
|
||||||
@@ -146,9 +179,9 @@ export default function (props: any) {
|
|||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
if (data.type === 'ping') {
|
if (data.type === 'ping') {
|
||||||
if (data && data.message === 'hanhh') {
|
if (data && data.message === 'hanhh') {
|
||||||
console.log('websocket连接成功', e);
|
console.log('WS connection succeeded !!!');
|
||||||
} else {
|
} else {
|
||||||
console.log('websocket连接失败', e);
|
console.log('WS connection Failed !!!', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSocketMessage(data);
|
setSocketMessage(data);
|
||||||
@@ -183,29 +216,31 @@ export default function (props: any) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (
|
if (initLoading) {
|
||||||
['/login', '/initialization', '/error'].includes(props.location.pathname)
|
return <PageLoading />;
|
||||||
) {
|
}
|
||||||
document.title = `${(config.documentTitleMap as any)[props.location.pathname]
|
|
||||||
} - 控制面板`;
|
if (['/login', '/initialization', '/error'].includes(location.pathname)) {
|
||||||
if (
|
document.title = `${
|
||||||
systemInfo?.isInitialized &&
|
(config.documentTitleMap as any)[location.pathname]
|
||||||
props.location.pathname === '/initialization'
|
} - 控制面板`;
|
||||||
) {
|
if (systemInfo?.isInitialized && location.pathname === '/initialization') {
|
||||||
history.push('/crontab');
|
history.push('/crontab');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (systemInfo || props.location.pathname === '/error') {
|
if (systemInfo || location.pathname === '/error') {
|
||||||
return React.Children.map(props.children, (child) => {
|
return (
|
||||||
return React.cloneElement(child, {
|
<Outlet
|
||||||
...ctx,
|
context={{
|
||||||
theme,
|
...ctx,
|
||||||
user,
|
theme,
|
||||||
reloadUser,
|
user,
|
||||||
reloadTheme,
|
reloadUser,
|
||||||
ws: ws.current,
|
reloadTheme,
|
||||||
});
|
ws: ws.current,
|
||||||
});
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,10 +261,10 @@ export default function (props: any) {
|
|||||||
<PageLoading />
|
<PageLoading />
|
||||||
) : (
|
) : (
|
||||||
<ProLayout
|
<ProLayout
|
||||||
selectedKeys={[props.location.pathname]}
|
selectedKeys={[location.pathname]}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
ErrorBoundary={Sentry.ErrorBoundary}
|
ErrorBoundary={Sentry.ErrorBoundary}
|
||||||
logo={<Image preview={false} src="http://qn.whyour.cn/logo.png" />}
|
logo={<Image preview={false} src="https://qn.whyour.cn/logo.png" />}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<span style={{ fontSize: 16 }}>控制面板</span>
|
<span style={{ fontSize: 16 }}>控制面板</span>
|
||||||
@@ -241,17 +276,23 @@ export default function (props: any) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<Tooltip
|
||||||
style={{
|
title={systemInfo?.branch === 'develop' ? '开发版' : '正式版'}
|
||||||
fontSize: isFirefox ? 9 : 12,
|
|
||||||
color: '#666',
|
|
||||||
marginLeft: 2,
|
|
||||||
zoom: isSafari ? 0.66 : 0.8,
|
|
||||||
letterSpacing: isQQBrowser ? -2 : 0,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
v{version}
|
<Badge size="small" dot={systemInfo?.branch === 'develop'}>
|
||||||
</span>
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: isFirefox ? 9 : 12,
|
||||||
|
color: '#666',
|
||||||
|
marginLeft: 2,
|
||||||
|
zoom: isSafari ? 0.66 : 0.8,
|
||||||
|
letterSpacing: isQQBrowser ? -2 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
v{version}
|
||||||
|
</span>
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -319,16 +360,17 @@ export default function (props: any) {
|
|||||||
)}
|
)}
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
>
|
>
|
||||||
{React.Children.map(props.children, (child) => {
|
<Outlet
|
||||||
return React.cloneElement(child, {
|
context={{
|
||||||
...ctx,
|
...ctx,
|
||||||
theme,
|
theme,
|
||||||
user,
|
user,
|
||||||
reloadUser,
|
reloadUser,
|
||||||
reloadTheme,
|
reloadTheme,
|
||||||
socketMessage,
|
socketMessage,
|
||||||
});
|
systemInfo,
|
||||||
})}
|
}}
|
||||||
|
/>
|
||||||
</ProLayout>
|
</ProLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ import { PageContainer } from '@ant-design/pro-layout';
|
|||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const Config = ({ headerStyle, isPhone, theme }: any) => {
|
const Config = () => {
|
||||||
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [title, setTitle] = useState('config.sh');
|
const [title, setTitle] = useState('config.sh');
|
||||||
@@ -22,8 +25,10 @@ const Config = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
|
||||||
const getConfig = (name: string) => {
|
const getConfig = (name: string) => {
|
||||||
request.get(`${config.apiPrefix}configs/${name}`).then((data: any) => {
|
request.get(`${config.apiPrefix}configs/${name}`).then(({ code, data }) => {
|
||||||
setValue(data.data);
|
if (code === 200) {
|
||||||
|
setValue(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,8 +36,10 @@ const Config = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}configs/files`)
|
.get(`${config.apiPrefix}configs/files`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setData(data.data);
|
if (code === 200) {
|
||||||
|
setData(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -47,8 +54,10 @@ const Config = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
.post(`${config.apiPrefix}configs/save`, {
|
.post(`${config.apiPrefix}configs/save`, {
|
||||||
data: { content, name: select },
|
data: { content, name: select },
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
message.success(data.message);
|
if (code === 200) {
|
||||||
|
message.success('保存成功');
|
||||||
|
}
|
||||||
setConfirmLoading(false);
|
setConfirmLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -71,6 +80,7 @@ const Config = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
extra={[
|
extra={[
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
treeExpandAction="click"
|
||||||
className="config-select"
|
className="config-select"
|
||||||
value={select}
|
value={select}
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ const CronDetailModal = ({
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
fontFamily: 'Source Code Pro',
|
fontFamily: 'Source Code Pro',
|
||||||
folding: false,
|
|
||||||
glyphMargin: false,
|
glyphMargin: false,
|
||||||
wordWrap: 'on',
|
wordWrap: 'on',
|
||||||
}}
|
}}
|
||||||
@@ -123,9 +122,11 @@ const CronDetailModal = ({
|
|||||||
.get(
|
.get(
|
||||||
`${config.apiPrefix}logs/${item.filename}?path=${item.directory || ''}`,
|
`${config.apiPrefix}logs/${item.filename}?path=${item.directory || ''}`,
|
||||||
)
|
)
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setLog(data.data);
|
if (code === 200) {
|
||||||
setIsLogModalVisible(true);
|
setLog(data);
|
||||||
|
setIsLogModalVisible(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,9 +138,9 @@ const CronDetailModal = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}crons/${cron.id}/logs`)
|
.get(`${config.apiPrefix}crons/${cron.id}/logs`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
setLogs(data.data);
|
setLogs(data);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
@@ -165,8 +166,10 @@ const CronDetailModal = ({
|
|||||||
setScriptInfo({ parent: p, filename: s });
|
setScriptInfo({ parent: p, filename: s });
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
|
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setValue(data.data);
|
if (code === 200) {
|
||||||
|
setValue(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setValidTabs([validTabs[0]]);
|
setValidTabs([validTabs[0]]);
|
||||||
@@ -198,12 +201,10 @@ const CronDetailModal = ({
|
|||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (_data.code === 200) {
|
if (code === 200) {
|
||||||
setValue(content);
|
setValue(content);
|
||||||
message.success(`保存成功`);
|
message.success(`保存成功`);
|
||||||
} else {
|
|
||||||
message.error(_data);
|
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
})
|
})
|
||||||
@@ -231,14 +232,12 @@ const CronDetailModal = ({
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
|
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
|
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getLogs();
|
getLogs();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -263,11 +262,9 @@ const CronDetailModal = ({
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
|
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
|
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -300,14 +297,12 @@ const CronDetailModal = ({
|
|||||||
data: [currentCron.id],
|
data: [currentCron.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
setCurrentCron({
|
setCurrentCron({
|
||||||
...currentCron,
|
...currentCron,
|
||||||
isDisabled: currentCron.isDisabled === 1 ? 0 : 1,
|
isDisabled: currentCron.isDisabled === 1 ? 0 : 1,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -340,14 +335,12 @@ const CronDetailModal = ({
|
|||||||
data: [currentCron.id],
|
data: [currentCron.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
setCurrentCron({
|
setCurrentCron({
|
||||||
...currentCron,
|
...currentCron,
|
||||||
isPinned: currentCron.isPinned === 1 ? 0 : 1,
|
isPinned: currentCron.isPinned === 1 ? 0 : 1,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -382,7 +375,7 @@ const CronDetailModal = ({
|
|||||||
{currentCron.labels?.length > 0 &&
|
{currentCron.labels?.length > 0 &&
|
||||||
currentCron.labels[0] !== '' &&
|
currentCron.labels[0] !== '' &&
|
||||||
currentCron.labels?.map((label: string, i: number) => (
|
currentCron.labels?.map((label: string, i: number) => (
|
||||||
<Tag color="blue" style={{ marginRight: 5 }}>
|
<Tag key={label} color="blue" style={{ marginRight: 5 }}>
|
||||||
{label}
|
{label}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
@@ -445,7 +438,7 @@ const CronDetailModal = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
centered
|
centered
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
footer={false}
|
footer={false}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
|||||||
+168
-172
@@ -1,4 +1,4 @@
|
|||||||
import React, { PureComponent, Fragment, useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
message,
|
message,
|
||||||
@@ -43,13 +43,13 @@ import CronLogModal from './logModal';
|
|||||||
import CronDetailModal from './detail';
|
import CronDetailModal from './detail';
|
||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
import { diffTime } from '@/utils/date';
|
import { diffTime } from '@/utils/date';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import { history } from 'umi';
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import ViewCreateModal from './viewCreateModal';
|
import ViewCreateModal from './viewCreateModal';
|
||||||
import ViewManageModal from './viewManageModal';
|
import ViewManageModal from './viewManageModal';
|
||||||
import pagination from 'antd/lib/pagination';
|
|
||||||
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -81,7 +81,8 @@ enum OperationPath {
|
|||||||
'unpin',
|
'unpin',
|
||||||
}
|
}
|
||||||
|
|
||||||
const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
const Crontab = () => {
|
||||||
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
@@ -106,6 +107,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
{record.labels?.map((label: string) => (
|
{record.labels?.map((label: string) => (
|
||||||
<Tag
|
<Tag
|
||||||
color="blue"
|
color="blue"
|
||||||
|
key={label}
|
||||||
style={{ cursor: 'point' }}
|
style={{ cursor: 'point' }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -373,7 +375,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
filters: any;
|
filters: any;
|
||||||
}>({} as any);
|
}>({} as any);
|
||||||
const [viewConf, setViewConf] = useState<any>();
|
const [viewConf, setViewConf] = useState<any>();
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
|
||||||
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
||||||
const [detailCron, setDetailCron] = useState<any>();
|
const [detailCron, setDetailCron] = useState<any>();
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
@@ -385,6 +386,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const [cronViews, setCronViews] = useState<any[]>([]);
|
const [cronViews, setCronViews] = useState<any[]>([]);
|
||||||
const [enabledCronViews, setEnabledCronViews] = useState<any[]>([]);
|
const [enabledCronViews, setEnabledCronViews] = useState<any[]>([]);
|
||||||
const [moreMenuActive, setMoreMenuActive] = useState(false);
|
const [moreMenuActive, setMoreMenuActive] = useState(false);
|
||||||
|
const tableRef = useRef<any>();
|
||||||
|
const tableScrollHeight = useTableScrollHeight(tableRef)
|
||||||
|
|
||||||
const goToScriptManager = (record: any) => {
|
const goToScriptManager = (record: any) => {
|
||||||
const cmd = record.command.split(' ') as string[];
|
const cmd = record.command.split(' ') as string[];
|
||||||
@@ -411,9 +414,15 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const getCrons = () => {
|
const getCrons = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { page, size, sorter, filters } = pageConf;
|
const { page, size, sorter, filters } = pageConf;
|
||||||
let url = `${config.apiPrefix}crons?searchValue=${searchText}&page=${page}&size=${size}&filters=${JSON.stringify(filters)}`;
|
let url = `${config.apiPrefix
|
||||||
|
}crons?searchValue=${searchText}&page=${page}&size=${size}&filters=${JSON.stringify(
|
||||||
|
filters,
|
||||||
|
)}`;
|
||||||
if (sorter && sorter.field) {
|
if (sorter && sorter.field) {
|
||||||
url += `&sorter=${JSON.stringify({ field: sorter.field, type: sorter.order === 'ascend' ? 'ASC' : 'DESC' })}`;
|
url += `&sorter=${JSON.stringify({
|
||||||
|
field: sorter.field,
|
||||||
|
type: sorter.order === 'ascend' ? 'ASC' : 'DESC',
|
||||||
|
})}`;
|
||||||
}
|
}
|
||||||
if (viewConf) {
|
if (viewConf) {
|
||||||
url += `&queryString=${JSON.stringify({
|
url += `&queryString=${JSON.stringify({
|
||||||
@@ -423,20 +432,22 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
}
|
}
|
||||||
request
|
request
|
||||||
.get(url)
|
.get(url)
|
||||||
.then((_data: any) => {
|
.then(({ code, data: _data }) => {
|
||||||
const { data, total } = _data.data;
|
if (code === 200) {
|
||||||
setValue(
|
const { data, total } = _data;
|
||||||
data.map((x) => {
|
setValue(
|
||||||
return {
|
data.map((x) => {
|
||||||
...x,
|
return {
|
||||||
nextRunTime: cron_parser
|
...x,
|
||||||
.parseExpression(x.schedule)
|
nextRunTime: cron_parser
|
||||||
.next()
|
.parseExpression(x.schedule)
|
||||||
.toDate(),
|
.next()
|
||||||
};
|
.toDate(),
|
||||||
}),
|
};
|
||||||
);
|
}),
|
||||||
setTotal(total);
|
);
|
||||||
|
setTotal(total);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -466,8 +477,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}crons`, { data: [record.id] })
|
.delete(`${config.apiPrefix}crons`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
@@ -475,8 +486,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
result.splice(i, 1);
|
result.splice(i, 1);
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -501,8 +510,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: 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(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
if (i !== -1) {
|
if (i !== -1) {
|
||||||
@@ -512,8 +521,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -538,8 +545,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: 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(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
if (i !== -1) {
|
if (i !== -1) {
|
||||||
@@ -550,8 +557,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -583,8 +588,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
data: [record.id],
|
data: [record.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
@@ -595,8 +600,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -628,8 +631,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
data: [record.id],
|
data: [record.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
@@ -640,8 +643,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -743,19 +744,21 @@ const Crontab = ({ headerStyle, isPhone, theme }: 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(({ code, data }) => {
|
||||||
const index = value.findIndex((x) => x.id === cron.id);
|
if (code === 200) {
|
||||||
const result = [...value];
|
const index = value.findIndex((x) => x.id === cron.id);
|
||||||
data.data.nextRunTime = cron_parser
|
const result = [...value];
|
||||||
.parseExpression(data.data.schedule)
|
data.nextRunTime = cron_parser
|
||||||
.next()
|
.parseExpression(data.schedule)
|
||||||
.toDate();
|
.next()
|
||||||
if (index !== -1) {
|
.toDate();
|
||||||
result.splice(index, 1, {
|
if (index !== -1) {
|
||||||
...cron,
|
result.splice(index, 1, {
|
||||||
...data.data,
|
...cron,
|
||||||
});
|
...data,
|
||||||
setValue(result);
|
});
|
||||||
|
setValue(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
@@ -763,16 +766,10 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const onSelectChange = (selectedIds: any[]) => {
|
const onSelectChange = (selectedIds: any[]) => {
|
||||||
setSelectedRowIds(selectedIds);
|
setSelectedRowIds(selectedIds);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
|
|
||||||
setTableScrollHeight(getTableScroll());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
selectedRowIds,
|
selectedRowKeys: selectedRowIds,
|
||||||
onChange: onSelectChange,
|
onChange: onSelectChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -783,13 +780,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}crons`, { data: selectedRowIds })
|
.delete(`${config.apiPrefix}crons`, { data: selectedRowIds })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
message.success('批量删除成功');
|
message.success('批量删除成功');
|
||||||
setSelectedRowIds([]);
|
setSelectedRowIds([]);
|
||||||
getCrons();
|
getCrons();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -808,11 +803,9 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
.put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
|
.put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
|
||||||
data: selectedRowIds,
|
data: selectedRowIds,
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
getCrons();
|
getCrons();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -828,7 +821,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
sorter: SorterResult<any> | SorterResult<any>[],
|
sorter: SorterResult<any> | SorterResult<any>[],
|
||||||
) => {
|
) => {
|
||||||
const { current, pageSize } = pagination;
|
const { current, pageSize } = pagination;
|
||||||
setPageConf({ page: current as number, size: pageSize as number, sorter, filters });
|
setPageConf({
|
||||||
|
page: current as number,
|
||||||
|
size: pageSize as number,
|
||||||
|
sorter,
|
||||||
|
filters,
|
||||||
|
});
|
||||||
localStorage.setItem('pageSize', String(pageSize));
|
localStorage.setItem('pageSize', String(pageSize));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -865,98 +863,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
page: 1,
|
page: 1,
|
||||||
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
||||||
sorter: {},
|
sorter: {},
|
||||||
filters: {}
|
filters: {},
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
setTableScrollHeight(getTableScroll());
|
|
||||||
});
|
});
|
||||||
getCronViews();
|
getCronViews();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const panelContent = (
|
|
||||||
<>
|
|
||||||
{selectedRowIds.length > 0 && (
|
|
||||||
<div style={{ marginBottom: 16 }}>
|
|
||||||
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
|
|
||||||
批量删除
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => operateCrons(0)}
|
|
||||||
style={{ marginLeft: 8, marginBottom: 5 }}
|
|
||||||
>
|
|
||||||
批量启用
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => operateCrons(1)}
|
|
||||||
style={{ marginLeft: 8, marginRight: 8 }}
|
|
||||||
>
|
|
||||||
批量禁用
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
onClick={() => operateCrons(2)}
|
|
||||||
>
|
|
||||||
批量运行
|
|
||||||
</Button>
|
|
||||||
<Button type="primary" onClick={() => operateCrons(3)}>
|
|
||||||
批量停止
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => operateCrons(4)}
|
|
||||||
style={{ marginLeft: 8, marginRight: 8 }}
|
|
||||||
>
|
|
||||||
批量置顶
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => operateCrons(5)}
|
|
||||||
style={{ marginLeft: 8, marginRight: 8 }}
|
|
||||||
>
|
|
||||||
批量取消置顶
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => setIsLabelModalVisible(true)}
|
|
||||||
style={{ marginLeft: 8, marginRight: 8 }}
|
|
||||||
>
|
|
||||||
批量修改标签
|
|
||||||
</Button>
|
|
||||||
<span style={{ marginLeft: 8 }}>
|
|
||||||
已选择
|
|
||||||
<a>{selectedRowIds?.length}</a>项
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
pagination={{
|
|
||||||
current: pageConf.page,
|
|
||||||
pageSize: pageConf.size,
|
|
||||||
showSizeChanger: true,
|
|
||||||
simple: isPhone,
|
|
||||||
total,
|
|
||||||
showTotal: (total: number, range: number[]) =>
|
|
||||||
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
|
||||||
pageSizeOptions: [10, 20, 50, 100, 200, 500, total || 10000].sort(
|
|
||||||
(a, b) => a - b,
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
dataSource={value}
|
|
||||||
rowKey="id"
|
|
||||||
size="middle"
|
|
||||||
scroll={{ x: 1000, y: tableScrollHeight }}
|
|
||||||
loading={loading}
|
|
||||||
rowSelection={rowSelection}
|
|
||||||
rowClassName={getRowClassName}
|
|
||||||
onChange={onPageChange}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewAction = (key: string) => {
|
const viewAction = (key: string) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'new':
|
case 'new':
|
||||||
@@ -1012,9 +923,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}crons/views`)
|
.get(`${config.apiPrefix}crons/views`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setCronViews(data.data);
|
if (code === 200) {
|
||||||
setEnabledCronViews(data.data.filter((x) => !x.isDisabled));
|
setCronViews(data);
|
||||||
|
setEnabledCronViews(data.filter((x) => !x.isDisabled));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -1023,6 +936,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const tabClick = (key: string) => {
|
const tabClick = (key: string) => {
|
||||||
const view = enabledCronViews.find((x) => x.id == key);
|
const view = enabledCronViews.find((x) => x.id == key);
|
||||||
|
setSelectedRowIds([]);
|
||||||
setPageConf({ ...pageConf, page: 1 });
|
setPageConf({ ...pageConf, page: 1 });
|
||||||
setViewConf(view ? view : null);
|
setViewConf(view ? view : null);
|
||||||
};
|
};
|
||||||
@@ -1071,16 +985,98 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
}
|
}
|
||||||
onTabClick={tabClick}
|
onTabClick={tabClick}
|
||||||
>
|
items={[
|
||||||
<Tabs.TabPane tab="全部任务" key="all">
|
{
|
||||||
{panelContent}
|
key: 'all',
|
||||||
</Tabs.TabPane>
|
label: '全部任务',
|
||||||
{[...enabledCronViews].slice(0, 2).map((x) => (
|
},
|
||||||
<Tabs.TabPane tab={x.name} key={x.id}>
|
...[...enabledCronViews].slice(0, 2).map((x) => ({
|
||||||
{panelContent}
|
key: x.id,
|
||||||
</Tabs.TabPane>
|
label: x.name,
|
||||||
))}
|
})),
|
||||||
</Tabs>
|
]}
|
||||||
|
/>
|
||||||
|
<div ref={tableRef}>
|
||||||
|
{selectedRowIds.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
|
||||||
|
批量删除
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => operateCrons(0)}
|
||||||
|
style={{ marginLeft: 8, marginBottom: 5 }}
|
||||||
|
>
|
||||||
|
批量启用
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => operateCrons(1)}
|
||||||
|
style={{ marginLeft: 8, marginRight: 8 }}
|
||||||
|
>
|
||||||
|
批量禁用
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
onClick={() => operateCrons(2)}
|
||||||
|
>
|
||||||
|
批量运行
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" onClick={() => operateCrons(3)}>
|
||||||
|
批量停止
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => operateCrons(4)}
|
||||||
|
style={{ marginLeft: 8, marginRight: 8 }}
|
||||||
|
>
|
||||||
|
批量置顶
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => operateCrons(5)}
|
||||||
|
style={{ marginLeft: 8, marginRight: 8 }}
|
||||||
|
>
|
||||||
|
批量取消置顶
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => setIsLabelModalVisible(true)}
|
||||||
|
style={{ marginLeft: 8, marginRight: 8 }}
|
||||||
|
>
|
||||||
|
批量修改标签
|
||||||
|
</Button>
|
||||||
|
<span style={{ marginLeft: 8 }}>
|
||||||
|
已选择
|
||||||
|
<a>{selectedRowIds?.length}</a>项
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
pagination={{
|
||||||
|
current: pageConf.page,
|
||||||
|
pageSize: pageConf.size,
|
||||||
|
showSizeChanger: true,
|
||||||
|
simple: isPhone,
|
||||||
|
total,
|
||||||
|
showTotal: (total: number, range: number[]) =>
|
||||||
|
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100, 200, 500, total || 10000].sort(
|
||||||
|
(a, b) => a - b,
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
dataSource={value}
|
||||||
|
rowKey="id"
|
||||||
|
size="middle"
|
||||||
|
scroll={{ x: 1000, y: tableScrollHeight }}
|
||||||
|
loading={loading}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
rowClassName={getRowClassName}
|
||||||
|
onChange={onPageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<CronLogModal
|
<CronLogModal
|
||||||
visible={isLogModalVisible}
|
visible={isLogModalVisible}
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
|
import { logEnded } from '@/utils';
|
||||||
|
|
||||||
enum CrontabStatus {
|
enum CrontabStatus {
|
||||||
'running',
|
'running',
|
||||||
@@ -41,14 +42,17 @@ const CronLogModal = ({
|
|||||||
}
|
}
|
||||||
request
|
request
|
||||||
.get(logUrl ? logUrl : `${config.apiPrefix}crons/${cron.id}/log`)
|
.get(logUrl ? logUrl : `${config.apiPrefix}crons/${cron.id}/log`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (localStorage.getItem('logCron') === String(cron.id)) {
|
if (
|
||||||
const log = data.data as string;
|
code === 200 &&
|
||||||
|
localStorage.getItem('logCron') === String(cron.id)
|
||||||
|
) {
|
||||||
|
const log = data as string;
|
||||||
setValue(log || '暂无日志');
|
setValue(log || '暂无日志');
|
||||||
setExecuting(
|
setExecuting(
|
||||||
log && !log.includes('执行结束') && !log.includes('重启面板'),
|
log && !logEnded(log) && !log.includes('重启面板'),
|
||||||
);
|
);
|
||||||
if (log && !log.includes('执行结束') && !log.includes('重启面板')) {
|
if (log && !logEnded(log) && !log.includes('重启面板')) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getCronLog();
|
getCronLog();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -119,7 +123,7 @@ const CronLogModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
bodyStyle={{
|
bodyStyle={{
|
||||||
|
|||||||
+20
-18
@@ -28,13 +28,12 @@ const CronModal = ({
|
|||||||
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
|
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
|
||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
||||||
} else {
|
handleCancel(data);
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -47,7 +46,7 @@ const CronModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={cron ? '编辑任务' : '新建任务'}
|
title={cron ? '编辑任务' : '新建任务'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -128,21 +127,24 @@ const CronLabelModal = ({
|
|||||||
.then(async (values) => {
|
.then(async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const payload = { ids, labels: values.labels };
|
const payload = { ids, labels: values.labels };
|
||||||
const { code, data } = await request[action](
|
try {
|
||||||
`${config.apiPrefix}crons/labels`,
|
const { code, data } = await request[action](
|
||||||
{
|
`${config.apiPrefix}crons/labels`,
|
||||||
data: payload,
|
{
|
||||||
},
|
data: payload,
|
||||||
);
|
},
|
||||||
if (code === 200) {
|
|
||||||
message.success(
|
|
||||||
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
message.error(data);
|
if (code === 200) {
|
||||||
|
message.success(
|
||||||
|
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
||||||
|
);
|
||||||
|
handleCancel(true);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel(true);
|
|
||||||
})
|
})
|
||||||
.catch((info) => {
|
.catch((info) => {
|
||||||
console.log('Validate Failed:', info);
|
console.log('Validate Failed:', info);
|
||||||
@@ -166,7 +168,7 @@ const CronLabelModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="批量修改标签"
|
title="批量修改标签"
|
||||||
visible={visible}
|
open={visible}
|
||||||
footer={buttons}
|
footer={buttons}
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ const ViewCreateModal = ({
|
|||||||
data: view ? { ...values, id: view.id } : values,
|
data: view ? { ...values, id: view.id } : values,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (code !== 200) {
|
|
||||||
message.error(data);
|
if (code === 200) {
|
||||||
|
handleCancel(data);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ const ViewCreateModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={view ? '编辑视图' : '新建视图'}
|
title={view ? '编辑视图' : '新建视图'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
width={580}
|
width={580}
|
||||||
centered
|
centered
|
||||||
|
|||||||
@@ -141,12 +141,10 @@ const ViewManageModal = ({
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}crons/views`, { data: [record.id] })
|
.delete(`${config.apiPrefix}crons/views`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
cronViewChange();
|
cronViewChange();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -162,14 +160,12 @@ const ViewManageModal = ({
|
|||||||
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
|
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
|
||||||
data: [record.id],
|
data: [record.id],
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
const _list = [...list];
|
const _list = [...list];
|
||||||
_list.splice(index, 1, { ...list[index], isDisabled: !checked });
|
_list.splice(index, 1, { ...list[index], isDisabled: !checked });
|
||||||
setList(_list);
|
setList(_list);
|
||||||
cronViewChange();
|
cronViewChange();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -190,15 +186,13 @@ const ViewManageModal = ({
|
|||||||
.put(`${config.apiPrefix}crons/views/move`, {
|
.put(`${config.apiPrefix}crons/views/move`, {
|
||||||
data: { fromIndex: dragIndex, toIndex: hoverIndex, id: dragRow.id },
|
data: { fromIndex: dragIndex, toIndex: hoverIndex, id: dragRow.id },
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
const newData = [...list];
|
const newData = [...list];
|
||||||
newData.splice(dragIndex, 1);
|
newData.splice(dragIndex, 1);
|
||||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
newData.splice(hoverIndex, 0, { ...dragRow, ...data });
|
||||||
setList(newData);
|
setList(newData);
|
||||||
cronViewChange();
|
cronViewChange();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -212,7 +206,7 @@ const ViewManageModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="视图管理"
|
title="视图管理"
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
width={620}
|
width={620}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
|||||||
+118
-97
@@ -27,8 +27,11 @@ import DependenceModal from './modal';
|
|||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { getTableScroll } from '@/utils/index';
|
|
||||||
import DependenceLogModal from './logModal';
|
import DependenceLogModal from './logModal';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
||||||
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -48,7 +51,9 @@ enum StatusColor {
|
|||||||
'error',
|
'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
const Dependence = () => {
|
||||||
|
const { headerStyle, isPhone, socketMessage } =
|
||||||
|
useOutletContext<SharedContext>();
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '序号',
|
title: '序号',
|
||||||
@@ -160,10 +165,11 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
const [editedDependence, setEditedDependence] = useState();
|
const [editedDependence, setEditedDependence] = useState();
|
||||||
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
|
||||||
const [logDependence, setLogDependence] = useState<any>();
|
const [logDependence, setLogDependence] = useState<any>();
|
||||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||||
const [type, setType] = useState('nodejs');
|
const [type, setType] = useState('nodejs');
|
||||||
|
const tableRef = useRef<any>();
|
||||||
|
const tableScrollHeight = useTableScrollHeight(tableRef, 59)
|
||||||
|
|
||||||
const getDependencies = () => {
|
const getDependencies = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -171,8 +177,10 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
.get(
|
.get(
|
||||||
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
|
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setValue(data.data);
|
if (code === 200) {
|
||||||
|
setValue(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -208,18 +216,14 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
.delete(`${config.apiPrefix}dependencies${force ? '/force' : ''}`, {
|
.delete(`${config.apiPrefix}dependencies${force ? '/force' : ''}`, {
|
||||||
data: [record.id],
|
data: [record.id],
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200 && force) {
|
||||||
if (force) {
|
const i = value.findIndex((x) => x.id === data.data[0].id);
|
||||||
const i = value.findIndex((x) => x.id === data.data[0].id);
|
if (i !== -1) {
|
||||||
if (i !== -1) {
|
const result = [...value];
|
||||||
const result = [...value];
|
result.splice(i, 1);
|
||||||
result.splice(i, 1);
|
setValue(result);
|
||||||
setValue(result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -246,11 +250,9 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||||
data: [record.id],
|
data: [record.id],
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
handleDependence(data.data[0]);
|
handleDependence(data[0]);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -282,16 +284,10 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
|
|
||||||
const onSelectChange = (selectedIds: any[]) => {
|
const onSelectChange = (selectedIds: any[]) => {
|
||||||
setSelectedRowIds(selectedIds);
|
setSelectedRowIds(selectedIds);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
|
|
||||||
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
selectedRowIds,
|
selectedRowKeys: selectedRowIds,
|
||||||
onChange: onSelectChange,
|
onChange: onSelectChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -305,12 +301,32 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
.delete(`${config.apiPrefix}dependencies${forceUrl}`, {
|
.delete(`${config.apiPrefix}dependencies${forceUrl}`, {
|
||||||
data: selectedRowIds,
|
data: selectedRowIds,
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
|
setSelectedRowIds([]);
|
||||||
|
getDependencies();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlereInstallDependencies = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认重新安装',
|
||||||
|
content: <>确认重新安装选中的依赖吗</>,
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||||
|
data: selectedRowIds,
|
||||||
|
})
|
||||||
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200) {
|
||||||
setSelectedRowIds([]);
|
setSelectedRowIds([]);
|
||||||
getDependencies();
|
getDependencies();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -323,15 +339,17 @@ 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(({ code, data }) => {
|
||||||
const index = value.findIndex((x) => x.id === dependence.id);
|
if (code === 200) {
|
||||||
const result = [...value];
|
const index = value.findIndex((x) => x.id === dependence.id);
|
||||||
if (index !== -1) {
|
const result = [...value];
|
||||||
result.splice(index, 1, {
|
if (index !== -1) {
|
||||||
...dependence,
|
result.splice(index, 1, {
|
||||||
...data.data,
|
...dependence,
|
||||||
});
|
...data,
|
||||||
setValue(result);
|
});
|
||||||
|
setValue(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
@@ -345,12 +363,6 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
getDependencies();
|
getDependencies();
|
||||||
}, [searchText, type]);
|
}, [searchText, type]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (logDependence) {
|
if (logDependence) {
|
||||||
localStorage.setItem('logDependence', logDependence.id);
|
localStorage.setItem('logDependence', logDependence.id);
|
||||||
@@ -399,46 +411,8 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
}
|
}
|
||||||
}, [socketMessage]);
|
}, [socketMessage]);
|
||||||
|
|
||||||
const panelContent = () => (
|
|
||||||
<>
|
|
||||||
{selectedRowIds.length > 0 && (
|
|
||||||
<div style={{ marginBottom: 16 }}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
|
||||||
onClick={() => delDependencies(false)}
|
|
||||||
>
|
|
||||||
批量删除
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
|
||||||
onClick={() => delDependencies(true)}
|
|
||||||
>
|
|
||||||
批量强制删除
|
|
||||||
</Button>
|
|
||||||
<span style={{ marginLeft: 8 }}>
|
|
||||||
已选择
|
|
||||||
<a>{selectedRowIds?.length}</a>项
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<DndProvider backend={HTML5Backend}>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
rowSelection={rowSelection}
|
|
||||||
pagination={false}
|
|
||||||
dataSource={value}
|
|
||||||
rowKey="id"
|
|
||||||
size="middle"
|
|
||||||
scroll={{ x: 768, y: tableScrollHeight }}
|
|
||||||
loading={loading}
|
|
||||||
/>
|
|
||||||
</DndProvider>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const onTabChange = (activeKey: string) => {
|
const onTabChange = (activeKey: string) => {
|
||||||
|
setSelectedRowIds([]);
|
||||||
setType(activeKey);
|
setType(activeKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -467,17 +441,64 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
size="small"
|
size="small"
|
||||||
tabPosition="top"
|
tabPosition="top"
|
||||||
onChange={onTabChange}
|
onChange={onTabChange}
|
||||||
>
|
items={[
|
||||||
<Tabs.TabPane tab="NodeJs" key="nodejs">
|
{
|
||||||
{panelContent()}
|
key: 'nodejs',
|
||||||
</Tabs.TabPane>
|
label: 'NodeJs',
|
||||||
<Tabs.TabPane tab="Python3" key="python3">
|
},
|
||||||
{panelContent()}
|
{
|
||||||
</Tabs.TabPane>
|
key: 'python3',
|
||||||
<Tabs.TabPane tab="Linux" key="linux">
|
label: 'Python3',
|
||||||
{panelContent()}
|
},
|
||||||
</Tabs.TabPane>
|
{
|
||||||
</Tabs>
|
key: 'linux',
|
||||||
|
label: 'Linux',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<div ref={tableRef}>
|
||||||
|
{selectedRowIds.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
|
onClick={() => handlereInstallDependencies()}
|
||||||
|
>
|
||||||
|
批量安装
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
|
onClick={() => delDependencies(false)}
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
|
onClick={() => delDependencies(true)}
|
||||||
|
>
|
||||||
|
批量强制删除
|
||||||
|
</Button>
|
||||||
|
<span style={{ marginLeft: 8 }}>
|
||||||
|
已选择
|
||||||
|
<a>{selectedRowIds?.length}</a>项
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={value}
|
||||||
|
rowKey="id"
|
||||||
|
size="middle"
|
||||||
|
scroll={{ x: 768, y: tableScrollHeight }}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</DndProvider>
|
||||||
|
</div>
|
||||||
<DependenceModal
|
<DependenceModal
|
||||||
visible={isModalVisible}
|
visible={isModalVisible}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
|
|||||||
@@ -48,9 +48,12 @@ const DependenceLogModal = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (localStorage.getItem('logDependence') === String(dependence.id)) {
|
if (
|
||||||
const log = (data.data.log || []).join('') as string;
|
code === 200 &&
|
||||||
|
localStorage.getItem('logDependence') === String(dependence.id)
|
||||||
|
) {
|
||||||
|
const log = (data.log || []).join('') as string;
|
||||||
setValue(log);
|
setValue(log);
|
||||||
setExecuting(!log.includes('结束时间'));
|
setExecuting(!log.includes('结束时间'));
|
||||||
setIsRemoveFailed(log.includes('删除失败'));
|
setIsRemoveFailed(log.includes('删除失败'));
|
||||||
@@ -67,8 +70,10 @@ const DependenceLogModal = ({
|
|||||||
.delete(`${config.apiPrefix}dependencies/force`, {
|
.delete(`${config.apiPrefix}dependencies/force`, {
|
||||||
data: [dependence.id],
|
data: [dependence.id],
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
cancel(true);
|
if (code === 200) {
|
||||||
|
cancel(true);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setRemoveLoading(false);
|
setRemoveLoading(false);
|
||||||
@@ -112,7 +117,7 @@ const DependenceLogModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
bodyStyle={{
|
bodyStyle={{
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ const DependenceModal = ({
|
|||||||
data: payload,
|
data: payload,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (code !== 200) {
|
|
||||||
message.error(data);
|
if (code === 200) {
|
||||||
|
handleCancel(data);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ const DependenceModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={dependence ? '编辑依赖' : '新建依赖'}
|
title={dependence ? '编辑依赖' : '新建依赖'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -99,7 +99,9 @@ const DependenceModal = ({
|
|||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{config.dependenceTypes.map((x, i) => (
|
{config.dependenceTypes.map((x, i) => (
|
||||||
<Option value={i}>{x}</Option>
|
<Option key={i} value={i}>
|
||||||
|
{x}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -17,4 +17,8 @@
|
|||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ section {
|
||||||
|
height: calc(100% - 40px) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-13
@@ -6,10 +6,13 @@ import { request } from '@/utils/http';
|
|||||||
import './index.less';
|
import './index.less';
|
||||||
import { DiffEditor } from '@monaco-editor/react';
|
import { DiffEditor } from '@monaco-editor/react';
|
||||||
import ReactDiffViewer from 'react-diff-viewer';
|
import ReactDiffViewer from 'react-diff-viewer';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
const Diff = () => {
|
||||||
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const [origin, setOrigin] = useState('config.sample.sh');
|
const [origin, setOrigin] = useState('config.sample.sh');
|
||||||
const [current, setCurrent] = useState('config.sh');
|
const [current, setCurrent] = useState('config.sh');
|
||||||
const [originValue, setOriginValue] = useState('');
|
const [originValue, setOriginValue] = useState('');
|
||||||
@@ -19,15 +22,23 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
|
|
||||||
const getConfig = () => {
|
const getConfig = () => {
|
||||||
request.get(`${config.apiPrefix}configs/${current}`).then((data) => {
|
request
|
||||||
setCurrentValue(data.data);
|
.get(`${config.apiPrefix}configs/${current}`)
|
||||||
});
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200) {
|
||||||
|
setCurrentValue(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSample = () => {
|
const getSample = () => {
|
||||||
request.get(`${config.apiPrefix}configs/${origin}`).then((data) => {
|
request
|
||||||
setOriginValue(data.data);
|
.get(`${config.apiPrefix}configs/${origin}`)
|
||||||
});
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200) {
|
||||||
|
setOriginValue(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateConfig = () => {
|
const updateConfig = () => {
|
||||||
@@ -39,8 +50,10 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
.post(`${config.apiPrefix}configs/save`, {
|
.post(`${config.apiPrefix}configs/save`, {
|
||||||
data: { content, name: current },
|
data: { content, name: current },
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
message.success(data.message);
|
if (code === 200) {
|
||||||
|
message.success('保存成功');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,8 +61,10 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}configs/files`)
|
.get(`${config.apiPrefix}configs/files`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setFiles(data.data);
|
if (code === 200) {
|
||||||
|
setFiles(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -95,7 +110,9 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<Form.Item label="源文件">
|
<Form.Item label="源文件">
|
||||||
<Select value={origin} onChange={originFileChange}>
|
<Select value={origin} onChange={originFileChange}>
|
||||||
{files.map((x) => (
|
{files.map((x) => (
|
||||||
<Option value={x.value}>{x.title}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.title}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -104,7 +121,9 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<Form.Item label="当前文件">
|
<Form.Item label="当前文件">
|
||||||
<Select value={current} onChange={currentFileChange}>
|
<Select value={current} onChange={currentFileChange}>
|
||||||
{files.map((x) => (
|
{files.map((x) => (
|
||||||
<Option value={x.value}>{x.title}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.title}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
Vendored
+16
-13
@@ -17,19 +17,22 @@ const EditNameModal = ({
|
|||||||
|
|
||||||
const handleOk = async (values: any) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { code, data } = await request.put(`${config.apiPrefix}envs/name`, {
|
try {
|
||||||
data: {
|
const { code, data } = await request.put(`${config.apiPrefix}envs/name`, {
|
||||||
ids,
|
data: {
|
||||||
name: values.name,
|
ids,
|
||||||
},
|
name: values.name,
|
||||||
});
|
},
|
||||||
if (code === 200) {
|
});
|
||||||
message.success('更新环境变量名称成功');
|
|
||||||
} else {
|
if (code === 200) {
|
||||||
message.error(data);
|
message.success('更新环境变量名称成功');
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,7 +42,7 @@ const EditNameModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="修改环境变量名称"
|
title="修改环境变量名称"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
Vendored
+67
-41
@@ -9,6 +9,8 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Input,
|
Input,
|
||||||
|
UploadProps,
|
||||||
|
Upload,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
@@ -16,6 +18,7 @@ import {
|
|||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
StopOutlined,
|
StopOutlined,
|
||||||
|
UploadOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
@@ -25,7 +28,10 @@ import EditNameModal from './editNameModal';
|
|||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { exportJson, getTableScroll } from '@/utils/index';
|
import { exportJson } from '@/utils/index';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -96,7 +102,8 @@ const DragableBodyRow = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Env = ({ headerStyle, isPhone, theme }: any) => {
|
const Env = () => {
|
||||||
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '序号',
|
title: '序号',
|
||||||
@@ -128,6 +135,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
}}
|
}}
|
||||||
ellipsis={{ tooltip: text, rows: 2 }}
|
ellipsis={{ tooltip: text, rows: 2 }}
|
||||||
|
copyable
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -246,14 +254,18 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const [editedEnv, setEditedEnv] = useState();
|
const [editedEnv, setEditedEnv] = useState();
|
||||||
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
const [importLoading, setImportLoading] = useState(false);
|
||||||
|
const tableRef = useRef<any>();
|
||||||
|
const tableScrollHeight = useTableScrollHeight(tableRef, 59)
|
||||||
|
|
||||||
const getEnvs = () => {
|
const getEnvs = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}envs?searchValue=${searchText}`)
|
.get(`${config.apiPrefix}envs?searchValue=${searchText}`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setValue(data.data);
|
if (code === 200) {
|
||||||
|
setValue(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -274,15 +286,14 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(
|
.put(
|
||||||
`${config.apiPrefix}envs/${
|
`${config.apiPrefix}envs/${record.status === Status.已禁用 ? 'enable' : 'disable'
|
||||||
record.status === Status.已禁用 ? 'enable' : 'disable'
|
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
data: [record.id],
|
data: [record.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
message.success(
|
message.success(
|
||||||
`${record.status === Status.已禁用 ? '启用' : '禁用'}成功`,
|
`${record.status === Status.已禁用 ? '启用' : '禁用'}成功`,
|
||||||
);
|
);
|
||||||
@@ -294,8 +305,6 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
status: newStatus,
|
status: newStatus,
|
||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -330,14 +339,12 @@ 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(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
result.splice(index, 1);
|
result.splice(index, 1);
|
||||||
setValue(result);
|
setValue(result);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -387,14 +394,12 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
.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(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
const newData = [...value];
|
const newData = [...value];
|
||||||
newData.splice(dragIndex, 1);
|
newData.splice(dragIndex, 1);
|
||||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
||||||
setValue([...newData]);
|
setValue([...newData]);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -403,16 +408,10 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const onSelectChange = (selectedIds: any[]) => {
|
const onSelectChange = (selectedIds: any[]) => {
|
||||||
setSelectedRowIds(selectedIds);
|
setSelectedRowIds(selectedIds);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
|
|
||||||
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
selectedRowIds,
|
selectedRowKeys: selectedRowIds,
|
||||||
onChange: onSelectChange,
|
onChange: onSelectChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -423,13 +422,11 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}envs`, { data: selectedRowIds })
|
.delete(`${config.apiPrefix}envs`, { data: selectedRowIds })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
message.success('批量删除成功');
|
message.success('批量删除成功');
|
||||||
setSelectedRowIds([]);
|
setSelectedRowIds([]);
|
||||||
getEnvs();
|
getEnvs();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -448,11 +445,9 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
.put(`${config.apiPrefix}envs/${OperationPath[operationStatus]}`, {
|
.put(`${config.apiPrefix}envs/${OperationPath[operationStatus]}`, {
|
||||||
data: selectedRowIds,
|
data: selectedRowIds,
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
getEnvs();
|
getEnvs();
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -477,16 +472,37 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
setSearchText(value.trim());
|
setSearchText(value.trim());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uploadProps: UploadProps = {
|
||||||
|
accept: 'application/json',
|
||||||
|
beforeUpload: async (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('env', file);
|
||||||
|
setImportLoading(true);
|
||||||
|
try {
|
||||||
|
const { code, data } = await request.post(
|
||||||
|
`${config.apiPrefix}envs/upload`,
|
||||||
|
{
|
||||||
|
data: formData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(`成功上传${data.length}个环境变量`);
|
||||||
|
getEnvs();
|
||||||
|
}
|
||||||
|
setImportLoading(false);
|
||||||
|
} catch (error: any) {
|
||||||
|
setImportLoading(false);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
fileList: [],
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getEnvs();
|
getEnvs();
|
||||||
}, [searchText]);
|
}, [searchText]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper env-wrapper"
|
className="ql-container-wrapper env-wrapper"
|
||||||
@@ -499,6 +515,15 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
|
<Upload {...uploadProps}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
loading={importLoading}
|
||||||
|
>
|
||||||
|
导入
|
||||||
|
</Button>
|
||||||
|
</Upload>,
|
||||||
<Button key="2" type="primary" onClick={() => addEnv()}>
|
<Button key="2" type="primary" onClick={() => addEnv()}>
|
||||||
新建变量
|
新建变量
|
||||||
</Button>,
|
</Button>,
|
||||||
@@ -552,6 +577,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
)}
|
)}
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Table
|
<Table
|
||||||
|
ref={tableRef}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
@@ -561,7 +587,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
scroll={{ x: 1000, y: tableScrollHeight }}
|
scroll={{ x: 1000, y: tableScrollHeight }}
|
||||||
components={components}
|
components={components}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onRow={(record: any, index: number) => {
|
onRow={(record: any, index: number | undefined) => {
|
||||||
return {
|
return {
|
||||||
index,
|
index,
|
||||||
moveRow,
|
moveRow,
|
||||||
|
|||||||
Vendored
+3
-4
@@ -40,13 +40,12 @@ const EnvModal = ({
|
|||||||
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(env ? '更新变量成功' : '新建变量成功');
|
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||||
} else {
|
handleCancel(data);
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -59,7 +58,7 @@ const EnvModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={env ? '编辑变量' : '新建变量'}
|
title={env ? '编辑变量' : '新建变量'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import config from '@/utils/config';
|
|||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import Terminal, { ColorMode, LineType } from '../../components/terminal';
|
import Terminal, { ColorMode, LineType } from '../../components/terminal';
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
import { history } from 'umi';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import Ansi from 'ansi-to-react';
|
import Ansi from 'ansi-to-react';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const Error = ({ user, theme }: any) => {
|
const Error = () => {
|
||||||
|
const { user, theme } = useOutletContext<SharedContext>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState('暂无日志');
|
const [data, setData] = useState('暂无日志');
|
||||||
|
|
||||||
@@ -15,8 +17,10 @@ const Error = ({ user, theme }: any) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}public/panel/log`)
|
.get(`${config.apiPrefix}public/panel/log`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setData(data.data);
|
if (code === 200) {
|
||||||
|
setData(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { history } from 'umi';
|
import { history } from '@umijs/max';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
|
|
||||||
@@ -41,11 +41,9 @@ const Initialization = () => {
|
|||||||
password: values.password,
|
password: values.password,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
next();
|
next();
|
||||||
} else {
|
|
||||||
message.error(data.message);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
@@ -59,11 +57,9 @@ const Initialization = () => {
|
|||||||
...values,
|
...values,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (_data && _data.code === 200) {
|
if (code === 200) {
|
||||||
next();
|
next();
|
||||||
} else {
|
|
||||||
message.error(_data.message);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
@@ -116,12 +112,15 @@ const Initialization = () => {
|
|||||||
{config.notificationModes
|
{config.notificationModes
|
||||||
.filter((x) => x.value !== 'closed')
|
.filter((x) => x.value !== 'closed')
|
||||||
.map((x) => (
|
.map((x) => (
|
||||||
<Option value={x.value}>{x.label}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.label}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{fields.map((x) => (
|
{fields.map((x) => (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
key={x.label}
|
||||||
label={x.label}
|
label={x.label}
|
||||||
name={x.label}
|
name={x.label}
|
||||||
extra={x.tip}
|
extra={x.tip}
|
||||||
@@ -233,7 +232,7 @@ const Initialization = () => {
|
|||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src="http://qn.whyour.cn/logo.png"
|
src="https://qn.whyour.cn/logo.png"
|
||||||
/>
|
/>
|
||||||
<span className={styles.title}>初始化配置</span>
|
<span className={styles.title}>初始化配置</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: @component-background;
|
background-color: @component-background;
|
||||||
height: calc(100vh - 128px);
|
height: 100%;
|
||||||
height: calc(100vh - var(--vh-offset, 0px) - 128px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
+156
-61
@@ -1,5 +1,15 @@
|
|||||||
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
||||||
import { TreeSelect, Tree, Input, Empty } from 'antd';
|
import {
|
||||||
|
TreeSelect,
|
||||||
|
Tree,
|
||||||
|
Input,
|
||||||
|
Empty,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} 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';
|
||||||
@@ -7,53 +17,36 @@ 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 SplitPane from 'react-split-pane';
|
import SplitPane from 'react-split-pane';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import { depthFirstSearch } from '@/utils';
|
||||||
|
import debounce from 'lodash/groupBy';
|
||||||
|
import uniq from 'lodash/uniq';
|
||||||
|
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||||
|
|
||||||
function getFilterData(keyword: string, data: any) {
|
const { Text } = Typography;
|
||||||
const expandedKeys: string[] = [];
|
|
||||||
if (keyword) {
|
|
||||||
const tree: any = [];
|
|
||||||
data.forEach((item: any) => {
|
|
||||||
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
|
||||||
tree.push(item);
|
|
||||||
} else {
|
|
||||||
const children: any[] = [];
|
|
||||||
(item.children || []).forEach((subItem: any) => {
|
|
||||||
if (subItem.title.toLocaleLowerCase().includes(keyword)) {
|
|
||||||
children.push(subItem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (children.length > 0) {
|
|
||||||
tree.push({
|
|
||||||
...item,
|
|
||||||
children,
|
|
||||||
});
|
|
||||||
expandedKeys.push(item.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { tree, expandedKeys };
|
|
||||||
}
|
|
||||||
return { tree: data, expandedKeys };
|
|
||||||
}
|
|
||||||
|
|
||||||
const Log = ({ headerStyle, isPhone, theme }: any) => {
|
const Log = () => {
|
||||||
const [title, setTitle] = useState('请选择日志文件');
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const [value, setValue] = useState('请选择日志文件');
|
const [value, setValue] = useState('请选择日志文件');
|
||||||
const [select, setSelect] = useState<any>();
|
const [select, setSelect] = useState<string>('');
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [filterData, setFilterData] = useState<any[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [height, setHeight] = useState<number>();
|
const [height, setHeight] = useState<number>();
|
||||||
const treeDom = useRef<any>();
|
const treeDom = useRef<any>();
|
||||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||||
|
const [currentNode, setCurrentNode] = useState<any>();
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
const getLogs = () => {
|
const getLogs = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}logs`)
|
.get(`${config.apiPrefix}logs`)
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setData(data.data);
|
if (code === 200) {
|
||||||
setFilterData(data.data);
|
setData(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -61,18 +54,27 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const getLog = (node: any) => {
|
const getLog = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
|
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setValue(data.data);
|
if (code === 200) {
|
||||||
|
setValue(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
|
setCurrentNode(node);
|
||||||
|
setSelect(value);
|
||||||
|
|
||||||
if (node.key === select || !value) {
|
if (node.key === select || !value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.type === 'directory') {
|
||||||
|
setValue('请选择日志文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setValue('加载中...');
|
setValue('加载中...');
|
||||||
setSelect(value);
|
|
||||||
setTitle(node.key);
|
|
||||||
getLog(node);
|
getLog(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,41 +85,130 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
const { tree, expandedKeys } = getFilterData(
|
debounceSearch(keyword);
|
||||||
keyword.toLocaleLowerCase(),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
setFilterData(tree);
|
|
||||||
setExpandedKeys(expandedKeys);
|
|
||||||
},
|
},
|
||||||
[data, setFilterData],
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const debounceSearch = useCallback(
|
||||||
|
debounce((keyword) => {
|
||||||
|
setSearchValue(keyword);
|
||||||
|
}, 300),
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
|
||||||
|
data,
|
||||||
|
searchValue,
|
||||||
|
{ treeNodeFilterProp: 'title' },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
|
||||||
|
}, [searchExpandedKeys]);
|
||||||
|
|
||||||
|
const deleteFile = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: `确认删除`,
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认删除
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{select}
|
||||||
|
</Text>
|
||||||
|
文件{currentNode.type === 'directory' ? '夹下所以日志' : ''}
|
||||||
|
,删除后不可恢复
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.delete(`${config.apiPrefix}logs`, {
|
||||||
|
data: {
|
||||||
|
filename: currentNode.title,
|
||||||
|
path: currentNode.parent || '',
|
||||||
|
type: currentNode.type,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ code }) => {
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(`删除成功`);
|
||||||
|
let newData = [...data];
|
||||||
|
if (currentNode.parent) {
|
||||||
|
newData = depthFirstSearch(
|
||||||
|
newData,
|
||||||
|
(c) => c.key === currentNode.key,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const index = newData.findIndex(
|
||||||
|
(x) => x.key === currentNode.key,
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
newData.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setData(newData);
|
||||||
|
initState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initState = () => {
|
||||||
|
setSelect('');
|
||||||
|
setCurrentNode(null);
|
||||||
|
setValue('请选择脚本文件');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExpand = (expKeys: any) => {
|
||||||
|
setExpandedKeys(expKeys);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLogs();
|
getLogs();
|
||||||
if (treeDom && treeDom.current) {
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (treeDom.current) {
|
||||||
setHeight(treeDom.current.clientHeight);
|
setHeight(treeDom.current.clientHeight);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [treeDom.current, data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper log-wrapper"
|
className="ql-container-wrapper log-wrapper"
|
||||||
title={title}
|
title={select}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
extra={
|
extra={
|
||||||
isPhone && [
|
isPhone
|
||||||
<TreeSelect
|
? [
|
||||||
className="log-select"
|
<TreeSelect
|
||||||
value={select}
|
treeExpandAction="click"
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
className="log-select"
|
||||||
treeData={data}
|
value={select}
|
||||||
placeholder="请选择日志"
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
fieldNames={{ value: 'key', label: 'title' }}
|
treeData={data}
|
||||||
showSearch
|
placeholder="请选择日志"
|
||||||
onSelect={onSelect}
|
fieldNames={{ value: 'key' }}
|
||||||
/>,
|
treeNodeFilterProp="title"
|
||||||
]
|
showSearch
|
||||||
|
allowClear
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
<Tooltip title="删除">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
disabled={!select}
|
||||||
|
onClick={deleteFile}
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
/>
|
||||||
|
</Tooltip>,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
header={{
|
header={{
|
||||||
style: headerStyle,
|
style: headerStyle,
|
||||||
@@ -125,6 +216,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
>
|
>
|
||||||
<div className={`${styles['log-container']} log-container`}>
|
<div className={`${styles['log-container']} log-container`}>
|
||||||
{!isPhone && (
|
{!isPhone && (
|
||||||
|
/*// @ts-ignore*/
|
||||||
<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']}>
|
||||||
{data.length > 0 ? (
|
{data.length > 0 ? (
|
||||||
@@ -137,6 +229,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
></Input.Search>
|
></Input.Search>
|
||||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||||
<Tree
|
<Tree
|
||||||
|
expandAction="click"
|
||||||
className={styles['left-tree']}
|
className={styles['left-tree']}
|
||||||
treeData={filterData}
|
treeData={filterData}
|
||||||
showIcon={true}
|
showIcon={true}
|
||||||
@@ -144,6 +237,8 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
selectedKeys={[select]}
|
selectedKeys={[select]}
|
||||||
showLine={{ showLeafIcon: true }}
|
showLine={{ showLeafIcon: true }}
|
||||||
onSelect={onTreeSelect}
|
onSelect={onTreeSelect}
|
||||||
|
expandedKeys={expandedKeys}
|
||||||
|
onExpand={onExpand}
|
||||||
></Tree>
|
></Tree>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+21
-27
@@ -9,16 +9,18 @@ import {
|
|||||||
Statistic,
|
Statistic,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { history, Link } from 'umi';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import { useTheme } from '@/utils/hooks';
|
import { useTheme } from '@/utils/hooks';
|
||||||
import { MobileOutlined } from '@ant-design/icons';
|
import { MobileOutlined } from '@ant-design/icons';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const { Countdown } = Statistic;
|
const { Countdown } = Statistic;
|
||||||
|
|
||||||
const Login = ({ reloadUser }: any) => {
|
const Login = () => {
|
||||||
|
const { reloadUser } = useOutletContext<SharedContext>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [waitTime, setWaitTime] = useState<any>();
|
const [waitTime, setWaitTime] = useState<any>();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -38,15 +40,7 @@ const Login = ({ reloadUser }: any) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code === 420) {
|
checkResponse(data, values);
|
||||||
setLoginInfo({
|
|
||||||
username: values.username,
|
|
||||||
password: values.password,
|
|
||||||
});
|
|
||||||
setTwoFactor(true);
|
|
||||||
} else {
|
|
||||||
checkResponse(data);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
@@ -62,11 +56,7 @@ const Login = ({ reloadUser }: any) => {
|
|||||||
data: { ...loginInfo, code: values.code },
|
data: { ...loginInfo, code: values.code },
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 430) {
|
checkResponse(data);
|
||||||
message.error(data.message);
|
|
||||||
} else {
|
|
||||||
checkResponse(data);
|
|
||||||
}
|
|
||||||
setVerifying(false);
|
setVerifying(false);
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -75,8 +65,11 @@ const Login = ({ reloadUser }: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkResponse = (data: any) => {
|
const checkResponse = (
|
||||||
if (data.code === 200) {
|
{ code, data, message: _message }: any,
|
||||||
|
values?: any,
|
||||||
|
) => {
|
||||||
|
if (code === 200) {
|
||||||
const {
|
const {
|
||||||
token,
|
token,
|
||||||
lastip,
|
lastip,
|
||||||
@@ -84,7 +77,7 @@ const Login = ({ reloadUser }: any) => {
|
|||||||
lastlogon,
|
lastlogon,
|
||||||
retries = 0,
|
retries = 0,
|
||||||
platform,
|
platform,
|
||||||
} = data.data;
|
} = data;
|
||||||
localStorage.setItem(config.authKey, token);
|
localStorage.setItem(config.authKey, token);
|
||||||
notification.success({
|
notification.success({
|
||||||
message: '登录成功!',
|
message: '登录成功!',
|
||||||
@@ -103,13 +96,14 @@ const Login = ({ reloadUser }: any) => {
|
|||||||
});
|
});
|
||||||
reloadUser(true);
|
reloadUser(true);
|
||||||
history.push('/crontab');
|
history.push('/crontab');
|
||||||
} else if (data.code === 100) {
|
} else if (code === 410) {
|
||||||
message.warn(data.message);
|
setWaitTime(data);
|
||||||
} else if (data.code === 410) {
|
} else if (code === 420) {
|
||||||
message.error(data.message);
|
setLoginInfo({
|
||||||
setWaitTime(data.data);
|
username: values.username,
|
||||||
} else {
|
password: values.password,
|
||||||
message.error(data.message);
|
});
|
||||||
|
setTwoFactor(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +129,7 @@ const Login = ({ reloadUser }: any) => {
|
|||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src="http://qn.whyour.cn/logo.png"
|
src="https://qn.whyour.cn/logo.png"
|
||||||
/>
|
/>
|
||||||
<span className={styles.title}>
|
<span className={styles.title}>
|
||||||
{twoFactor ? '两步验证' : config.siteName}
|
{twoFactor ? '两步验证' : config.siteName}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Editor from '@monaco-editor/react';
|
|||||||
import SaveModal from './saveModal';
|
import SaveModal from './saveModal';
|
||||||
import SettingModal from './setting';
|
import SettingModal from './setting';
|
||||||
import { useTheme } from '@/utils/hooks';
|
import { useTheme } from '@/utils/hooks';
|
||||||
|
import { logEnded } from '@/utils';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const LangMap: any = {
|
const LangMap: any = {
|
||||||
@@ -57,6 +58,11 @@ const EditModal = ({
|
|||||||
if (node.key === selectedKey || !value) {
|
if (node.key === selectedKey || !value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.type === 'directory') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newMode = LangMap[value.slice(-3)] || '';
|
const newMode = LangMap[value.slice(-3)] || '';
|
||||||
setCNode(node);
|
setCNode(node);
|
||||||
setLanguage(newMode);
|
setLanguage(newMode);
|
||||||
@@ -67,8 +73,10 @@ const EditModal = ({
|
|||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setValue(data.data);
|
if (code === 200) {
|
||||||
|
setValue(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,8 +91,10 @@ const EditModal = ({
|
|||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setIsRunning(true);
|
if (code === 200) {
|
||||||
|
setIsRunning(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,8 +111,10 @@ const EditModal = ({
|
|||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setIsRunning(false);
|
if (code === 200) {
|
||||||
|
setIsRunning(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,7 +129,7 @@ const EditModal = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_message.includes('执行结束')) {
|
if (logEnded(_message)) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsRunning(false);
|
setIsRunning(false);
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -130,10 +142,13 @@ const EditModal = ({
|
|||||||
}, [socketMessage]);
|
}, [socketMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setLog('');
|
||||||
if (currentNode) {
|
if (currentNode) {
|
||||||
setCNode(currentNode);
|
setCNode(currentNode);
|
||||||
setValue(content as string);
|
setValue(content as string);
|
||||||
setSelectedKey(currentNode.key);
|
setSelectedKey(currentNode.key);
|
||||||
|
const newMode = LangMap[currentNode.title.slice(-3)] || '';
|
||||||
|
setLanguage(newMode);
|
||||||
}
|
}
|
||||||
}, [content, currentNode]);
|
}, [content, currentNode]);
|
||||||
|
|
||||||
@@ -144,6 +159,7 @@ const EditModal = ({
|
|||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
treeExpandAction="click"
|
||||||
style={{ marginRight: 8, width: 150 }}
|
style={{ marginRight: 8, width: 150 }}
|
||||||
value={selectedKey}
|
value={selectedKey}
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
@@ -214,8 +230,9 @@ const EditModal = ({
|
|||||||
width={'100%'}
|
width={'100%'}
|
||||||
headerStyle={{ padding: '11px 24px' }}
|
headerStyle={{ padding: '11px 24px' }}
|
||||||
onClose={cancel}
|
onClose={cancel}
|
||||||
visible={visible}
|
open={visible}
|
||||||
>
|
>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<SplitPane
|
<SplitPane
|
||||||
split="vertical"
|
split="vertical"
|
||||||
minSize={200}
|
minSize={200}
|
||||||
@@ -236,7 +253,14 @@ const EditModal = ({
|
|||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<pre style={{ height: '100%', whiteSpace: 'break-spaces' }}>{log}</pre>
|
<pre
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
padding: '0 15px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{log}
|
||||||
|
</pre>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
<SaveModal
|
<SaveModal
|
||||||
visible={saveModalVisible}
|
visible={saveModalVisible}
|
||||||
|
|||||||
@@ -32,32 +32,31 @@ const EditScriptNameModal = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [dirs, setDirs] = useState<any[]>([]);
|
const [dirs, setDirs] = useState<any[]>([]);
|
||||||
const [file, setFile] = useState<File>();
|
const [file, setFile] = useState<File>();
|
||||||
const [type, setType] = useState<'blank' | 'upload'>('blank');
|
const [type, setType] = useState<'blank' | 'upload' | 'directory'>('blank');
|
||||||
|
|
||||||
const handleOk = async (values: any) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
values.path = values.path || '';
|
const { path = '', filename: inputFilename, directory = '' } = values;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file as any);
|
formData.append('file', file as any);
|
||||||
formData.append('filename', values.filename);
|
formData.append('filename', inputFilename);
|
||||||
formData.append('path', values.path);
|
formData.append('path', path);
|
||||||
formData.append('content', '');
|
formData.append('content', '');
|
||||||
|
formData.append('directory', directory);
|
||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}scripts`, {
|
.post(`${config.apiPrefix}scripts`, {
|
||||||
data: formData,
|
data: formData,
|
||||||
})
|
})
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success('新建文件成功');
|
message.success(directory ? '新建文件夹成功' : '新建文件成功');
|
||||||
const key = values.path ? `${values.path}/` : '';
|
const key = path ? `${path}/` : '';
|
||||||
const filename = file ? file.name : values.filename;
|
const filename = file ? file.name : inputFilename;
|
||||||
handleCancel({
|
handleCancel({
|
||||||
filename,
|
filename,
|
||||||
path: values.path,
|
path,
|
||||||
key: `${key}${filename}`,
|
key: `${key}${filename}`,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
@@ -99,8 +98,8 @@ const EditScriptNameModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="新建脚本"
|
title="新建"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -126,18 +125,36 @@ const EditScriptNameModal = ({
|
|||||||
>
|
>
|
||||||
<Radio.Group onChange={typeChange}>
|
<Radio.Group onChange={typeChange}>
|
||||||
<Radio value="blank">空文件</Radio>
|
<Radio value="blank">空文件</Radio>
|
||||||
<Radio value="upload">本地上传</Radio>
|
<Radio value="upload">本地文件</Radio>
|
||||||
|
<Radio value="directory">文件夹</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{type === 'blank' && (
|
{type === 'blank' && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="filename"
|
name="filename"
|
||||||
label="文件名"
|
label="文件名"
|
||||||
rules={[{ required: true, message: '请输入文件名' }]}
|
rules={[
|
||||||
|
{ required: true, message: '请输入文件名' },
|
||||||
|
{
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.includes('/')
|
||||||
|
? Promise.reject(new Error('文件名不能包含斜杠'))
|
||||||
|
: Promise.resolve(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入文件名" />
|
<Input placeholder="请输入文件名" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
{type === 'directory' && (
|
||||||
|
<Form.Item
|
||||||
|
name="directory"
|
||||||
|
label="文件夹名"
|
||||||
|
rules={[{ required: true, message: '请输入文件夹名' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入文件夹名" />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
<Form.Item label="父目录" name="path">
|
<Form.Item label="父目录" name="path">
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
allowClear
|
allowClear
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: @component-background;
|
background-color: @component-background;
|
||||||
height: calc(100vh - 128px);
|
height: 100%;
|
||||||
height: calc(100vh - var(--vh-offset, 0px) - 128px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-109
@@ -33,38 +33,15 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import EditScriptNameModal from './editNameModal';
|
import EditScriptNameModal from './editNameModal';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { history } from 'umi';
|
import { history, useOutletContext, useLocation } from '@umijs/max';
|
||||||
|
import { parse } from 'query-string';
|
||||||
|
import { depthFirstSearch } from '@/utils';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||||
|
import uniq from 'lodash/uniq';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
function getFilterData(keyword: string, data: any) {
|
|
||||||
const expandedKeys: string[] = [];
|
|
||||||
if (keyword) {
|
|
||||||
const tree: any = [];
|
|
||||||
data.forEach((item: any) => {
|
|
||||||
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
|
||||||
tree.push(item);
|
|
||||||
} else {
|
|
||||||
const children: any[] = [];
|
|
||||||
(item.children || []).forEach((subItem: any) => {
|
|
||||||
if (subItem.title.toLocaleLowerCase().includes(keyword)) {
|
|
||||||
children.push(subItem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (children.length > 0) {
|
|
||||||
tree.push({
|
|
||||||
...item,
|
|
||||||
children,
|
|
||||||
});
|
|
||||||
expandedKeys.push(item.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { tree, expandedKeys };
|
|
||||||
}
|
|
||||||
return { tree: data, expandedKeys };
|
|
||||||
}
|
|
||||||
|
|
||||||
const LangMap: any = {
|
const LangMap: any = {
|
||||||
'.py': 'python',
|
'.py': 'python',
|
||||||
'.js': 'javascript',
|
'.js': 'javascript',
|
||||||
@@ -72,12 +49,12 @@ const LangMap: any = {
|
|||||||
'.ts': 'typescript',
|
'.ts': 'typescript',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
const Script = () => {
|
||||||
const [title, setTitle] = useState('请选择脚本文件');
|
const { headerStyle, isPhone, theme, socketMessage } =
|
||||||
|
useOutletContext<SharedContext>();
|
||||||
const [value, setValue] = useState('请选择脚本文件');
|
const [value, setValue] = useState('请选择脚本文件');
|
||||||
const [select, setSelect] = useState<any>();
|
const [select, setSelect] = useState<string>('');
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [filterData, setFilterData] = useState<any[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [mode, setMode] = useState('');
|
const [mode, setMode] = useState('');
|
||||||
const [height, setHeight] = useState<number>();
|
const [height, setHeight] = useState<number>();
|
||||||
@@ -94,24 +71,31 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts`)
|
.get(`${config.apiPrefix}scripts`)
|
||||||
.then((data) => {
|
.then(({ code, data }) => {
|
||||||
setData(data.data);
|
if (code === 200) {
|
||||||
setFilterData(data.data);
|
setData(data);
|
||||||
initGetScript();
|
initGetScript();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
.get(
|
||||||
.then((data) => {
|
`${config.apiPrefix}scripts/${encodeURIComponent(node.title)}?path=${
|
||||||
setValue(data.data);
|
node.parent || ''
|
||||||
|
}`,
|
||||||
|
)
|
||||||
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200) {
|
||||||
|
setValue(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const initGetScript = () => {
|
const initGetScript = () => {
|
||||||
const { p, s } = history.location.query as any;
|
const { p, s } = parse(history.location.search);
|
||||||
if (s) {
|
if (s) {
|
||||||
const vkey = `${p}/${s}`;
|
const vkey = `${p}/${s}`;
|
||||||
const obj = {
|
const obj = {
|
||||||
@@ -127,22 +111,24 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
|
setSelect(node.key);
|
||||||
|
setCurrentNode(node);
|
||||||
|
|
||||||
if (node.key === select || !value) {
|
if (node.key === select || !value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setValue('加载中...');
|
|
||||||
|
if (node.type === 'directory') {
|
||||||
|
setValue('请选择脚本文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newMode = value ? LangMap[value.slice(-3)] : '';
|
const newMode = value ? LangMap[value.slice(-3)] : '';
|
||||||
setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode);
|
setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode);
|
||||||
setSelect(node.key);
|
setValue('加载中...');
|
||||||
setTitle(node.key);
|
|
||||||
setCurrentNode(node);
|
|
||||||
getDetail(node);
|
getDetail(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onExpand = (expKeys: any) => {
|
|
||||||
setExpandedKeys(expKeys);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTreeSelect = useCallback(
|
const onTreeSelect = useCallback(
|
||||||
(keys: Key[], e: any) => {
|
(keys: Key[], e: any) => {
|
||||||
const content = editorRef.current
|
const content = editorRef.current
|
||||||
@@ -173,22 +159,30 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
debounceSearch(keyword);
|
debounceSearch(keyword);
|
||||||
},
|
},
|
||||||
[data, setFilterData],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debounceSearch = useCallback(
|
const debounceSearch = useCallback(
|
||||||
debounce((keyword) => {
|
debounce((keyword) => {
|
||||||
setSearchValue(keyword);
|
setSearchValue(keyword);
|
||||||
const { tree, expandedKeys } = getFilterData(
|
|
||||||
keyword.toLocaleLowerCase(),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
setExpandedKeys(expandedKeys);
|
|
||||||
setFilterData(tree);
|
|
||||||
}, 300),
|
}, 300),
|
||||||
[data, setFilterData],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
|
||||||
|
data,
|
||||||
|
searchValue,
|
||||||
|
{ treeNodeFilterProp: 'title' },
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
|
||||||
|
}, [searchExpandedKeys]);
|
||||||
|
|
||||||
|
const onExpand = (expKeys: any) => {
|
||||||
|
setExpandedKeys(expKeys);
|
||||||
|
};
|
||||||
|
|
||||||
const editFile = () => {
|
const editFile = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
@@ -226,13 +220,11 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (_data.code === 200) {
|
if (code === 200) {
|
||||||
message.success(`保存成功`);
|
message.success(`保存成功`);
|
||||||
setValue(content);
|
setValue(content);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
} else {
|
|
||||||
message.error(_data);
|
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
})
|
})
|
||||||
@@ -250,10 +242,11 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
title: `确认删除`,
|
title: `确认删除`,
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
确认删除文件
|
确认删除
|
||||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
{select}
|
{select}
|
||||||
</Text>{' '}
|
</Text>
|
||||||
|
文件{currentNode.type === 'directory' ? '夹及其子文件' : ''}
|
||||||
,删除后不可恢复
|
,删除后不可恢复
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@@ -263,24 +256,18 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
data: {
|
data: {
|
||||||
filename: currentNode.title,
|
filename: currentNode.title,
|
||||||
path: currentNode.parent || '',
|
path: currentNode.parent || '',
|
||||||
|
type: currentNode.type,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then(({ code }) => {
|
||||||
if (_data.code === 200) {
|
if (code === 200) {
|
||||||
message.success(`删除成功`);
|
message.success(`删除成功`);
|
||||||
let newData = [...data];
|
let newData = [...data];
|
||||||
if (currentNode.parent) {
|
if (currentNode.parent) {
|
||||||
const parentNodeIndex = newData.findIndex(
|
newData = depthFirstSearch(
|
||||||
(x) => x.key === currentNode.parent,
|
newData,
|
||||||
|
(c) => c.key === currentNode.key,
|
||||||
);
|
);
|
||||||
const parentNode = newData[parentNodeIndex];
|
|
||||||
const index = parentNode.children.findIndex(
|
|
||||||
(y) => y.key === currentNode.key,
|
|
||||||
);
|
|
||||||
if (index !== -1 && parentNodeIndex !== -1) {
|
|
||||||
parentNode.children.splice(index, 1);
|
|
||||||
newData.splice(parentNodeIndex, 1, { ...parentNode });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const index = newData.findIndex(
|
const index = newData.findIndex(
|
||||||
(x) => x.key === currentNode.key,
|
(x) => x.key === currentNode.key,
|
||||||
@@ -290,8 +277,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setData(newData);
|
setData(newData);
|
||||||
} else {
|
initState();
|
||||||
message.error(_data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -313,20 +299,17 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const newData = [...data];
|
let newData = [...data];
|
||||||
const _file = { title: filename, key, parent: path };
|
const _file = { title: filename, key, parent: path };
|
||||||
if (path) {
|
if (path) {
|
||||||
// TODO: 更新左侧树数据
|
newData = depthFirstSearch(newData, (c) => c.key === path, _file);
|
||||||
const parentNodeIndex = newData.findIndex((x) => x.key === path);
|
const keys = path.split('/');
|
||||||
if (parentNodeIndex !== -1) {
|
const sKeys: string[] = [];
|
||||||
const parentNode = newData[parentNodeIndex];
|
keys.reduce((p, c) => {
|
||||||
if (parentNode.children && parentNode.children.length > 0) {
|
sKeys.push(p);
|
||||||
parentNode.children.unshift(_file);
|
return `${p}/${c}`;
|
||||||
} else {
|
});
|
||||||
parentNode.children = [_file];
|
setExpandedKeys([...expandedKeys, ...sKeys, path]);
|
||||||
}
|
|
||||||
newData.splice(parentNodeIndex, 1, { ...parentNode });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
newData.unshift(_file);
|
newData.unshift(_file);
|
||||||
}
|
}
|
||||||
@@ -344,34 +327,35 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
filename: currentNode.title,
|
filename: currentNode.title,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then(({ code, data }) => {
|
||||||
const blob = new Blob([_data], { type: 'application/json' });
|
if (code === 200) {
|
||||||
const url = URL.createObjectURL(blob);
|
const blob = new Blob([data], { type: 'application/json' });
|
||||||
const a = document.createElement('a');
|
const url = URL.createObjectURL(blob);
|
||||||
a.href = url;
|
const a = document.createElement('a');
|
||||||
a.download = currentNode.title;
|
a.href = url;
|
||||||
document.documentElement.appendChild(a);
|
a.download = currentNode.title;
|
||||||
a.click();
|
document.documentElement.appendChild(a);
|
||||||
document.documentElement.removeChild(a);
|
a.click();
|
||||||
|
document.documentElement.removeChild(a);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const initState = () => {
|
||||||
const word = searchValue || '';
|
|
||||||
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
|
||||||
setFilterData(tree);
|
|
||||||
setSelect('');
|
setSelect('');
|
||||||
setCurrentNode(null);
|
setCurrentNode(null);
|
||||||
setTitle('请选择脚本文件');
|
|
||||||
setValue('请选择脚本文件');
|
setValue('请选择脚本文件');
|
||||||
}, [data]);
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getScripts();
|
getScripts();
|
||||||
if (treeDom && treeDom.current) {
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (treeDom.current) {
|
||||||
setHeight(treeDom.current.clientHeight);
|
setHeight(treeDom.current.clientHeight);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [treeDom.current, data]);
|
||||||
|
|
||||||
const action = (key: string | number) => {
|
const action = (key: string | number) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -440,19 +424,22 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper log-wrapper"
|
className="ql-container-wrapper log-wrapper"
|
||||||
title={title}
|
title={select}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
extra={
|
extra={
|
||||||
isPhone
|
isPhone
|
||||||
? [
|
? [
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
treeExpandAction="click"
|
||||||
className="log-select"
|
className="log-select"
|
||||||
value={select}
|
value={select}
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
placeholder="请选择脚本"
|
placeholder="请选择脚本"
|
||||||
fieldNames={{ value: 'key', label: 'title' }}
|
fieldNames={{ value: 'key' }}
|
||||||
|
treeNodeFilterProp="title"
|
||||||
showSearch
|
showSearch
|
||||||
|
allowClear
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
@@ -508,6 +495,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
>
|
>
|
||||||
<div className={`${styles['log-container']} log-container`}>
|
<div className={`${styles['log-container']} log-container`}>
|
||||||
{!isPhone && (
|
{!isPhone && (
|
||||||
|
/*// @ts-ignore*/
|
||||||
<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']}>
|
||||||
{data.length > 0 ? (
|
{data.length > 0 ? (
|
||||||
@@ -520,6 +508,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
></Input.Search>
|
></Input.Search>
|
||||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||||
<Tree
|
<Tree
|
||||||
|
expandAction="click"
|
||||||
className={styles['left-tree']}
|
className={styles['left-tree']}
|
||||||
treeData={filterData}
|
treeData={filterData}
|
||||||
showIcon={true}
|
showIcon={true}
|
||||||
@@ -556,7 +545,6 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
readOnly: !isEditing,
|
readOnly: !isEditing,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
folding: false,
|
|
||||||
glyphMargin: false,
|
glyphMargin: false,
|
||||||
}}
|
}}
|
||||||
onMount={(editor) => {
|
onMount={(editor) => {
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ const SaveModal = ({
|
|||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success('保存文件成功');
|
message.success('保存文件成功');
|
||||||
handleCancel(data);
|
handleCancel(data);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
@@ -41,7 +39,7 @@ const SaveModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="保存文件"
|
title="保存文件"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ const SettingModal = ({
|
|||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success('保存文件成功');
|
message.success('保存文件成功');
|
||||||
handleCancel(data);
|
handleCancel(data);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
@@ -41,7 +39,7 @@ const SettingModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="运行设置"
|
title="运行设置"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Typography, Input, Form, Button, message } from 'antd';
|
import { Typography, Input, Form, Button, message, Descriptions } from 'antd';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const { Link } = Typography;
|
const { Link } = Typography;
|
||||||
|
|
||||||
const About = () => {
|
enum TVersion {
|
||||||
|
'develop' = '开发版',
|
||||||
|
'master' = '正式版',
|
||||||
|
}
|
||||||
|
|
||||||
|
const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
style={{ width: 140, marginRight: 20 }}
|
style={{ width: 140, marginRight: 20 }}
|
||||||
src="http://qn.whyour.cn/logo.png"
|
src="https://qn.whyour.cn/logo.png"
|
||||||
/>
|
/>
|
||||||
<div className={styles.right}>
|
<div className={styles.right}>
|
||||||
<span className={styles.title}>青龙</span>
|
<span className={styles.title}>青龙</span>
|
||||||
@@ -19,6 +26,17 @@ const About = () => {
|
|||||||
task management panel that supports typescript, javaScript, python3,
|
task management panel that supports typescript, javaScript, python3,
|
||||||
and shell.)
|
and shell.)
|
||||||
</span>
|
</span>
|
||||||
|
<Descriptions>
|
||||||
|
<Descriptions.Item label="版本" span={3}>
|
||||||
|
{TVersion[systemInfo.branch]} v{systemInfo.version}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="更新时间" span={3}>
|
||||||
|
{dayjs(systemInfo.lastCommitTime).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="更新ID" span={3}>
|
||||||
|
{systemInfo.lastCommitId}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/whyour/qinglong"
|
href="https://github.com/whyour/qinglong"
|
||||||
|
|||||||
@@ -22,16 +22,19 @@ const AppModal = ({
|
|||||||
if (app) {
|
if (app) {
|
||||||
payload.id = app.id;
|
payload.id = app.id;
|
||||||
}
|
}
|
||||||
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
try {
|
||||||
data: payload,
|
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
||||||
});
|
data: payload,
|
||||||
if (code === 200) {
|
});
|
||||||
message.success(app ? '更新应用成功' : '新建应用成功');
|
|
||||||
} else {
|
if (code === 200) {
|
||||||
message.error(data);
|
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||||
|
handleCancel(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel(data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -41,7 +44,7 @@ const AppModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={app ? '编辑应用' : '新建应用'}
|
title={app ? '编辑应用' : '新建应用'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -64,7 +67,18 @@ const AppModal = ({
|
|||||||
name="form_app_modal"
|
name="form_app_modal"
|
||||||
initialValues={app}
|
initialValues={app}
|
||||||
>
|
>
|
||||||
<Form.Item name="name" label="名称">
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="名称"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: (_, value) =>
|
||||||
|
['system'].includes(value)
|
||||||
|
? Promise.reject(new Error('名称不能为保留关键字'))
|
||||||
|
: Promise.resolve(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Input placeholder="请输入应用名称" />
|
<Input placeholder="请输入应用名称" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="scopes" label="权限" rules={[{ required: true }]}>
|
<Form.Item name="scopes" label="权限" rules={[{ required: true }]}>
|
||||||
@@ -75,7 +89,11 @@ const AppModal = ({
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
{config.scopes.map((x) => {
|
{config.scopes.map((x) => {
|
||||||
return <Select.Option value={x.value}>{x.name}</Select.Option>;
|
return (
|
||||||
|
<Select.Option key={x.value} value={x.value}>
|
||||||
|
{x.name}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -17,17 +17,14 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
message.loading('检查更新中...', 0);
|
message.loading('检查更新中...', 0);
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}system/update-check`)
|
.put(`${config.apiPrefix}system/update-check`)
|
||||||
.then((_data: any) => {
|
.then(({ code, data }) => {
|
||||||
message.destroy();
|
message.destroy();
|
||||||
const { code, data } = _data;
|
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
if (data.hasNewVersion) {
|
if (data.hasNewVersion) {
|
||||||
showConfirmUpdateModal(data);
|
showConfirmUpdateModal(data);
|
||||||
} else {
|
} else {
|
||||||
showForceUpdateModal();
|
showForceUpdateModal();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -80,8 +77,6 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
content: (
|
content: (
|
||||||
<pre
|
<pre
|
||||||
style={{
|
style={{
|
||||||
wordBreak: 'break-all',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
paddingTop: 15,
|
paddingTop: 15,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
@@ -105,26 +100,23 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showUpdatingModal = () => {
|
const showUpdatingModal = () => {
|
||||||
|
setValue('');
|
||||||
modalRef.current = Modal.info({
|
modalRef.current = Modal.info({
|
||||||
width: 600,
|
width: 600,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
closable: false,
|
closable: false,
|
||||||
okButtonProps: { disabled: true },
|
okButtonProps: { disabled: true },
|
||||||
title: '更新日志',
|
title: '更新中...',
|
||||||
centered: true,
|
centered: true,
|
||||||
content: (
|
content: (
|
||||||
<div style={{ height: '60vh', overflowY: 'auto' }}>
|
<pre
|
||||||
<pre
|
style={{
|
||||||
style={{
|
fontSize: 12,
|
||||||
wordBreak: 'break-all',
|
fontWeight: 400,
|
||||||
whiteSpace: 'pre-wrap',
|
}}
|
||||||
fontSize: 12,
|
>
|
||||||
fontWeight: 400,
|
{value}
|
||||||
}}
|
</pre>
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -140,13 +132,16 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newMessage = `${value}${_message}`;
|
const newMessage = `${value}${_message}`;
|
||||||
|
const updateFailed = newMessage.includes('失败,请检查');
|
||||||
|
|
||||||
modalRef.current.update({
|
modalRef.current.update({
|
||||||
|
maskClosable: updateFailed,
|
||||||
|
closable: updateFailed,
|
||||||
|
okButtonProps: { disabled: !updateFailed },
|
||||||
content: (
|
content: (
|
||||||
<div style={{ height: '60vh', overflowY: 'auto' }}>
|
<>
|
||||||
<pre
|
<pre
|
||||||
style={{
|
style={{
|
||||||
wordBreak: 'break-all',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
}}
|
}}
|
||||||
@@ -154,9 +149,14 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
{newMessage}
|
{newMessage}
|
||||||
</pre>
|
</pre>
|
||||||
<div id="log-identifier" style={{ paddingBottom: 5 }}></div>
|
<div id="log-identifier" style={{ paddingBottom: 5 }}></div>
|
||||||
</div>
|
</>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (updateFailed && !value.includes('失败,请检查')) {
|
||||||
|
message.error('更新失败,请检查网络及日志或稍后再试');
|
||||||
|
}
|
||||||
|
|
||||||
setValue(newMessage);
|
setValue(newMessage);
|
||||||
|
|
||||||
document.getElementById('log-identifier') &&
|
document.getElementById('log-identifier') &&
|
||||||
|
|||||||
@@ -18,5 +18,12 @@
|
|||||||
.desc {
|
.desc {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.ant-descriptions-row > th,
|
||||||
|
.ant-descriptions-row > td {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-91
@@ -29,6 +29,8 @@ import LoginLog from './loginLog';
|
|||||||
import NotificationSetting from './notification';
|
import NotificationSetting from './notification';
|
||||||
import CheckUpdate from './checkUpdate';
|
import CheckUpdate from './checkUpdate';
|
||||||
import About from './about';
|
import About from './about';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const optionsWithDisabled = [
|
const optionsWithDisabled = [
|
||||||
@@ -37,14 +39,16 @@ const optionsWithDisabled = [
|
|||||||
{ label: '跟随系统', value: 'auto' },
|
{ label: '跟随系统', value: 'auto' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Setting = ({
|
const Setting = () => {
|
||||||
headerStyle,
|
const {
|
||||||
isPhone,
|
headerStyle,
|
||||||
user,
|
isPhone,
|
||||||
reloadUser,
|
user,
|
||||||
reloadTheme,
|
reloadUser,
|
||||||
socketMessage,
|
reloadTheme,
|
||||||
}: any) => {
|
socketMessage,
|
||||||
|
systemInfo,
|
||||||
|
} = useOutletContext<SharedContext>();
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
@@ -147,8 +151,10 @@ const Setting = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}apps`)
|
.get(`${config.apiPrefix}apps`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setDataSource(data.data);
|
if (code === 200) {
|
||||||
|
setDataSource(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -178,14 +184,12 @@ const Setting = ({
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}apps`, { data: [record.id] })
|
.delete(`${config.apiPrefix}apps`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
const result = [...dataSource];
|
const result = [...dataSource];
|
||||||
result.splice(index, 1);
|
result.splice(index, 1);
|
||||||
setDataSource(result);
|
setDataSource(result);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -212,12 +216,10 @@ 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(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
message.success('重置成功');
|
message.success('重置成功');
|
||||||
handleApp(data.data);
|
handleApp(data);
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -250,8 +252,10 @@ const Setting = ({
|
|||||||
const getLoginLog = () => {
|
const getLoginLog = () => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}user/login-log`)
|
.get(`${config.apiPrefix}user/login-log`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setLoginLogData(data.data);
|
if (code === 200) {
|
||||||
|
setLoginLogData(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -274,8 +278,10 @@ const Setting = ({
|
|||||||
const getNotification = () => {
|
const getNotification = () => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}user/notification`)
|
.get(`${config.apiPrefix}user/notification`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setNotificationInfo(data.data);
|
if (code === 200) {
|
||||||
|
setNotificationInfo(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -285,9 +291,9 @@ const Setting = ({
|
|||||||
const getLogRemoveFrequency = () => {
|
const getLogRemoveFrequency = () => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}system/log/remove`)
|
.get(`${config.apiPrefix}system/log/remove`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.data.info) {
|
if (code === 200 && data.info) {
|
||||||
const { frequency } = data.data.info;
|
const { frequency } = data.info;
|
||||||
setLogRemoveFrequency(frequency);
|
setLogRemoveFrequency(frequency);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -302,8 +308,10 @@ const Setting = ({
|
|||||||
.put(`${config.apiPrefix}system/log/remove`, {
|
.put(`${config.apiPrefix}system/log/remove`, {
|
||||||
data: { frequency: logRemoveFrequency },
|
data: { frequency: logRemoveFrequency },
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
message.success('更新成功');
|
if (code === 200) {
|
||||||
|
message.success('更新成功');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -333,70 +341,87 @@ const Setting = ({
|
|||||||
size="small"
|
size="small"
|
||||||
tabPosition="top"
|
tabPosition="top"
|
||||||
onChange={tabChange}
|
onChange={tabChange}
|
||||||
>
|
items={[
|
||||||
<Tabs.TabPane tab="安全设置" key="security">
|
{
|
||||||
<SecuritySettings user={user} userChange={reloadUser} />
|
key: 'security',
|
||||||
</Tabs.TabPane>
|
label: '安全设置',
|
||||||
<Tabs.TabPane tab="应用设置" key="app">
|
children: <SecuritySettings user={user} userChange={reloadUser} />,
|
||||||
<Table
|
},
|
||||||
columns={columns}
|
{
|
||||||
pagination={false}
|
key: 'app',
|
||||||
dataSource={dataSource}
|
label: '应用设置',
|
||||||
rowKey="id"
|
children: (
|
||||||
size="middle"
|
<Table
|
||||||
scroll={{ x: 768 }}
|
columns={columns}
|
||||||
loading={loading}
|
pagination={false}
|
||||||
/>
|
dataSource={dataSource}
|
||||||
</Tabs.TabPane>
|
rowKey="id"
|
||||||
<Tabs.TabPane tab="通知设置" key="notification">
|
size="middle"
|
||||||
<NotificationSetting data={notificationInfo} />
|
scroll={{ x: 768 }}
|
||||||
</Tabs.TabPane>
|
loading={loading}
|
||||||
<Tabs.TabPane tab="登录日志" key="login">
|
|
||||||
<LoginLog data={loginLogData} />
|
|
||||||
</Tabs.TabPane>
|
|
||||||
<Tabs.TabPane tab="其他设置" key="other">
|
|
||||||
<Form layout="vertical" form={form}>
|
|
||||||
<Form.Item
|
|
||||||
label="主题设置"
|
|
||||||
name="theme"
|
|
||||||
initialValue={defaultTheme}
|
|
||||||
>
|
|
||||||
<Radio.Group
|
|
||||||
options={optionsWithDisabled}
|
|
||||||
onChange={themeChange}
|
|
||||||
value={defaultTheme}
|
|
||||||
optionType="button"
|
|
||||||
buttonStyle="solid"
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
),
|
||||||
<Form.Item
|
},
|
||||||
label="日志删除频率"
|
{
|
||||||
name="frequency"
|
key: 'notification',
|
||||||
tooltip="每x天自动删除x天以前的日志"
|
label: '通知设置',
|
||||||
>
|
children: <NotificationSetting data={notificationInfo} />,
|
||||||
<Input.Group compact>
|
},
|
||||||
<InputNumber
|
{
|
||||||
addonBefore="每"
|
key: 'login',
|
||||||
addonAfter="天"
|
label: '登录日志',
|
||||||
style={{ width: 150 }}
|
children: <LoginLog data={loginLogData} />,
|
||||||
min={0}
|
},
|
||||||
value={logRemoveFrequency}
|
{
|
||||||
onChange={(value) => setLogRemoveFrequency(value)}
|
key: 'other',
|
||||||
/>
|
label: '其他设置',
|
||||||
<Button type="primary" onClick={updateRemoveLogFrequency}>
|
children: (
|
||||||
确认
|
<Form layout="vertical" form={form}>
|
||||||
</Button>
|
<Form.Item
|
||||||
</Input.Group>
|
label="主题设置"
|
||||||
</Form.Item>
|
name="theme"
|
||||||
<Form.Item label="检查更新" name="update">
|
initialValue={defaultTheme}
|
||||||
<CheckUpdate socketMessage={socketMessage} />
|
>
|
||||||
</Form.Item>
|
<Radio.Group
|
||||||
</Form>
|
options={optionsWithDisabled}
|
||||||
</Tabs.TabPane>
|
onChange={themeChange}
|
||||||
<Tabs.TabPane tab="关于" key="about">
|
value={defaultTheme}
|
||||||
<About />
|
optionType="button"
|
||||||
</Tabs.TabPane>
|
buttonStyle="solid"
|
||||||
</Tabs>
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="日志删除频率"
|
||||||
|
name="frequency"
|
||||||
|
tooltip="每x天自动删除x天以前的日志"
|
||||||
|
>
|
||||||
|
<Input.Group compact>
|
||||||
|
<InputNumber
|
||||||
|
addonBefore="每"
|
||||||
|
addonAfter="天"
|
||||||
|
style={{ width: 150 }}
|
||||||
|
min={0}
|
||||||
|
value={logRemoveFrequency}
|
||||||
|
onChange={(value) => setLogRemoveFrequency(value)}
|
||||||
|
/>
|
||||||
|
<Button type="primary" onClick={updateRemoveLogFrequency}>
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="检查更新" name="update">
|
||||||
|
<CheckUpdate socketMessage={socketMessage} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'about',
|
||||||
|
label: '关于',
|
||||||
|
children: <About systemInfo={systemInfo} />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
></Tabs>
|
||||||
<AppModal
|
<AppModal
|
||||||
visible={isModalVisible}
|
visible={isModalVisible}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
|
|||||||
@@ -12,20 +12,20 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const handleOk = (values: any) => {
|
const handleOk = (values: any) => {
|
||||||
if (values.type == 'closed') {
|
const { type } = values;
|
||||||
|
if (type == 'closed') {
|
||||||
values.type = '';
|
values.type = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}user/notification`, {
|
.put(`${config.apiPrefix}user/notification`, {
|
||||||
data: {
|
data: {
|
||||||
...values,
|
...values,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (_data && _data.code === 200) {
|
if (code === 200) {
|
||||||
message.success(values.type ? '通知发送成功' : '通知关闭成功');
|
message.success(values.type ? '通知发送成功' : '通知关闭成功');
|
||||||
} else {
|
|
||||||
message.error(_data.data);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -58,19 +58,35 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
>
|
>
|
||||||
<Select onChange={notificationModeChange}>
|
<Select onChange={notificationModeChange}>
|
||||||
{config.notificationModes.map((x) => (
|
{config.notificationModes.map((x) => (
|
||||||
<Option value={x.value}>{x.label}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.label}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{fields.map((x) => (
|
{fields.map((x) => (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
key={x.label}
|
||||||
label={x.label}
|
label={x.label}
|
||||||
name={x.label}
|
name={x.label}
|
||||||
extra={x.tip}
|
extra={x.tip}
|
||||||
rules={[{ required: x.required }]}
|
rules={[{ required: x.required }]}
|
||||||
style={{ maxWidth: 400 }}
|
style={{ maxWidth: 400 }}
|
||||||
>
|
>
|
||||||
<Input.TextArea autoSize={true} placeholder={`请输入${x.label}`} />
|
{x.items ? (
|
||||||
|
<Select placeholder={x.placeholder || `请选择${x.label}`}>
|
||||||
|
{x.items.map((y) => (
|
||||||
|
<Option key={y.value} value={y.value}>
|
||||||
|
{y.label || y.value}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Input.TextArea
|
||||||
|
autoSize={true}
|
||||||
|
placeholder={x.placeholder || `请输入${x.label}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { Typography, Input, Form, Button, message, Avatar, Upload } from 'antd';
|
import { Typography, Input, Form, Button, message, Avatar, Upload } from 'antd';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { history } from 'umi';
|
import { history } from '@umijs/max';
|
||||||
import QRCode from 'qrcode.react';
|
import QRCode from 'qrcode.react';
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
import { UploadOutlined, UserOutlined } from '@ant-design/icons';
|
import { UploadOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
@@ -27,9 +27,11 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
|||||||
password: values.password,
|
password: values.password,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
localStorage.removeItem(config.authKey);
|
if (code === 200) {
|
||||||
history.push('/login');
|
localStorage.removeItem(config.authKey);
|
||||||
|
history.push('/login');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -48,8 +50,8 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
|||||||
const deactiveTowFactor = () => {
|
const deactiveTowFactor = () => {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}user/two-factor/deactive`)
|
.put(`${config.apiPrefix}user/two-factor/deactive`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.data) {
|
if (code === 200 && data) {
|
||||||
setTwoFactorActivated(false);
|
setTwoFactorActivated(false);
|
||||||
userChange();
|
userChange();
|
||||||
}
|
}
|
||||||
@@ -63,14 +65,16 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}user/two-factor/active`, { data: { code } })
|
.put(`${config.apiPrefix}user/two-factor/active`, { data: { code } })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.data) {
|
if (code === 200) {
|
||||||
message.success('激活成功');
|
if (data) {
|
||||||
setTwoFactoring(false);
|
message.success('激活成功');
|
||||||
setTwoFactorActivated(true);
|
setTwoFactoring(false);
|
||||||
userChange();
|
setTwoFactorActivated(true);
|
||||||
} else {
|
userChange();
|
||||||
message.success('验证失败');
|
} else {
|
||||||
|
message.success('验证失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -82,8 +86,10 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
|||||||
const getTwoFactorInfo = () => {
|
const getTwoFactorInfo = () => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}user/two-factor/init`)
|
.get(`${config.apiPrefix}user/two-factor/init`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setTwoFactorInfo(data.data);
|
if (code === 200) {
|
||||||
|
setTwoFactorInfo(data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
message,
|
message,
|
||||||
@@ -29,10 +29,11 @@ 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 SubscriptionModal from './modal';
|
import SubscriptionModal from './modal';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import { history } from 'umi';
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import SubscriptionLogModal from './logModal';
|
import SubscriptionLogModal from './logModal';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -57,7 +58,10 @@ export enum SubscriptionType {
|
|||||||
'file' = '单文件',
|
'file' = '单文件',
|
||||||
}
|
}
|
||||||
|
|
||||||
const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
const Subscription = () => {
|
||||||
|
const { headerStyle, isPhone, socketMessage } =
|
||||||
|
useOutletContext<SharedContext>();
|
||||||
|
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
@@ -239,10 +243,11 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(20);
|
const [pageSize, setPageSize] = useState(20);
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||||
const [logSubscription, setLogSubscription] = useState<any>();
|
const [logSubscription, setLogSubscription] = useState<any>();
|
||||||
|
const tableRef = useRef<any>();
|
||||||
|
const tableScrollHeight = useTableScrollHeight(tableRef)
|
||||||
|
|
||||||
const runSubscription = (record: any, index: number) => {
|
const runSubscription = (record: any, index: number) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
@@ -259,8 +264,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}subscriptions/run`, { data: [record.id] })
|
.put(`${config.apiPrefix}subscriptions/run`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
if (i !== -1) {
|
if (i !== -1) {
|
||||||
@@ -270,8 +275,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -296,8 +299,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}subscriptions/stop`, { data: [record.id] })
|
.put(`${config.apiPrefix}subscriptions/stop`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
if (i !== -1) {
|
if (i !== -1) {
|
||||||
@@ -308,8 +311,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -323,9 +324,11 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}subscriptions?searchValue=${searchText}`)
|
.get(`${config.apiPrefix}subscriptions?searchValue=${searchText}`)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
setValue(data.data);
|
if (code === 200) {
|
||||||
setCurrentPage(1);
|
setValue(data);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -355,8 +358,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}subscriptions`, { data: [record.id] })
|
.delete(`${config.apiPrefix}subscriptions`, { data: [record.id] })
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (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);
|
||||||
@@ -364,8 +367,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
result.splice(i, 1);
|
result.splice(i, 1);
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -398,8 +399,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
data: [record.id],
|
data: [record.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (data.code === 200) {
|
if (code === 200) {
|
||||||
const newStatus = record.is_disabled === 1 ? 0 : 1;
|
const newStatus = record.is_disabled === 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);
|
||||||
@@ -410,8 +411,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
});
|
});
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -541,9 +540,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
|
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
|
||||||
setTimeout(() => {
|
|
||||||
setTableScrollHeight(getTableScroll());
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -570,6 +566,7 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
|
ref={tableRef}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
|
import { logEnded } from '@/utils';
|
||||||
|
|
||||||
const SubscriptionLogModal = ({
|
const SubscriptionLogModal = ({
|
||||||
subscription,
|
subscription,
|
||||||
@@ -36,14 +37,15 @@ const SubscriptionLogModal = ({
|
|||||||
? logUrl
|
? logUrl
|
||||||
: `${config.apiPrefix}subscriptions/${subscription.id}/log`,
|
: `${config.apiPrefix}subscriptions/${subscription.id}/log`,
|
||||||
)
|
)
|
||||||
.then((data: any) => {
|
.then(({ code, data }) => {
|
||||||
if (
|
if (
|
||||||
|
code === 200 &&
|
||||||
localStorage.getItem('logSubscription') === String(subscription.id)
|
localStorage.getItem('logSubscription') === String(subscription.id)
|
||||||
) {
|
) {
|
||||||
const log = data.data as string;
|
const log = data as string;
|
||||||
setValue(log || '暂无日志');
|
setValue(log || '暂无日志');
|
||||||
setExecuting(log && !log.includes('执行结束'));
|
setExecuting(log && !logEnded(log));
|
||||||
if (log && !log.includes('执行结束')) {
|
if (log && !logEnded(log)) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getCronLog();
|
getCronLog();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -93,7 +95,7 @@ const SubscriptionLogModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
bodyStyle={{
|
bodyStyle={{
|
||||||
|
|||||||
@@ -39,11 +39,9 @@ const SubscriptionModal = ({
|
|||||||
);
|
);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
|
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
|
||||||
} else {
|
handleCancel(data);
|
||||||
message.error(data);
|
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -190,8 +188,8 @@ const SubscriptionModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onPaste = useCallback((e: any) => {
|
const onPaste = useCallback((e: any) => {
|
||||||
const text = e.clipboardData.getData('text');
|
const text = e.clipboardData.getData('text') as string;
|
||||||
if (!subscription && text.includes('ql ')) {
|
if (text.startsWith('ql ')) {
|
||||||
const [
|
const [
|
||||||
,
|
,
|
||||||
type,
|
type,
|
||||||
@@ -225,6 +223,13 @@ const SubscriptionModal = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onNamePaste = useCallback((e) => {
|
||||||
|
const text = e.clipboardData.getData('text') as string;
|
||||||
|
if (text.startsWith('ql ')) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
window.addEventListener('paste', onPaste);
|
window.addEventListener('paste', onPaste);
|
||||||
@@ -243,36 +248,10 @@ const SubscriptionModal = ({
|
|||||||
}
|
}
|
||||||
}, [subscription, visible]);
|
}, [subscription, visible]);
|
||||||
|
|
||||||
const isFirefox = navigator.userAgent.includes('Firefox');
|
|
||||||
const isSafari =
|
|
||||||
navigator.userAgent.includes('Safari') &&
|
|
||||||
!navigator.userAgent.includes('Chrome');
|
|
||||||
const isQQBrowser = navigator.userAgent.includes('QQBrowser');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={subscription ? '编辑订阅' : '新建订阅'}
|
||||||
subscription ? (
|
open={visible}
|
||||||
'编辑订阅'
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
新建订阅
|
|
||||||
<small
|
|
||||||
style={{
|
|
||||||
color: '#999',
|
|
||||||
fontWeight: 400,
|
|
||||||
fontSize: isFirefox ? 9 : 12,
|
|
||||||
marginLeft: 2,
|
|
||||||
zoom: isSafari ? 0.66 : 0.8,
|
|
||||||
letterSpacing: isQQBrowser ? -2 : 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
支持repo/raw命令,粘贴导入
|
|
||||||
</small>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
visible={visible}
|
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -291,7 +270,10 @@ const SubscriptionModal = ({
|
|||||||
>
|
>
|
||||||
<Form form={form} name="form_in_modal" layout="vertical">
|
<Form form={form} name="form_in_modal" layout="vertical">
|
||||||
<Form.Item name="name" label="名称">
|
<Form.Item name="name" label="名称">
|
||||||
<Input placeholder="请输入订阅名" />
|
<Input
|
||||||
|
placeholder="支持拷贝ql repo/raw命令,粘贴导入"
|
||||||
|
onPaste={onNamePaste}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="type"
|
name="type"
|
||||||
|
|||||||
+70
-4
@@ -56,7 +56,7 @@ export default {
|
|||||||
value: 'scripts',
|
value: 'scripts',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '任务日志',
|
name: '日志管理',
|
||||||
value: 'logs',
|
value: 'logs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -74,7 +74,7 @@ export default {
|
|||||||
subscriptions: '订阅管理',
|
subscriptions: '订阅管理',
|
||||||
configs: '配置文件',
|
configs: '配置文件',
|
||||||
scripts: '脚本管理',
|
scripts: '脚本管理',
|
||||||
logs: '任务日志',
|
logs: '日志管理',
|
||||||
dependencies: '依赖管理',
|
dependencies: '依赖管理',
|
||||||
system: '系统信息',
|
system: '系统信息',
|
||||||
},
|
},
|
||||||
@@ -88,10 +88,13 @@ export default {
|
|||||||
{ value: 'dingtalkBot', label: '钉钉机器人' },
|
{ value: 'dingtalkBot', label: '钉钉机器人' },
|
||||||
{ value: 'weWorkBot', label: '企业微信机器人' },
|
{ value: 'weWorkBot', label: '企业微信机器人' },
|
||||||
{ value: 'weWorkApp', label: '企业微信应用' },
|
{ value: 'weWorkApp', label: '企业微信应用' },
|
||||||
|
{ value: 'aibotk', label: '智能微秘书' },
|
||||||
{ value: 'iGot', label: 'IGot' },
|
{ value: 'iGot', label: 'IGot' },
|
||||||
{ value: 'pushPlus', label: 'PushPlus' },
|
{ value: 'pushPlus', label: 'PushPlus' },
|
||||||
{ value: 'chat', label: '群辉chat' },
|
{ value: 'chat', label: '群辉chat' },
|
||||||
{ value: 'email', label: '邮箱' },
|
{ value: 'email', label: '邮箱' },
|
||||||
|
{ value: 'lark', label: '飞书机器人' },
|
||||||
|
{ value: 'webhook', label: '自定义通知' },
|
||||||
{ value: 'closed', label: '已关闭' },
|
{ value: 'closed', label: '已关闭' },
|
||||||
],
|
],
|
||||||
notificationModeMap: {
|
notificationModeMap: {
|
||||||
@@ -195,6 +198,28 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
aibotk: [
|
||||||
|
{
|
||||||
|
label: 'aibotkKey',
|
||||||
|
tip: '密钥key,智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'aibotkType',
|
||||||
|
tip: '发送的目标,群组或者好友',
|
||||||
|
required: true,
|
||||||
|
placeholder: '请输入要发送的目标',
|
||||||
|
items: [
|
||||||
|
{ value: 'room', label: '群聊' },
|
||||||
|
{ value: 'contact', label: '好友' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'aibotkName',
|
||||||
|
tip: '要发送的用户昵称或群名,如果目标是群,需要填群名,如果目标是好友,需要填好友昵称',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
iGot: [
|
iGot: [
|
||||||
{
|
{
|
||||||
label: 'iGotPushKey',
|
label: 'iGotPushKey',
|
||||||
@@ -205,7 +230,7 @@ export default {
|
|||||||
pushPlus: [
|
pushPlus: [
|
||||||
{
|
{
|
||||||
label: 'pushPlusToken',
|
label: 'pushPlusToken',
|
||||||
tip: '微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送',
|
tip: '微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -213,6 +238,13 @@ export default {
|
|||||||
tip: '一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)',
|
tip: '一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
lark: [
|
||||||
|
{
|
||||||
|
label: 'larkKey',
|
||||||
|
tip: '飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
email: [
|
email: [
|
||||||
{
|
{
|
||||||
label: 'emailService',
|
label: 'emailService',
|
||||||
@@ -222,6 +254,40 @@ export default {
|
|||||||
{ label: 'emailUser', tip: '邮箱地址', required: true },
|
{ label: 'emailUser', tip: '邮箱地址', required: true },
|
||||||
{ label: 'emailPass', tip: '邮箱SMTP授权码', required: true },
|
{ label: 'emailPass', tip: '邮箱SMTP授权码', required: true },
|
||||||
],
|
],
|
||||||
|
webhook: [
|
||||||
|
{
|
||||||
|
label: 'webhookMethod',
|
||||||
|
tip: '请求方法',
|
||||||
|
required: true,
|
||||||
|
items: [{ value: 'GET' }, { value: 'POST' }, { value: 'PUT' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'webhookContentType',
|
||||||
|
tip: '请求头Content-Type',
|
||||||
|
required: true,
|
||||||
|
items: [
|
||||||
|
{ value: 'application/json' },
|
||||||
|
{ value: 'multipart/form-data' },
|
||||||
|
{ value: 'application/x-www-form-urlencoded' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'webhookUrl',
|
||||||
|
tip: '请求链接以http或者https开头。url或者body中必须包含$title,$content可选,对应api内容的位置',
|
||||||
|
required: true,
|
||||||
|
placeholder: 'https://xxx.cn/api?content=$title\n',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'webhookHeaders',
|
||||||
|
tip: '请求头格式Custom-Header1: Header1,多个换行分割',
|
||||||
|
placeholder: 'Custom-Header1: Header1\nCustom-Header2: Header2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'webhookBody',
|
||||||
|
tip: '请求体格式key1: value1,多个换行分割。url或者body中必须包含$title,$content可选,对应api内容的位置',
|
||||||
|
placeholder: 'key1: $title\nkey2: $content',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
documentTitleMap: {
|
documentTitleMap: {
|
||||||
'/login': '登录',
|
'/login': '登录',
|
||||||
@@ -232,7 +298,7 @@ export default {
|
|||||||
'/config': '配置文件',
|
'/config': '配置文件',
|
||||||
'/script': '脚本管理',
|
'/script': '脚本管理',
|
||||||
'/diff': '对比工具',
|
'/diff': '对比工具',
|
||||||
'/log': '任务日志',
|
'/log': '日志管理',
|
||||||
'/setting': '系统设置',
|
'/setting': '系统设置',
|
||||||
'/error': '错误日志',
|
'/error': '错误日志',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const LOG_END_SYMBOL = '\n ';
|
||||||
+18
-2
@@ -1,7 +1,7 @@
|
|||||||
import { extend } from 'umi-request';
|
import { extend } from 'umi-request';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import { history } from 'umi';
|
import { history } from '@umijs/max';
|
||||||
|
|
||||||
message.config({
|
message.config({
|
||||||
duration: 1.5,
|
duration: 1.5,
|
||||||
@@ -54,7 +54,23 @@ _request.interceptors.request.use((url, options) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
_request.interceptors.response.use(async (response) => {
|
_request.interceptors.response.use(async (response) => {
|
||||||
const res = await response.clone();
|
const responseStatus = response.status;
|
||||||
|
if ([502, 504].includes(responseStatus)) {
|
||||||
|
message.error('服务异常,请稍后刷新!');
|
||||||
|
history.push('/error');
|
||||||
|
} else if (responseStatus === 401) {
|
||||||
|
if (history.location.pathname !== '/login') {
|
||||||
|
localStorage.removeItem(config.authKey);
|
||||||
|
history.push('/login');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const res = await response.clone().json();
|
||||||
|
if (res.code !== 200) {
|
||||||
|
const msg = res.message || res.data;
|
||||||
|
msg && message.error(msg);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+44
-9
@@ -1,3 +1,5 @@
|
|||||||
|
import { LOG_END_SYMBOL } from './const';
|
||||||
|
|
||||||
export default function browserType() {
|
export default function browserType() {
|
||||||
// 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
|
// 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
|
||||||
const ua = navigator.userAgent.toLowerCase();
|
const ua = navigator.userAgent.toLowerCase();
|
||||||
@@ -178,19 +180,15 @@ export default function browserType() {
|
|||||||
*/
|
*/
|
||||||
export function getTableScroll({
|
export function getTableScroll({
|
||||||
extraHeight,
|
extraHeight,
|
||||||
id,
|
target,
|
||||||
}: { extraHeight?: number; id?: string } = {}) {
|
}: { extraHeight?: number; target?: HTMLElement } = {}) {
|
||||||
if (typeof extraHeight == 'undefined') {
|
if (typeof extraHeight == 'undefined') {
|
||||||
// 47 + 40 + 12
|
// 47 + 40 + 12
|
||||||
extraHeight = 99;
|
extraHeight = 99;
|
||||||
}
|
}
|
||||||
let tHeader = null;
|
let tHeader = null;
|
||||||
if (id) {
|
if (target) {
|
||||||
tHeader = document.getElementById(id)
|
tHeader = target;
|
||||||
? document
|
|
||||||
.getElementById(id)!
|
|
||||||
.getElementsByClassName('ant-table-thead')[0]
|
|
||||||
: null;
|
|
||||||
} else {
|
} else {
|
||||||
tHeader = document.querySelector('.ant-table-wrapper');
|
tHeader = document.querySelector('.ant-table-wrapper');
|
||||||
}
|
}
|
||||||
@@ -200,7 +198,7 @@ export function getTableScroll({
|
|||||||
if (tHeader) {
|
if (tHeader) {
|
||||||
mainTop = tHeader.getBoundingClientRect().top;
|
mainTop = tHeader.getBoundingClientRect().top;
|
||||||
}
|
}
|
||||||
|
|
||||||
//窗体高度-表格内容顶部的高度-表格内容底部的高度
|
//窗体高度-表格内容顶部的高度-表格内容底部的高度
|
||||||
let height = document.body.clientHeight - mainTop - extraHeight;
|
let height = document.body.clientHeight - mainTop - extraHeight;
|
||||||
return height;
|
return height;
|
||||||
@@ -241,3 +239,40 @@ export function exportJson(name: string, data: string) {
|
|||||||
createA.download = name;
|
createA.download = name;
|
||||||
automaticClick(createA);
|
automaticClick(createA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function depthFirstSearch<
|
||||||
|
T extends Record<string, any> & { children?: T[] },
|
||||||
|
>(children: T[], condition: (column: T) => boolean, item?: T) {
|
||||||
|
const c = [...children];
|
||||||
|
const keys = [];
|
||||||
|
|
||||||
|
(function find(cls: T[] | undefined) {
|
||||||
|
if (!cls) return;
|
||||||
|
for (let i = 0; i < cls?.length; i++) {
|
||||||
|
if (condition(cls[i])) {
|
||||||
|
if (!item) {
|
||||||
|
cls.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cls[i].children) {
|
||||||
|
cls[i].children!.unshift(item);
|
||||||
|
} else {
|
||||||
|
cls[i].children = [item];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cls[i].children) {
|
||||||
|
keys.push(cls[i].key);
|
||||||
|
find(cls[i].children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(c);
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logEnded(log: string): boolean {
|
||||||
|
const endTips = [LOG_END_SYMBOL, '执行结束'];
|
||||||
|
return endTips.some((x) => log.includes(x));
|
||||||
|
}
|
||||||
|
|||||||
+6
-5
@@ -1,6 +1,7 @@
|
|||||||
export const version = '2.14.3';
|
export const version = '2.14.11';
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/330';
|
export const changeLogLink = 'https://t.me/jiao_long/338';
|
||||||
export const changeLog = `2.14.8 版本说明
|
export const changeLog = `2.14.11 版本说明
|
||||||
1. 修复安装依赖可能出现的 ERR_PNPM_REGISTRIES_MISMATCH
|
1. 修复列表表格滚动
|
||||||
2. 今人不见古时月,今月曾经照古人 -> 中秋快乐
|
2. 关于增加版本详细信息
|
||||||
|
3. 修改pre样式
|
||||||
`;
|
`;
|
||||||
|
|||||||
+4
-1
@@ -1,12 +1,15 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"lib": ["es2017", "esnext.asynciterable", "DOM"],
|
"lib": ["es2021", "esnext.asynciterable", "DOM"],
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types",
|
"./node_modules/@types",
|
||||||
"./src/types",
|
"./src/types",
|
||||||
"./node_modules/celebrate/lib/index.d.ts"
|
"./node_modules/celebrate/lib/index.d.ts"
|
||||||
],
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./back/*"],
|
||||||
|
},
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user