mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 03:10:48 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
+2
-2
@@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<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>
|
||||
</p>
|
||||
|
||||
@@ -183,7 +183,7 @@ task <file_path> desi <env_name> <account_number>
|
||||
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
||||
- [Ant Design](https://ant.design)
|
||||
- [Ant Design Pro](https://pro.ant.design/)
|
||||
- [Umijs3.0](https://umijs.org)
|
||||
- [Umijs](https://umijs.org)
|
||||
- [darkreader](https://github.com/darkreader/darkreader)
|
||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<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>
|
||||
</p>
|
||||
|
||||
@@ -183,7 +183,7 @@ task <file_path> desi <env_name> <account_number>
|
||||
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
||||
- [Ant Design](https://ant.design)
|
||||
- [Ant Design Pro](https://pro.ant.design/)
|
||||
- [Umijs3.0](https://umijs.org)
|
||||
- [Umijs](https://umijs.org)
|
||||
- [darkreader](https://github.com/darkreader/darkreader)
|
||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||
|
||||
|
||||
+32
-1
@@ -3,8 +3,9 @@ import { Container } from 'typedi';
|
||||
import { Logger } from 'winston';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import { getFileContentByName, readDirs } from '../config/util';
|
||||
import { emptyDir, getFileContentByName, readDirs } from '../config/util';
|
||||
import { join } from 'path';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
const route = Router();
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
+8
-1
@@ -4,6 +4,7 @@ import {
|
||||
readDirs,
|
||||
getLastModifyFilePath,
|
||||
readDir,
|
||||
emptyDir,
|
||||
} from '../config/util';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
@@ -169,17 +170,23 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().allow(''),
|
||||
type: Joi.string().optional()
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
let { filename, path } = req.body as {
|
||||
let { filename, path, type } = req.body as {
|
||||
filename: string;
|
||||
path: string;
|
||||
type: string;
|
||||
};
|
||||
const filePath = join(config.scriptPath, path, filename);
|
||||
if (type === 'directory') {
|
||||
emptyDir(filePath);
|
||||
} else {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ async function startServer() {
|
||||
|
||||
const server = app
|
||||
.listen(config.port, () => {
|
||||
Logger.debug(`✌️ Back server launched on port ${config.port}`);
|
||||
Logger.debug(`✌️ 后端服务启动成功!`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
@@ -11,7 +11,7 @@ if (!process.env.QL_DIR) {
|
||||
if (qlHomePath.endsWith('/static/')) {
|
||||
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()}`;
|
||||
|
||||
+29
-7
@@ -302,7 +302,6 @@ export function readDirs(
|
||||
title: file,
|
||||
key,
|
||||
type: 'directory',
|
||||
disabled: true,
|
||||
parent: relativePath,
|
||||
children: readDirs(subPath, baseDir).sort(
|
||||
(a: any, b: any) =>
|
||||
@@ -313,6 +312,7 @@ export function readDirs(
|
||||
return {
|
||||
title: file,
|
||||
type: 'file',
|
||||
isLeaf: true,
|
||||
key,
|
||||
parent: relativePath,
|
||||
};
|
||||
@@ -345,6 +345,20 @@ export function readDir(
|
||||
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> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
@@ -365,7 +379,8 @@ export function parseHeaders(headers: string) {
|
||||
let val;
|
||||
let i;
|
||||
|
||||
headers && headers.split('\n').forEach(function parser(line) {
|
||||
headers &&
|
||||
headers.split('\n').forEach(function parser(line) {
|
||||
i = line.indexOf(':');
|
||||
key = line.substring(0, i).trim().toLowerCase();
|
||||
val = line.substring(i + 1).trim();
|
||||
@@ -378,9 +393,15 @@ export function parseHeaders(headers: string) {
|
||||
});
|
||||
|
||||
return parsed;
|
||||
};
|
||||
}
|
||||
|
||||
export function parseBody(body: string, contentType: 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded') {
|
||||
export function parseBody(
|
||||
body: string,
|
||||
contentType:
|
||||
| 'application/json'
|
||||
| 'multipart/form-data'
|
||||
| 'application/x-www-form-urlencoded',
|
||||
) {
|
||||
if (!body) return '';
|
||||
|
||||
const parsed: any = {};
|
||||
@@ -388,7 +409,8 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
|
||||
let val;
|
||||
let i;
|
||||
|
||||
body && body.split('\n').forEach(function parser(line) {
|
||||
body &&
|
||||
body.split('\n').forEach(function parser(line) {
|
||||
i = line.indexOf(':');
|
||||
key = line.substring(0, i).trim().toLowerCase();
|
||||
val = line.substring(i + 1).trim();
|
||||
@@ -403,7 +425,7 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
|
||||
switch (contentType) {
|
||||
case 'multipart/form-data':
|
||||
return Object.keys(parsed).reduce((p, c) => {
|
||||
p.append(c, parsed[c])
|
||||
p.append(c, parsed[c]);
|
||||
return p;
|
||||
}, new FormData());
|
||||
case 'application/x-www-form-urlencoded':
|
||||
@@ -413,4 +435,4 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
|
||||
}
|
||||
|
||||
return parsed;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,13 +18,12 @@ export default async () => {
|
||||
tokenCommand = `node ${tokenFile}`;
|
||||
}
|
||||
const cron = {
|
||||
id: 'token',
|
||||
id: NaN,
|
||||
name: '生成token',
|
||||
command: tokenCommand,
|
||||
};
|
||||
scheduleService.createIntervalTask(cron as any, {
|
||||
scheduleService.createIntervalTask(cron, {
|
||||
days: 28,
|
||||
runImmediately: true,
|
||||
});
|
||||
|
||||
// 运行删除日志任务
|
||||
@@ -37,7 +36,6 @@ export default async () => {
|
||||
};
|
||||
scheduleService.createIntervalTask(cron, {
|
||||
days: data.info.frequency,
|
||||
runImmediately: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,23 @@ import { Application } from 'express';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import * as Tracing from '@sentry/tracing';
|
||||
import Logger from './logger';
|
||||
import config from '../config';
|
||||
import fs from 'fs';
|
||||
|
||||
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({
|
||||
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
||||
integrations: [
|
||||
new Sentry.Integrations.Http({ tracing: true }),
|
||||
new Tracing.Integrations.Express({ app: expressApp }),
|
||||
],
|
||||
|
||||
tracesSampleRate: 1.0,
|
||||
tracesSampleRate: 0.1,
|
||||
release: currentVersion,
|
||||
});
|
||||
|
||||
expressApp.use(Sentry.Handlers.requestHandler());
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ app
|
||||
await require('./loaders/sentry').default({ expressApp: app });
|
||||
await require('./loaders/db').default();
|
||||
|
||||
Logger.debug(`✌️ Back server launched on port ${config.publicPort}`);
|
||||
Logger.debug(`✌️ 公共服务启动成功!`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
+1
-5
@@ -44,11 +44,7 @@ app
|
||||
await require('./loaders/db').default();
|
||||
|
||||
await run();
|
||||
Logger.info(`
|
||||
################################################
|
||||
🛡️ Schedule listening on port: ${config.cronPort} 🛡️
|
||||
################################################
|
||||
`);
|
||||
Logger.debug('定时任务服务启动成功!');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
@@ -70,9 +70,8 @@ export default class SystemService {
|
||||
};
|
||||
await this.scheduleService.cancelIntervalTask(cron);
|
||||
if (frequency > 0) {
|
||||
await this.scheduleService.createIntervalTask(cron, {
|
||||
this.scheduleService.createIntervalTask(cron, {
|
||||
days: frequency,
|
||||
runImmediately: true,
|
||||
});
|
||||
}
|
||||
return { code: 200, data: { ...cron } };
|
||||
|
||||
+18
-13
@@ -65,15 +65,13 @@ export default class UserService {
|
||||
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 {
|
||||
code: 410,
|
||||
message: `失败次数过多,请${Math.round(
|
||||
(Math.pow(3, retries) * 1000 - Date.now() + lastlogon) / 1000,
|
||||
)}秒后重试`,
|
||||
data: Math.round(
|
||||
(Math.pow(3, retries) * 1000 - Date.now() + lastlogon) / 1000,
|
||||
),
|
||||
message: `失败次数过多,请${waitTime}秒后重试`,
|
||||
data: waitTime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,12 +83,12 @@ export default class UserService {
|
||||
});
|
||||
return {
|
||||
code: 420,
|
||||
message: '请输入两步验证token',
|
||||
message: '',
|
||||
};
|
||||
}
|
||||
|
||||
const data = createRandomString(50, 100);
|
||||
const expiration = twoFactorActivated ? 30 : 3;
|
||||
const expiration = twoFactorActivated ? 60 : 20;
|
||||
let token = jwt.sign({ data }, config.secret as any, {
|
||||
expiresIn: 60 * 60 * 24 * expiration,
|
||||
algorithm: 'HS384',
|
||||
@@ -111,8 +109,7 @@ export default class UserService {
|
||||
});
|
||||
await this.notificationService.notify(
|
||||
'登录通知',
|
||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||
req.platform
|
||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${req.platform
|
||||
}端 登录成功,ip地址 ${ip}`,
|
||||
);
|
||||
await this.getLoginLog();
|
||||
@@ -140,8 +137,7 @@ export default class UserService {
|
||||
});
|
||||
await this.notificationService.notify(
|
||||
'登录通知',
|
||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||
req.platform
|
||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${req.platform
|
||||
}端 登录失败,ip地址 ${ip}`,
|
||||
);
|
||||
await this.getLoginLog();
|
||||
@@ -155,8 +151,17 @@ export default class UserService {
|
||||
status: LoginStatus.fail,
|
||||
},
|
||||
});
|
||||
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 {
|
||||
return this.initAuthInfo();
|
||||
}
|
||||
|
||||
@@ -14,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
|
||||
pm2 l &>/dev/null
|
||||
|
||||
if [[ $PipMirror ]]; then
|
||||
pip3 config set global.index-url $PipMirror
|
||||
fi
|
||||
if [[ $NpmMirror ]]; then
|
||||
npm config set registry $NpmMirror
|
||||
fi
|
||||
patch_version &>/dev/null
|
||||
echo
|
||||
|
||||
echo -e "======================2. 安装依赖========================\n"
|
||||
@@ -47,13 +42,13 @@ echo -e "定时任务启动成功...\n"
|
||||
|
||||
if [[ $AutoStartBot == true ]]; then
|
||||
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"
|
||||
fi
|
||||
|
||||
if [[ $EnableExtraShell == true ]]; then
|
||||
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"
|
||||
fi
|
||||
|
||||
|
||||
+2
-2
@@ -125,12 +125,12 @@
|
||||
"qrcode.react": "^1.0.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"react": "18.x",
|
||||
"react": "18.2.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dom": "18.x",
|
||||
"react-dom": "18.2.0",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
|
||||
@@ -19,6 +19,11 @@ RepoFileExtensions="js py"
|
||||
## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890
|
||||
ProxyUrl=""
|
||||
|
||||
## 资源告警阙值,默认CPU 80%、内存80%、磁盘90%
|
||||
CpuWarn=80
|
||||
MemoryWarn=80
|
||||
DiskWarn=90
|
||||
|
||||
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||
CommandTimeoutTime="1h"
|
||||
|
||||
|
||||
+1
-2
@@ -35,8 +35,7 @@ fi
|
||||
cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
|
||||
|
||||
cd $dir_data
|
||||
cat requirements.txt | while read LREAD
|
||||
do
|
||||
cat requirements.txt | while read LREAD; do
|
||||
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
|
||||
pip3 --default-timeout=100 install ${LREAD}
|
||||
fi
|
||||
|
||||
+59
-2
@@ -78,6 +78,10 @@ import_config() {
|
||||
else
|
||||
default_cron="$(random_range 0 59) $(random_range 0 23) * * *"
|
||||
fi
|
||||
|
||||
cpu_warn=${CpuWarn:-80}
|
||||
mem_warn=${MemoryWarn:-80}
|
||||
disk_warn=${DiskWarn:-90}
|
||||
}
|
||||
|
||||
set_proxy() {
|
||||
@@ -349,8 +353,8 @@ reset_branch() {
|
||||
local branch="$1"
|
||||
if [[ $branch ]]; then
|
||||
part_cmd="origin/${branch}"
|
||||
git checkout -B "$branch"
|
||||
git branch --set-upstream-to=$part_cmd $branch
|
||||
git checkout -B "$branch" &>/dev/null
|
||||
git branch --set-upstream-to=$part_cmd $branch &>/dev/null
|
||||
fi
|
||||
git reset --hard $part_cmd &>/dev/null
|
||||
}
|
||||
@@ -423,6 +427,59 @@ format_timestamp() {
|
||||
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
|
||||
detect_termux
|
||||
detect_macos
|
||||
|
||||
+48
-51
@@ -118,6 +118,44 @@ handle_log_path() {
|
||||
make_dir "$dir_log/$log_dir"
|
||||
}
|
||||
|
||||
check_server() {
|
||||
cpu_use=$(top -b -n 1 | grep CPU | grep -v -E 'grep|PID' | awk '{print $2}' | cut -f 1 -d "%")
|
||||
|
||||
mem_free=$(free -m | grep "Mem" | awk '{print $3}')
|
||||
mem_total=$(free -m | grep "Mem" | awk '{print $2}')
|
||||
mem_use=$(printf "%d%%" $((mem_free * 100 / mem_total)) | cut -f 1 -d "%")
|
||||
|
||||
disk_use=$(df -P | grep /dev | grep -v -E '(tmp|boot|shm)' | awk '{print $5}' | cut -f 1 -d "%")
|
||||
|
||||
eval echo -e "\#\# 当前CPU占用 $cpu_use% 内存占用 $mem_use% 磁盘占用 $disk_use% \\\n" $cmd
|
||||
if [[ $cpu_use -gt $cpu_warn ]] || [[ $mem_free -lt $mem_warn ]] || [[ $disk_use -gt $disk_warn ]]; then
|
||||
echo -e "\#\# 服务器资源占用异常,本次任务跳过执行" $cmd
|
||||
notify_api "服务器资源异常警告" "当前CPU占用 $cpu_use% 内存占用 $mem_use% 磁盘占用 $disk_use%"
|
||||
|
||||
end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
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
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
handle_task_before() {
|
||||
handle_log_path
|
||||
|
||||
begin_time=$(format_time "$time_format" "$time")
|
||||
begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
|
||||
[[ $is_macos -eq 0 ]] && check_server
|
||||
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
|
||||
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||
eval . $file_task_before "$@" $cmd
|
||||
}
|
||||
|
||||
## 正常运行单个脚本,$1:传入参数
|
||||
run_normal() {
|
||||
local file_param=$1
|
||||
@@ -125,17 +163,7 @@ run_normal() {
|
||||
random_delay "$file_param"
|
||||
fi
|
||||
|
||||
handle_log_path
|
||||
|
||||
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
|
||||
handle_task_before
|
||||
|
||||
cd $dir_scripts
|
||||
local relative_path="${file_param%/*}"
|
||||
@@ -150,7 +178,7 @@ run_normal() {
|
||||
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"
|
||||
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||
}
|
||||
|
||||
@@ -164,6 +192,8 @@ run_concurrent() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
handle_task_before
|
||||
|
||||
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")
|
||||
@@ -179,18 +209,6 @@ run_concurrent() {
|
||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||
|
||||
handle_log_path
|
||||
|
||||
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")
|
||||
@@ -218,7 +236,7 @@ run_concurrent() {
|
||||
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"
|
||||
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||
}
|
||||
|
||||
@@ -231,10 +249,7 @@ run_designated() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
handle_log_path
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
handle_task_before
|
||||
|
||||
local envs=$(eval echo "\$${env_param}")
|
||||
local array=($(echo $envs | sed 's/&/ /g'))
|
||||
@@ -251,13 +266,6 @@ run_designated() {
|
||||
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
|
||||
@@ -270,7 +278,7 @@ run_designated() {
|
||||
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"
|
||||
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||
}
|
||||
|
||||
@@ -278,17 +286,7 @@ run_designated() {
|
||||
run_else() {
|
||||
local file_param="$1"
|
||||
|
||||
handle_log_path
|
||||
|
||||
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
|
||||
handle_task_before
|
||||
|
||||
cd $dir_scripts
|
||||
local relative_path="${file_param%/*}"
|
||||
@@ -304,15 +302,14 @@ run_else() {
|
||||
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"
|
||||
[[ $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
|
||||
while getopts ":l" opt; do
|
||||
case $opt in
|
||||
l)
|
||||
show_log="true"
|
||||
|
||||
+12
-63
@@ -244,11 +244,10 @@ usage() {
|
||||
|
||||
## 更新qinglong
|
||||
update_qinglong() {
|
||||
patch_version
|
||||
patch_version &>/dev/null
|
||||
|
||||
export isFirstStartServer=false
|
||||
|
||||
local no_restart="$1"
|
||||
local all_branch=$(git branch -a)
|
||||
local primary_branch="master"
|
||||
if [[ "${all_branch}" =~ "${current_branch}" ]]; then
|
||||
@@ -266,10 +265,16 @@ update_qinglong() {
|
||||
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
||||
[[ "$ql_depend_old" != "$ql_depend_new" ]] && npm_install_2 $dir_root
|
||||
else
|
||||
echo -e "\n更新青龙源文件失败,请检查原因...\n"
|
||||
fi
|
||||
|
||||
update_qinglong_static "$1" "$primary_branch"
|
||||
else
|
||||
echo -e "\n更新青龙源文件失败,请检查网络...\n"
|
||||
fi
|
||||
}
|
||||
|
||||
update_qinglong_static() {
|
||||
local no_restart="$1"
|
||||
local primary_branch="$2"
|
||||
local url="https://github.com/whyour/qinglong-static.git"
|
||||
if [[ -d ${ql_static_repo}/.git ]]; then
|
||||
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
||||
@@ -291,63 +296,8 @@ update_qinglong() {
|
||||
reload_pm2
|
||||
fi
|
||||
else
|
||||
echo -e "\n更新青龙静态资源失败,请检查原因...\n"
|
||||
echo -e "\n更新青龙静态资源失败,请检查网络...\n"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
patch_version() {
|
||||
# 兼容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
|
||||
|
||||
if [[ $PipMirror ]]; then
|
||||
pip3 config set global.index-url $PipMirror
|
||||
fi
|
||||
if [[ $NpmMirror ]]; then
|
||||
npm config set registry $NpmMirror
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
## 对比脚本
|
||||
@@ -466,8 +416,7 @@ get_uniq_path() {
|
||||
main() {
|
||||
## for ql update
|
||||
show_log="false"
|
||||
while getopts ":l" opt
|
||||
do
|
||||
while getopts ":l" opt; do
|
||||
case $opt in
|
||||
l)
|
||||
show_log="true"
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
@@ -68,18 +68,18 @@ export default {
|
||||
icon: <IconFont type="ql-icon-dependence" />,
|
||||
component: '@/pages/dependence/index',
|
||||
},
|
||||
{
|
||||
path: '/log',
|
||||
name: '日志管理',
|
||||
icon: <IconFont type="ql-icon-log" />,
|
||||
component: '@/pages/log/index',
|
||||
},
|
||||
{
|
||||
path: '/diff',
|
||||
name: '对比工具',
|
||||
icon: <IconFont type="ql-icon-diff" />,
|
||||
component: '@/pages/diff/index',
|
||||
},
|
||||
{
|
||||
path: '/log',
|
||||
name: '任务日志',
|
||||
icon: <IconFont type="ql-icon-log" />,
|
||||
component: '@/pages/log/index',
|
||||
},
|
||||
{
|
||||
path: '/setting',
|
||||
name: '系统设置',
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
&.env-wrapper,
|
||||
&.config-wrapper {
|
||||
.CodeMirror {
|
||||
width: calc(100vw - 80px);
|
||||
width: calc(100vw - 24px);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,6 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
[data-dark='true'] {
|
||||
/* Change autocomplete styles in WebKit */
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
@@ -254,11 +253,10 @@
|
||||
select:-webkit-autofill,
|
||||
select:-webkit-autofill:hover,
|
||||
select:-webkit-autofill:focus {
|
||||
border: 1px solid @border-color-base;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
-webkit-text-fill-color: @text-color;
|
||||
caret-color: #e8e6f3 !important;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
@@ -268,7 +266,6 @@
|
||||
.ant-select-selection-placeholder {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-basicLayout-content {
|
||||
margin: 12px;
|
||||
|
||||
+10
-6
@@ -20,6 +20,9 @@ import { message, Badge, Modal, Avatar, Dropdown, Menu, Image } from 'antd';
|
||||
import SockJS from 'sockjs-client';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { init } from '../utils/init';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
|
||||
export interface SharedContext {
|
||||
headerStyle: React.CSSProperties;
|
||||
@@ -41,6 +44,7 @@ export default function () {
|
||||
const ws = useRef<any>(null);
|
||||
const [socketMessage, setSocketMessage] = useState<any>();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [initLoading, setInitLoading] = useState<boolean>(true);
|
||||
const {
|
||||
enable: enableDarkMode,
|
||||
disable: disableDarkMode,
|
||||
@@ -64,17 +68,15 @@ export default function () {
|
||||
setSystemInfo(data);
|
||||
if (!data.isInitialized) {
|
||||
history.push('/initialization');
|
||||
setLoading(false);
|
||||
} else {
|
||||
getUser();
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
})
|
||||
.finally(() => setInitLoading(false));
|
||||
};
|
||||
|
||||
const getUser = (needLoading = true) => {
|
||||
@@ -87,8 +89,6 @@ export default function () {
|
||||
if (location.pathname === '/') {
|
||||
history.push('/crontab');
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
needLoading && setLoading(false);
|
||||
})
|
||||
@@ -196,6 +196,10 @@ export default function () {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (initLoading) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
if (['/login', '/initialization', '/error'].includes(location.pathname)) {
|
||||
document.title = `${
|
||||
(config.documentTitleMap as any)[location.pathname]
|
||||
|
||||
@@ -25,8 +25,10 @@ const Config = () => {
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const getConfig = (name: string) => {
|
||||
request.get(`${config.apiPrefix}configs/${name}`).then((data: any) => {
|
||||
setValue(data.data);
|
||||
request.get(`${config.apiPrefix}configs/${name}`).then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -34,8 +36,10 @@ const Config = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/files`)
|
||||
.then((data: any) => {
|
||||
setData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -50,8 +54,10 @@ const Config = () => {
|
||||
.post(`${config.apiPrefix}configs/save`, {
|
||||
data: { content, name: select },
|
||||
})
|
||||
.then((data: any) => {
|
||||
message.success(data.message);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('保存成功');
|
||||
}
|
||||
setConfirmLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -103,7 +103,6 @@ const CronDetailModal = ({
|
||||
fontSize: 12,
|
||||
lineNumbersMinChars: 3,
|
||||
fontFamily: 'Source Code Pro',
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
@@ -123,9 +122,11 @@ const CronDetailModal = ({
|
||||
.get(
|
||||
`${config.apiPrefix}logs/${item.filename}?path=${item.directory || ''}`,
|
||||
)
|
||||
.then((data) => {
|
||||
setLog(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLog(data);
|
||||
setIsLogModalVisible(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -137,9 +138,9 @@ const CronDetailModal = ({
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/${cron.id}/logs`)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
setLogs(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLogs(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -165,8 +166,10 @@ const CronDetailModal = ({
|
||||
setScriptInfo({ parent: p, filename: s });
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setValidTabs([validTabs[0]]);
|
||||
@@ -198,12 +201,10 @@ const CronDetailModal = ({
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(content);
|
||||
message.success(`保存成功`);
|
||||
} else {
|
||||
message.error(_data);
|
||||
}
|
||||
resolve(null);
|
||||
})
|
||||
@@ -231,14 +232,12 @@ const CronDetailModal = ({
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
|
||||
setTimeout(() => {
|
||||
getLogs();
|
||||
}, 1000);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -263,11 +262,9 @@ const CronDetailModal = ({
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -300,14 +297,12 @@ const CronDetailModal = ({
|
||||
data: [currentCron.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({
|
||||
...currentCron,
|
||||
isDisabled: currentCron.isDisabled === 1 ? 0 : 1,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -340,14 +335,12 @@ const CronDetailModal = ({
|
||||
data: [currentCron.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({
|
||||
...currentCron,
|
||||
isPinned: currentCron.isPinned === 1 ? 0 : 1,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
+29
-37
@@ -433,8 +433,9 @@ const Crontab = () => {
|
||||
}
|
||||
request
|
||||
.get(url)
|
||||
.then((_data: any) => {
|
||||
const { data, total } = _data.data;
|
||||
.then(({ code, data: _data }) => {
|
||||
if (code === 200) {
|
||||
const { data, total } = _data;
|
||||
setValue(
|
||||
data.map((x) => {
|
||||
return {
|
||||
@@ -447,6 +448,7 @@ const Crontab = () => {
|
||||
}),
|
||||
);
|
||||
setTotal(total);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -476,8 +478,8 @@ const Crontab = () => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -485,8 +487,6 @@ const Crontab = () => {
|
||||
result.splice(i, 1);
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -511,8 +511,8 @@ const Crontab = () => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/run`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -522,8 +522,6 @@ const Crontab = () => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -548,8 +546,8 @@ const Crontab = () => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/stop`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -560,8 +558,6 @@ const Crontab = () => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -594,8 +590,8 @@ const Crontab = () => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newStatus = record.isDisabled === 1 ? 0 : 1;
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -606,8 +602,6 @@ const Crontab = () => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -640,8 +634,8 @@ const Crontab = () => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newStatus = record.isPinned === 1 ? 0 : 1;
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -652,8 +646,6 @@ const Crontab = () => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -755,20 +747,22 @@ const Crontab = () => {
|
||||
const getCronDetail = (cron: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/${cron.id}`)
|
||||
.then((data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const index = value.findIndex((x) => x.id === cron.id);
|
||||
const result = [...value];
|
||||
data.data.nextRunTime = cron_parser
|
||||
.parseExpression(data.data.schedule)
|
||||
data.nextRunTime = cron_parser
|
||||
.parseExpression(data.schedule)
|
||||
.next()
|
||||
.toDate();
|
||||
if (index !== -1) {
|
||||
result.splice(index, 1, {
|
||||
...cron,
|
||||
...data.data,
|
||||
...data,
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -795,13 +789,11 @@ const Crontab = () => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons`, { data: selectedRowIds })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('批量删除成功');
|
||||
setSelectedRowIds([]);
|
||||
getCrons();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -820,11 +812,9 @@ const Crontab = () => {
|
||||
.put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
getCrons();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -1029,9 +1019,11 @@ const Crontab = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/views`)
|
||||
.then((data: any) => {
|
||||
setCronViews(data.data);
|
||||
setEnabledCronViews(data.data.filter((x) => !x.isDisabled));
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCronViews(data);
|
||||
setEnabledCronViews(data.filter((x) => !x.isDisabled));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -41,9 +41,12 @@ const CronLogModal = ({
|
||||
}
|
||||
request
|
||||
.get(logUrl ? logUrl : `${config.apiPrefix}crons/${cron.id}/log`)
|
||||
.then((data: any) => {
|
||||
if (localStorage.getItem('logCron') === String(cron.id)) {
|
||||
const log = data.data as string;
|
||||
.then(({ code, data }) => {
|
||||
if (
|
||||
code === 200 &&
|
||||
localStorage.getItem('logCron') === String(cron.id)
|
||||
) {
|
||||
const log = data as string;
|
||||
setValue(log || '暂无日志');
|
||||
setExecuting(
|
||||
log && !log.includes('执行结束') && !log.includes('重启面板'),
|
||||
|
||||
@@ -32,8 +32,6 @@ const CronModal = ({
|
||||
if (code === 200) {
|
||||
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error: any) {
|
||||
@@ -142,8 +140,6 @@ const CronLabelModal = ({
|
||||
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
||||
);
|
||||
handleCancel(true);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
|
||||
@@ -66,9 +66,7 @@ const ViewCreateModal = ({
|
||||
},
|
||||
);
|
||||
|
||||
if (code !== 200) {
|
||||
message.error(data);
|
||||
} else {
|
||||
if (code === 200) {
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
@@ -141,12 +141,10 @@ const ViewManageModal = ({
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons/views`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
cronViewChange();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -162,14 +160,12 @@ const ViewManageModal = ({
|
||||
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
|
||||
data: [record.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const _list = [...list];
|
||||
_list.splice(index, 1, { ...list[index], isDisabled: !checked });
|
||||
setList(_list);
|
||||
cronViewChange();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -190,15 +186,13 @@ const ViewManageModal = ({
|
||||
.put(`${config.apiPrefix}crons/views/move`, {
|
||||
data: { fromIndex: dragIndex, toIndex: hoverIndex, id: dragRow.id },
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newData = [...list];
|
||||
newData.splice(dragIndex, 1);
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data });
|
||||
setList(newData);
|
||||
cronViewChange();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -175,8 +175,10 @@ const Dependence = () => {
|
||||
.get(
|
||||
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
|
||||
)
|
||||
.then((data: any) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -212,9 +214,8 @@ const Dependence = () => {
|
||||
.delete(`${config.apiPrefix}dependencies${force ? '/force' : ''}`, {
|
||||
data: [record.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
if (force) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && force) {
|
||||
const i = value.findIndex((x) => x.id === data.data[0].id);
|
||||
if (i !== -1) {
|
||||
const result = [...value];
|
||||
@@ -222,9 +223,6 @@ const Dependence = () => {
|
||||
setValue(result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
@@ -250,11 +248,9 @@ const Dependence = () => {
|
||||
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||
data: [record.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
handleDependence(data.data[0]);
|
||||
} else {
|
||||
message.error(data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
handleDependence(data[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -309,12 +305,10 @@ const Dependence = () => {
|
||||
.delete(`${config.apiPrefix}dependencies${forceUrl}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setSelectedRowIds([]);
|
||||
getDependencies();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -333,12 +327,10 @@ const Dependence = () => {
|
||||
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setSelectedRowIds([]);
|
||||
getDependencies();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -351,16 +343,18 @@ const Dependence = () => {
|
||||
const getDependenceDetail = (dependence: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||
.then((data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const index = value.findIndex((x) => x.id === dependence.id);
|
||||
const result = [...value];
|
||||
if (index !== -1) {
|
||||
result.splice(index, 1, {
|
||||
...dependence,
|
||||
...data.data,
|
||||
...data,
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
@@ -48,9 +48,12 @@ const DependenceLogModal = ({
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||
.then((data: any) => {
|
||||
if (localStorage.getItem('logDependence') === String(dependence.id)) {
|
||||
const log = (data.data.log || []).join('') as string;
|
||||
.then(({ code, data }) => {
|
||||
if (
|
||||
code === 200 &&
|
||||
localStorage.getItem('logDependence') === String(dependence.id)
|
||||
) {
|
||||
const log = (data.log || []).join('') as string;
|
||||
setValue(log);
|
||||
setExecuting(!log.includes('结束时间'));
|
||||
setIsRemoveFailed(log.includes('删除失败'));
|
||||
@@ -67,8 +70,10 @@ const DependenceLogModal = ({
|
||||
.delete(`${config.apiPrefix}dependencies/force`, {
|
||||
data: [dependence.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
cancel(true);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setRemoveLoading(false);
|
||||
|
||||
@@ -53,9 +53,7 @@ const DependenceModal = ({
|
||||
},
|
||||
);
|
||||
|
||||
if (code !== 200) {
|
||||
message.error(data);
|
||||
} else {
|
||||
if (code === 200) {
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
@@ -22,14 +22,22 @@ const Diff = () => {
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const getConfig = () => {
|
||||
request.get(`${config.apiPrefix}configs/${current}`).then((data) => {
|
||||
setCurrentValue(data.data);
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/${current}`)
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getSample = () => {
|
||||
request.get(`${config.apiPrefix}configs/${origin}`).then((data) => {
|
||||
setOriginValue(data.data);
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/${origin}`)
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setOriginValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -42,8 +50,10 @@ const Diff = () => {
|
||||
.post(`${config.apiPrefix}configs/save`, {
|
||||
data: { content, name: current },
|
||||
})
|
||||
.then((data: any) => {
|
||||
message.success(data.message);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('保存成功');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -51,8 +61,10 @@ const Diff = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/files`)
|
||||
.then((data: any) => {
|
||||
setFiles(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setFiles(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
Vendored
-2
@@ -28,8 +28,6 @@ const EditNameModal = ({
|
||||
if (code === 200) {
|
||||
message.success('更新环境变量名称成功');
|
||||
handleCancel();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
|
||||
Vendored
+14
-22
@@ -255,8 +255,10 @@ const Env = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}envs?searchValue=${searchText}`)
|
||||
.then((data: any) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -284,8 +286,8 @@ const Env = () => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success(
|
||||
`${record.status === Status.已禁用 ? '启用' : '禁用'}成功`,
|
||||
);
|
||||
@@ -297,8 +299,6 @@ const Env = () => {
|
||||
status: newStatus,
|
||||
});
|
||||
setValue(result);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -333,14 +333,12 @@ const Env = () => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}envs`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...value];
|
||||
result.splice(index, 1);
|
||||
setValue(result);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -390,14 +388,12 @@ const Env = () => {
|
||||
.put(`${config.apiPrefix}envs/${dragRow.id}/move`, {
|
||||
data: { fromIndex: dragIndex, toIndex: hoverIndex },
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newData = [...value];
|
||||
newData.splice(dragIndex, 1);
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
||||
setValue([...newData]);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -426,13 +422,11 @@ const Env = () => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}envs`, { data: selectedRowIds })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('批量删除成功');
|
||||
setSelectedRowIds([]);
|
||||
getEnvs();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -451,11 +445,9 @@ const Env = () => {
|
||||
.put(`${config.apiPrefix}envs/${OperationPath[operationStatus]}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
getEnvs();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Vendored
-2
@@ -44,8 +44,6 @@ const EnvModal = ({
|
||||
if (code === 200) {
|
||||
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -17,8 +17,10 @@ const Error = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}public/panel/log`)
|
||||
.then((data: any) => {
|
||||
setData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
@@ -41,11 +41,9 @@ const Initialization = () => {
|
||||
password: values.password,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
next();
|
||||
} else {
|
||||
message.error(data.message);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -59,11 +57,9 @@ const Initialization = () => {
|
||||
...values,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data && _data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
next();
|
||||
} else {
|
||||
message.error(_data.message);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
|
||||
+140
-50
@@ -1,5 +1,15 @@
|
||||
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 { PageContainer } from '@ant-design/pro-layout';
|
||||
import Editor from '@monaco-editor/react';
|
||||
@@ -9,54 +19,33 @@ import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
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, uniq } from 'lodash';
|
||||
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||
|
||||
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 { Text } = Typography;
|
||||
|
||||
const Log = () => {
|
||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||
const [title, setTitle] = useState('请选择日志文件');
|
||||
const [value, setValue] = useState('请选择日志文件');
|
||||
const [select, setSelect] = useState<any>();
|
||||
const [select, setSelect] = useState<string>('');
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [filterData, setFilterData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [height, setHeight] = useState<number>();
|
||||
const treeDom = useRef<any>();
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const [currentNode, setCurrentNode] = useState<any>();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
const getLogs = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}logs`)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
setFilterData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -64,18 +53,27 @@ const Log = () => {
|
||||
const getLog = (node: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onSelect = (value: any, node: any) => {
|
||||
setCurrentNode(node);
|
||||
setSelect(value);
|
||||
|
||||
if (node.key === select || !value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === 'directory') {
|
||||
setValue('请选择日志文件');
|
||||
return;
|
||||
}
|
||||
|
||||
setValue('加载中...');
|
||||
setSelect(value);
|
||||
setTitle(node.key);
|
||||
getLog(node);
|
||||
};
|
||||
|
||||
@@ -86,41 +84,129 @@ const Log = () => {
|
||||
const onSearch = useCallback(
|
||||
(e) => {
|
||||
const keyword = e.target.value;
|
||||
const { tree, expandedKeys } = getFilterData(
|
||||
keyword.toLocaleLowerCase(),
|
||||
data,
|
||||
);
|
||||
setFilterData(tree);
|
||||
setExpandedKeys(expandedKeys);
|
||||
debounceSearch(keyword);
|
||||
},
|
||||
[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(() => {
|
||||
getLogs();
|
||||
if (treeDom && treeDom.current) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (treeDom.current) {
|
||||
setHeight(treeDom.current.clientHeight);
|
||||
}
|
||||
}, []);
|
||||
}, [treeDom.current, data]);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper log-wrapper"
|
||||
title={title}
|
||||
title={select}
|
||||
loading={loading}
|
||||
extra={
|
||||
isPhone && [
|
||||
isPhone
|
||||
? [
|
||||
<TreeSelect
|
||||
className="log-select"
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择日志"
|
||||
fieldNames={{ value: 'key', label: 'title' }}
|
||||
fieldNames={{ value: 'key' }}
|
||||
treeNodeFilterProp="title"
|
||||
showSearch
|
||||
allowClear
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
]
|
||||
: [
|
||||
<Tooltip title="删除">
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!select}
|
||||
onClick={deleteFile}
|
||||
icon={<DeleteOutlined />}
|
||||
/>
|
||||
</Tooltip>,
|
||||
]
|
||||
}
|
||||
header={{
|
||||
style: headerStyle,
|
||||
@@ -128,6 +214,7 @@ const Log = () => {
|
||||
>
|
||||
<div className={`${styles['log-container']} log-container`}>
|
||||
{!isPhone && (
|
||||
/*// @ts-ignore*/
|
||||
<SplitPane split="vertical" size={200} maxSize={-100}>
|
||||
<div className={styles['left-tree-container']}>
|
||||
{data.length > 0 ? (
|
||||
@@ -140,6 +227,7 @@ const Log = () => {
|
||||
></Input.Search>
|
||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||
<Tree
|
||||
expandAction="click"
|
||||
className={styles['left-tree']}
|
||||
treeData={filterData}
|
||||
showIcon={true}
|
||||
@@ -147,6 +235,8 @@ const Log = () => {
|
||||
selectedKeys={[select]}
|
||||
showLine={{ showLeafIcon: true }}
|
||||
onSelect={onTreeSelect}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={onExpand}
|
||||
></Tree>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+15
-23
@@ -40,15 +40,7 @@ const Login = () => {
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code === 420) {
|
||||
setLoginInfo({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
});
|
||||
setTwoFactor(true);
|
||||
} else {
|
||||
checkResponse(data);
|
||||
}
|
||||
checkResponse(data, values);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(function (error) {
|
||||
@@ -64,11 +56,7 @@ const Login = () => {
|
||||
data: { ...loginInfo, code: values.code },
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 430) {
|
||||
message.error(data.message);
|
||||
} else {
|
||||
checkResponse(data);
|
||||
}
|
||||
setVerifying(false);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -77,8 +65,11 @@ const Login = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const checkResponse = (data: any) => {
|
||||
if (data.code === 200) {
|
||||
const checkResponse = (
|
||||
{ code, data, message: _message }: any,
|
||||
values?: any,
|
||||
) => {
|
||||
if (code === 200) {
|
||||
const {
|
||||
token,
|
||||
lastip,
|
||||
@@ -86,7 +77,7 @@ const Login = () => {
|
||||
lastlogon,
|
||||
retries = 0,
|
||||
platform,
|
||||
} = data.data;
|
||||
} = data;
|
||||
localStorage.setItem(config.authKey, token);
|
||||
notification.success({
|
||||
message: '登录成功!',
|
||||
@@ -105,13 +96,14 @@ const Login = () => {
|
||||
});
|
||||
reloadUser(true);
|
||||
history.push('/crontab');
|
||||
} else if (data.code === 100) {
|
||||
message.warn(data.message);
|
||||
} else if (data.code === 410) {
|
||||
message.error(data.message);
|
||||
setWaitTime(data.data);
|
||||
} else {
|
||||
message.error(data.message);
|
||||
} else if (code === 410) {
|
||||
setWaitTime(data);
|
||||
} else if (code === 420) {
|
||||
setLoginInfo({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
});
|
||||
setTwoFactor(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -67,8 +67,10 @@ const EditModal = ({
|
||||
const getDetail = (node: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -83,8 +85,10 @@ const EditModal = ({
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setIsRunning(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -101,8 +105,10 @@ const EditModal = ({
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setIsRunning(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -216,6 +222,7 @@ const EditModal = ({
|
||||
onClose={cancel}
|
||||
open={visible}
|
||||
>
|
||||
{/* @ts-ignore */}
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
minSize={200}
|
||||
|
||||
@@ -57,8 +57,6 @@ const EditScriptNameModal = ({
|
||||
path,
|
||||
key: `${key}${filename}`,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
|
||||
+70
-84
@@ -37,37 +37,11 @@ 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';
|
||||
|
||||
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 = {
|
||||
'.py': 'python',
|
||||
'.js': 'javascript',
|
||||
@@ -78,11 +52,9 @@ const LangMap: any = {
|
||||
const Script = () => {
|
||||
const { headerStyle, isPhone, theme, socketMessage } =
|
||||
useOutletContext<SharedContext>();
|
||||
const [title, setTitle] = useState('请选择脚本文件');
|
||||
const [value, setValue] = useState('请选择脚本文件');
|
||||
const [select, setSelect] = useState<any>();
|
||||
const [select, setSelect] = useState<string>('');
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [filterData, setFilterData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [mode, setMode] = useState('');
|
||||
const [height, setHeight] = useState<number>();
|
||||
@@ -99,10 +71,11 @@ const Script = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts`)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
setFilterData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
initGetScript();
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -110,8 +83,10 @@ const Script = () => {
|
||||
const getDetail = (node: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -132,22 +107,24 @@ const Script = () => {
|
||||
};
|
||||
|
||||
const onSelect = (value: any, node: any) => {
|
||||
setSelect(node.key);
|
||||
setCurrentNode(node);
|
||||
|
||||
if (node.key === select || !value) {
|
||||
return;
|
||||
}
|
||||
setValue('加载中...');
|
||||
|
||||
if (node.type === 'directory') {
|
||||
setValue('请选择脚本文件');
|
||||
return;
|
||||
}
|
||||
|
||||
const newMode = value ? LangMap[value.slice(-3)] : '';
|
||||
setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode);
|
||||
setSelect(node.key);
|
||||
setTitle(node.key);
|
||||
setCurrentNode(node);
|
||||
setValue('加载中...');
|
||||
getDetail(node);
|
||||
};
|
||||
|
||||
const onExpand = (expKeys: any) => {
|
||||
setExpandedKeys(expKeys);
|
||||
};
|
||||
|
||||
const onTreeSelect = useCallback(
|
||||
(keys: Key[], e: any) => {
|
||||
const content = editorRef.current
|
||||
@@ -178,22 +155,30 @@ const Script = () => {
|
||||
const keyword = e.target.value;
|
||||
debounceSearch(keyword);
|
||||
},
|
||||
[data, setFilterData],
|
||||
[data],
|
||||
);
|
||||
|
||||
const debounceSearch = useCallback(
|
||||
debounce((keyword) => {
|
||||
setSearchValue(keyword);
|
||||
const { tree, expandedKeys } = getFilterData(
|
||||
keyword.toLocaleLowerCase(),
|
||||
data,
|
||||
);
|
||||
setExpandedKeys(expandedKeys);
|
||||
setFilterData(tree);
|
||||
}, 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 = () => {
|
||||
setTimeout(() => {
|
||||
setIsEditing(true);
|
||||
@@ -231,13 +216,11 @@ const Script = () => {
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success(`保存成功`);
|
||||
setValue(content);
|
||||
setIsEditing(false);
|
||||
} else {
|
||||
message.error(_data);
|
||||
}
|
||||
resolve(null);
|
||||
})
|
||||
@@ -255,10 +238,11 @@ const Script = () => {
|
||||
title: `确认删除`,
|
||||
content: (
|
||||
<>
|
||||
确认删除文件
|
||||
确认删除
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{select}
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
文件{currentNode.type === 'directory' ? '夹及其子文件' : ''}
|
||||
,删除后不可恢复
|
||||
</>
|
||||
),
|
||||
@@ -268,24 +252,18 @@ const Script = () => {
|
||||
data: {
|
||||
filename: currentNode.title,
|
||||
path: currentNode.parent || '',
|
||||
type: currentNode.type,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data.code === 200) {
|
||||
.then(({ code }) => {
|
||||
if (code === 200) {
|
||||
message.success(`删除成功`);
|
||||
let newData = [...data];
|
||||
if (currentNode.parent) {
|
||||
const parentNodeIndex = newData.findIndex(
|
||||
(x) => x.key === currentNode.parent,
|
||||
newData = depthFirstSearch(
|
||||
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 {
|
||||
const index = newData.findIndex(
|
||||
(x) => x.key === currentNode.key,
|
||||
@@ -295,8 +273,7 @@ const Script = () => {
|
||||
}
|
||||
}
|
||||
setData(newData);
|
||||
} else {
|
||||
message.error(_data);
|
||||
initState();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -346,8 +323,9 @@ const Script = () => {
|
||||
filename: currentNode.title,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
const blob = new Blob([_data], { type: 'application/json' });
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const blob = new Blob([data], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
@@ -355,21 +333,25 @@ const Script = () => {
|
||||
document.documentElement.appendChild(a);
|
||||
a.click();
|
||||
document.documentElement.removeChild(a);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const word = searchValue || '';
|
||||
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
||||
setFilterData(tree);
|
||||
}, [data]);
|
||||
const initState = () => {
|
||||
setSelect('');
|
||||
setCurrentNode(null);
|
||||
setValue('请选择脚本文件');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getScripts();
|
||||
if (treeDom && treeDom.current) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (treeDom.current) {
|
||||
setHeight(treeDom.current.clientHeight);
|
||||
}
|
||||
}, []);
|
||||
}, [treeDom.current, data]);
|
||||
|
||||
const action = (key: string | number) => {
|
||||
switch (key) {
|
||||
@@ -438,19 +420,22 @@ const Script = () => {
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper log-wrapper"
|
||||
title={title}
|
||||
title={select}
|
||||
loading={loading}
|
||||
extra={
|
||||
isPhone
|
||||
? [
|
||||
<TreeSelect
|
||||
treeExpandAction="click"
|
||||
className="log-select"
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择脚本"
|
||||
fieldNames={{ value: 'key', label: 'title' }}
|
||||
fieldNames={{ value: 'key' }}
|
||||
treeNodeFilterProp="title"
|
||||
showSearch
|
||||
allowClear
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
<Dropdown overlay={menu} trigger={['click']}>
|
||||
@@ -506,6 +491,7 @@ const Script = () => {
|
||||
>
|
||||
<div className={`${styles['log-container']} log-container`}>
|
||||
{!isPhone && (
|
||||
/*// @ts-ignore*/
|
||||
<SplitPane split="vertical" size={200} maxSize={-100}>
|
||||
<div className={styles['left-tree-container']}>
|
||||
{data.length > 0 ? (
|
||||
@@ -518,6 +504,7 @@ const Script = () => {
|
||||
></Input.Search>
|
||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||
<Tree
|
||||
expandAction="click"
|
||||
className={styles['left-tree']}
|
||||
treeData={filterData}
|
||||
showIcon={true}
|
||||
@@ -554,7 +541,6 @@ const Script = () => {
|
||||
readOnly: !isEditing,
|
||||
fontSize: 12,
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
|
||||
@@ -26,8 +26,6 @@ const SaveModal = ({
|
||||
if (code === 200) {
|
||||
message.success('保存文件成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
@@ -26,8 +26,6 @@ const SettingModal = ({
|
||||
if (code === 200) {
|
||||
message.success('保存文件成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
@@ -30,8 +30,6 @@ const AppModal = ({
|
||||
if (code === 200) {
|
||||
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,17 +17,14 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
message.loading('检查更新中...', 0);
|
||||
request
|
||||
.put(`${config.apiPrefix}system/update-check`)
|
||||
.then((_data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
message.destroy();
|
||||
const { code, data } = _data;
|
||||
if (code === 200) {
|
||||
if (data.hasNewVersion) {
|
||||
showConfirmUpdateModal(data);
|
||||
} else {
|
||||
showForceUpdateModal();
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -105,6 +102,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
};
|
||||
|
||||
const showUpdatingModal = () => {
|
||||
setValue('');
|
||||
modalRef.current = Modal.info({
|
||||
width: 600,
|
||||
maskClosable: false,
|
||||
@@ -140,7 +138,12 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
}
|
||||
|
||||
const newMessage = `${value}${_message}`;
|
||||
const updateFailed = newMessage.includes('失败,请检查');
|
||||
|
||||
modalRef.current.update({
|
||||
maskClosable: updateFailed,
|
||||
closable: updateFailed,
|
||||
okButtonProps: { disabled: !updateFailed },
|
||||
content: (
|
||||
<div style={{ height: '60vh', overflowY: 'auto' }}>
|
||||
<pre
|
||||
@@ -157,6 +160,11 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
if (updateFailed && !value.includes('失败,请检查')) {
|
||||
message.error('更新失败,请检查网络及日志或稍后再试');
|
||||
}
|
||||
|
||||
setValue(newMessage);
|
||||
|
||||
document.getElementById('log-identifier') &&
|
||||
|
||||
+23
-19
@@ -144,8 +144,10 @@ const Setting = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}apps`)
|
||||
.then((data: any) => {
|
||||
setDataSource(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setDataSource(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -175,14 +177,12 @@ const Setting = () => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}apps`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...dataSource];
|
||||
result.splice(index, 1);
|
||||
setDataSource(result);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -209,12 +209,10 @@ const Setting = () => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}apps/${record.id}/reset-secret`)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('重置成功');
|
||||
handleApp(data.data);
|
||||
} else {
|
||||
message.error(data);
|
||||
handleApp(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -247,8 +245,10 @@ const Setting = () => {
|
||||
const getLoginLog = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}user/login-log`)
|
||||
.then((data: any) => {
|
||||
setLoginLogData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLoginLogData(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -271,8 +271,10 @@ const Setting = () => {
|
||||
const getNotification = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}user/notification`)
|
||||
.then((data: any) => {
|
||||
setNotificationInfo(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setNotificationInfo(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -282,9 +284,9 @@ const Setting = () => {
|
||||
const getLogRemoveFrequency = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}system/log/remove`)
|
||||
.then((data: any) => {
|
||||
if (data.data.info) {
|
||||
const { frequency } = data.data.info;
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && data.info) {
|
||||
const { frequency } = data.info;
|
||||
setLogRemoveFrequency(frequency);
|
||||
}
|
||||
})
|
||||
@@ -299,8 +301,10 @@ const Setting = () => {
|
||||
.put(`${config.apiPrefix}system/log/remove`, {
|
||||
data: { frequency: logRemoveFrequency },
|
||||
})
|
||||
.then((data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('更新成功');
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
|
||||
@@ -23,11 +23,9 @@ const NotificationSetting = ({ data }: any) => {
|
||||
...values,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data && _data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success(values.type ? '通知发送成功' : '通知关闭成功');
|
||||
} else {
|
||||
message.error(_data.message);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -75,8 +73,7 @@ const NotificationSetting = ({ data }: any) => {
|
||||
rules={[{ required: x.required }]}
|
||||
style={{ maxWidth: 400 }}
|
||||
>
|
||||
{
|
||||
x.items ? (
|
||||
{x.items ? (
|
||||
<Select placeholder={x.placeholder || `请选择${x.label}`}>
|
||||
{x.items.map((y) => (
|
||||
<Option key={y.value} value={y.value}>
|
||||
@@ -89,8 +86,7 @@ const NotificationSetting = ({ data }: any) => {
|
||||
autoSize={true}
|
||||
placeholder={x.placeholder || `请输入${x.label}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</Form.Item>
|
||||
))}
|
||||
<Button type="primary" htmlType="submit">
|
||||
|
||||
@@ -27,9 +27,11 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
password: values.password,
|
||||
},
|
||||
})
|
||||
.then((data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
localStorage.removeItem(config.authKey);
|
||||
history.push('/login');
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -48,8 +50,8 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
const deactiveTowFactor = () => {
|
||||
request
|
||||
.put(`${config.apiPrefix}user/two-factor/deactive`)
|
||||
.then((data: any) => {
|
||||
if (data.data) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && data) {
|
||||
setTwoFactorActivated(false);
|
||||
userChange();
|
||||
}
|
||||
@@ -63,8 +65,9 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.put(`${config.apiPrefix}user/two-factor/active`, { data: { code } })
|
||||
.then((data: any) => {
|
||||
if (data.data) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
if (data) {
|
||||
message.success('激活成功');
|
||||
setTwoFactoring(false);
|
||||
setTwoFactorActivated(true);
|
||||
@@ -72,6 +75,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
} else {
|
||||
message.success('验证失败');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -82,8 +86,10 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
const getTwoFactorInfo = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}user/two-factor/init`)
|
||||
.then((data: any) => {
|
||||
setTwoFactorInfo(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setTwoFactorInfo(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
|
||||
@@ -263,8 +263,8 @@ const Subscription = () => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}subscriptions/run`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -274,8 +274,6 @@ const Subscription = () => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -300,8 +298,8 @@ const Subscription = () => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}subscriptions/stop`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -312,8 +310,6 @@ const Subscription = () => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -327,9 +323,11 @@ const Subscription = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}subscriptions?searchValue=${searchText}`)
|
||||
.then((data: any) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
setCurrentPage(1);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -359,8 +357,8 @@ const Subscription = () => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}subscriptions`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -368,8 +366,6 @@ const Subscription = () => {
|
||||
result.splice(i, 1);
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -402,8 +398,8 @@ const Subscription = () => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newStatus = record.is_disabled === 1 ? 0 : 1;
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -414,8 +410,6 @@ const Subscription = () => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -36,11 +36,12 @@ const SubscriptionLogModal = ({
|
||||
? logUrl
|
||||
: `${config.apiPrefix}subscriptions/${subscription.id}/log`,
|
||||
)
|
||||
.then((data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
if (
|
||||
code === 200 &&
|
||||
localStorage.getItem('logSubscription') === String(subscription.id)
|
||||
) {
|
||||
const log = data.data as string;
|
||||
const log = data as string;
|
||||
setValue(log || '暂无日志');
|
||||
setExecuting(log && !log.includes('执行结束'));
|
||||
if (log && !log.includes('执行结束')) {
|
||||
|
||||
@@ -40,8 +40,6 @@ const SubscriptionModal = ({
|
||||
if (code === 200) {
|
||||
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error: any) {
|
||||
@@ -190,8 +188,8 @@ const SubscriptionModal = ({
|
||||
};
|
||||
|
||||
const onPaste = useCallback((e: any) => {
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (!subscription && text.includes('ql ')) {
|
||||
const text = e.clipboardData.getData('text') as string;
|
||||
if (text.startsWith('ql ')) {
|
||||
const [
|
||||
,
|
||||
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(() => {
|
||||
if (visible) {
|
||||
window.addEventListener('paste', onPaste);
|
||||
@@ -243,35 +248,9 @@ const SubscriptionModal = ({
|
||||
}
|
||||
}, [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 (
|
||||
<Modal
|
||||
title={
|
||||
subscription ? (
|
||||
'编辑订阅'
|
||||
) : (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
title={subscription ? '编辑订阅' : '新建订阅'}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
@@ -291,7 +270,10 @@ const SubscriptionModal = ({
|
||||
>
|
||||
<Form form={form} name="form_in_modal" layout="vertical">
|
||||
<Form.Item name="name" label="名称">
|
||||
<Input placeholder="请输入订阅名" />
|
||||
<Input
|
||||
placeholder="支持拷贝ql repo/raw命令,粘贴导入"
|
||||
onPaste={onNamePaste}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="type"
|
||||
|
||||
+3
-3
@@ -56,7 +56,7 @@ export default {
|
||||
value: 'scripts',
|
||||
},
|
||||
{
|
||||
name: '任务日志',
|
||||
name: '日志管理',
|
||||
value: 'logs',
|
||||
},
|
||||
{
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
subscriptions: '订阅管理',
|
||||
configs: '配置文件',
|
||||
scripts: '脚本管理',
|
||||
logs: '任务日志',
|
||||
logs: '日志管理',
|
||||
dependencies: '依赖管理',
|
||||
system: '系统信息',
|
||||
},
|
||||
@@ -263,7 +263,7 @@ export default {
|
||||
'/config': '配置文件',
|
||||
'/script': '脚本管理',
|
||||
'/diff': '对比工具',
|
||||
'/log': '任务日志',
|
||||
'/log': '日志管理',
|
||||
'/setting': '系统设置',
|
||||
'/error': '错误日志',
|
||||
},
|
||||
|
||||
+17
-1
@@ -54,7 +54,23 @@ _request.interceptors.request.use((url, options) => {
|
||||
});
|
||||
|
||||
_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;
|
||||
});
|
||||
|
||||
|
||||
+6
-1
@@ -244,7 +244,7 @@ export function exportJson(name: string, data: string) {
|
||||
|
||||
export function depthFirstSearch<
|
||||
T extends Record<string, any> & { children?: T[] },
|
||||
>(children: T[], condition: (column: T) => boolean, item: T) {
|
||||
>(children: T[], condition: (column: T) => boolean, item?: T) {
|
||||
const c = [...children];
|
||||
const keys = [];
|
||||
|
||||
@@ -252,6 +252,11 @@ export function depthFirstSearch<
|
||||
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 {
|
||||
|
||||
+7
-7
@@ -1,8 +1,8 @@
|
||||
export const version = '2.14.4';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/331';
|
||||
export const changeLog = `2.14.4 版本说明
|
||||
1. 通知设置新增自定义通知
|
||||
2. 脚本管理支持新建文件夹
|
||||
3. 依赖管理增加批量重新安装
|
||||
4. 修复创建应用错误
|
||||
export const version = '2.14.6';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/333';
|
||||
export const changeLog = `2.14.6 版本说明
|
||||
1. 任务执行日志增加资源占用说明
|
||||
2. 配置文件config.sh增加资源配置参数CpuWarn/MemoryWarn/DiskWarn
|
||||
3. 修复脚本管理和日志管理搜索
|
||||
4. 其他bug修复
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user