Compare commits

..
31 Commits
Author SHA1 Message Date
whyour 9c6d9ec22d 更新版本 v2.14.8 2022-10-22 18:36:38 +08:00
whyour ac075ace73 修复调试脚本语言判断 2022-10-22 00:31:34 +08:00
whyour eb58774ee9 优化task.sh 2022-10-22 00:25:54 +08:00
whyour 04613bf9a9 修复tsconfig 2022-10-20 15:30:22 +08:00
whyour 4194f1964d 修改日志结束符判定 2022-10-20 15:16:17 +08:00
whyour 42c64c82d7 修复 ql 命令日志目录 2022-10-16 21:59:19 +08:00
whyour d44a8966f2 修复环境变量名称验证 2022-10-16 09:22:37 +08:00
whyour ff44330aa1 修复 ql 命令日志目录 2022-10-15 18:40:15 +08:00
whyour ed259e8168 修复 task 命令执行时参数引号丢失 2022-10-15 18:29:34 +08:00
whyour 444836975d 修改 ql 命令打印日志逻辑 2022-10-14 23:02:20 +08:00
whyour 3756fa93e4 修复 ql 命令 2022-10-14 21:17:04 +08:00
whyour 064b52b306 修改 timezone action 2022-10-14 20:55:58 +08:00
whyour 648dc51474 更新ql指令 2022-10-14 15:17:37 +08:00
whyour 7c0bc32759 修改环境变量导入 2022-10-13 23:41:54 +08:00
whyour d4b6cc5e4d 修复调试脚本选择脚本 2022-10-13 23:39:28 +08:00
whyour 75fca715e3 增加环境变量导入 2022-10-11 22:16:36 +08:00
whyour 6e9a4a29ef 修复shell语法 2022-10-09 13:49:19 +08:00
whyour 78f643c50f 更新版本v2.14.7 2022-10-01 23:12:57 +08:00
whyour 2db4f1ba87 修改资源判定逻辑 2022-10-01 23:04:12 +08:00
whyour 5741dbb78c 修复获取task_before参数 2022-09-30 15:32:42 +08:00
whyour 88cf671465 更新版本v2.14.6 2022-09-29 23:06:31 +08:00
whyour 29f082dea2 增加config资源配置 2022-09-29 23:03:22 +08:00
whyour 9a55968ed6 修改资源异常推送 2022-09-29 13:29:22 +08:00
whyour 6ec6e25855 修改资源提示 2022-09-29 00:59:27 +08:00
whyour af48bc378b 修复获取cpu占用 2022-09-29 00:57:31 +08:00
whyour 3f5ae4bcb9 修复 echo 2022-09-29 00:44:07 +08:00
whyour c6624e9001 增加资源占用提示 2022-09-29 00:43:07 +08:00
whyour 5530ce76e3 添加系统资源消耗提示 2022-09-29 00:32:17 +08:00
whyour 654b51e476 修复脚本和日志搜索逻辑 2022-09-25 21:03:11 +08:00
whyour 6428ac3624 修复SplitPane类型错误 2022-09-25 14:02:27 +08:00
whyour c36450f436 修复ant-tree高度获取 2022-09-25 13:38:10 +08:00
34 changed files with 1515 additions and 1386 deletions
+3 -5
View File
@@ -57,12 +57,10 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
- name: Set time zone - name: Setup timezone
uses: szenius/set-timezone@v1.0 uses: zcong1993/setup-timezone@master
with: with:
timezoneLinux: "Asia/Shanghai" timezone: Asia/Shanghai
timezoneMacos: "Asia/Shanghai"
timezoneWindows: "China Standard Time"
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
-1
View File
@@ -61,7 +61,6 @@ podman run -dit \
-p 5700:5700 \ -p 5700:5700 \
--name qinglong \ --name qinglong \
--hostname qinglong \ --hostname qinglong \
--restart unless-stopped \
docker.io/whyour/qinglong:latest docker.io/whyour/qinglong:latest
``` ```
-1
View File
@@ -61,7 +61,6 @@ podman run -dit \
-p 5700:5700 \ -p 5700:5700 \
--name qinglong \ --name qinglong \
--hostname qinglong \ --hostname qinglong \
--restart unless-stopped \
docker.io/whyour/qinglong:latest docker.io/whyour/qinglong:latest
``` ```
+49 -1
View File
@@ -3,8 +3,21 @@ import { Container } from 'typedi';
import EnvService from '../services/env'; import EnvService from '../services/env';
import { Logger } from 'winston'; import { Logger } from 'winston';
import { celebrate, Joi } from 'celebrate'; import { celebrate, Joi } from 'celebrate';
import multer from 'multer';
import config from '../config';
import fs from 'fs';
const route = Router(); const route = Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, config.scriptPath);
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({ storage: storage });
export default (app: Router) => { export default (app: Router) => {
app.use('/envs', route); app.use('/envs', route);
@@ -26,7 +39,9 @@ export default (app: Router) => {
body: Joi.array().items( body: Joi.array().items(
Joi.object({ Joi.object({
value: Joi.string().required(), value: Joi.string().required(),
name: Joi.string().required(), name: Joi.string()
.required()
.pattern(/^[a-zA-Z_][0-9a-zA-Z_]*$/),
remarks: Joi.string().optional().allow(''), remarks: Joi.string().optional().allow(''),
}), }),
), ),
@@ -177,4 +192,37 @@ export default (app: Router) => {
} }
}, },
); );
route.post(
'/upload',
upload.single('env'),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const envService = Container.get(EnvService);
const fileContent = await fs.promises.readFile(req!.file!.path, 'utf8');
const parseContent = JSON.parse(fileContent);
const data = Array.isArray(parseContent)
? parseContent
: [parseContent];
if (data.every((x) => x.name && x.value)) {
const result = await envService.create(
data.map((x) => ({
name: x.name,
value: x.value,
remarks: x.remarks,
})),
);
return res.send({ code: 200, data: result });
} else {
return res.send({
code: 400,
message: '文件缺少name或者value字段,参考导出文件格式',
});
}
} catch (e) {
return next(e);
}
},
);
}; };
+1
View File
@@ -0,0 +1 @@
export const LOG_END_SYMBOL = '\n          ';
+16 -8
View File
@@ -312,6 +312,7 @@ export function readDirs(
return { return {
title: file, title: file,
type: 'file', type: 'file',
isLeaf: true,
key, key,
parent: relativePath, parent: relativePath,
}; };
@@ -346,7 +347,7 @@ export function readDir(
export function emptyDir(path: string) { export function emptyDir(path: string) {
const files = fs.readdirSync(path); const files = fs.readdirSync(path);
files.forEach(file => { files.forEach((file) => {
const filePath = `${path}/${file}`; const filePath = `${path}/${file}`;
const stats = fs.statSync(filePath); const stats = fs.statSync(filePath);
if (stats.isDirectory()) { if (stats.isDirectory()) {
@@ -358,7 +359,6 @@ export function emptyDir(path: string) {
fs.rmdirSync(path); fs.rmdirSync(path);
} }
export function promiseExec(command: string): Promise<string> { export function promiseExec(command: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec( exec(
@@ -379,7 +379,8 @@ export function parseHeaders(headers: string) {
let val; let val;
let i; let i;
headers && headers.split('\n').forEach(function parser(line) { headers &&
headers.split('\n').forEach(function parser(line) {
i = line.indexOf(':'); i = line.indexOf(':');
key = line.substring(0, i).trim().toLowerCase(); key = line.substring(0, i).trim().toLowerCase();
val = line.substring(i + 1).trim(); val = line.substring(i + 1).trim();
@@ -392,9 +393,15 @@ export function parseHeaders(headers: string) {
}); });
return parsed; 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 ''; if (!body) return '';
const parsed: any = {}; const parsed: any = {};
@@ -402,7 +409,8 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
let val; let val;
let i; let i;
body && body.split('\n').forEach(function parser(line) { body &&
body.split('\n').forEach(function parser(line) {
i = line.indexOf(':'); i = line.indexOf(':');
key = line.substring(0, i).trim().toLowerCase(); key = line.substring(0, i).trim().toLowerCase();
val = line.substring(i + 1).trim(); val = line.substring(i + 1).trim();
@@ -417,7 +425,7 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
switch (contentType) { switch (contentType) {
case 'multipart/form-data': case 'multipart/form-data':
return Object.keys(parsed).reduce((p, c) => { return Object.keys(parsed).reduce((p, c) => {
p.append(c, parsed[c]) p.append(c, parsed[c]);
return p; return p;
}, new FormData()); }, new FormData());
case 'application/x-www-form-urlencoded': case 'application/x-www-form-urlencoded':
@@ -427,4 +435,4 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
} }
return parsed; return parsed;
}; }
+3 -2
View File
@@ -11,6 +11,7 @@ import { promisify } from 'util';
import { Op } from 'sequelize'; import { Op } from 'sequelize';
import path from 'path'; import path from 'path';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { LOG_END_SYMBOL } from '../config/const';
@Service() @Service()
export default class CronService { export default class CronService {
@@ -335,7 +336,7 @@ export default class CronService {
`${absolutePath}`, `${absolutePath}`,
`${str}\n## 执行结束... ${endTime.format( `${str}\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss',
)}${diffTimeStr}`, )}${diffTimeStr}${LOG_END_SYMBOL}`,
); );
} }
} }
@@ -420,7 +421,7 @@ export default class CronService {
); );
cp.stderr.on('data', (data) => { cp.stderr.on('data', (data) => {
if (logFileExist) { if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${data}`); fs.appendFileSync(`${absolutePath}`, `${data.toString()}`);
} }
}); });
cp.on('error', (err) => { cp.on('error', (err) => {
+4 -4
View File
@@ -76,12 +76,12 @@ export default class CronViewService {
const views = await this.list(); const views = await this.list();
if (toIndex === 0 || toIndex === views.length - 1) { if (toIndex === 0 || toIndex === views.length - 1) {
targetPosition = isUpward targetPosition = isUpward
? views[0].position * 2 ? views[0].position! * 2
: views[toIndex].position / 2; : views[toIndex].position! / 2;
} else { } else {
targetPosition = isUpward targetPosition = isUpward
? (views[toIndex].position + views[toIndex - 1].position) / 2 ? (views[toIndex].position! + views[toIndex - 1].position!) / 2
: (views[toIndex].position + views[toIndex + 1].position) / 2; : (views[toIndex].position! + views[toIndex + 1].position!) / 2;
} }
const newDoc = await this.update({ const newDoc = await this.update({
id, id,
+6 -7
View File
@@ -67,12 +67,12 @@ export default class EnvService {
const envs = await this.envs(); const envs = await this.envs();
if (toIndex === 0 || toIndex === envs.length - 1) { if (toIndex === 0 || toIndex === envs.length - 1) {
targetPosition = isUpward targetPosition = isUpward
? envs[0].position * 2 ? envs[0].position! * 2
: envs[toIndex].position / 2; : envs[toIndex].position! / 2;
} else { } else {
targetPosition = isUpward targetPosition = isUpward
? (envs[toIndex].position + envs[toIndex - 1].position) / 2 ? (envs[toIndex].position! + envs[toIndex - 1].position!) / 2
: (envs[toIndex].position + envs[toIndex + 1].position) / 2; : (envs[toIndex].position! + envs[toIndex + 1].position!) / 2;
} }
const newDoc = await this.update({ const newDoc = await this.update({
id, id,
@@ -158,7 +158,7 @@ export default class EnvService {
const envs = await this.envs( const envs = await this.envs(
'', '',
{ position: -1 }, { position: -1 },
{ name: { [Op.not]: null } }, { name: { [Op.not]: null }, status: EnvStatus.normal },
); );
const groups = _.groupBy(envs, 'name'); const groups = _.groupBy(envs, 'name');
let env_string = ''; let env_string = '';
@@ -167,9 +167,8 @@ export default class EnvService {
const group = groups[key]; const group = groups[key];
// 忽略不符合bash要求的环境变量名称 // 忽略不符合bash要求的环境变量名称
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) { if (/^[a-zA-Z_][0-9a-zA-Z_]*$/.test(key)) {
let value = _(group) let value = _(group)
.filter((x) => x.status !== EnvStatus.disabled)
.map('value') .map('value')
.join('&') .join('&')
.replace(/(\\)[^n]/g, '\\\\') .replace(/(\\)[^n]/g, '\\\\')
+2 -1
View File
@@ -6,6 +6,7 @@ import SockService from './sock';
import CronService from './cron'; import CronService from './cron';
import ScheduleService, { TaskCallbacks } from './schedule'; import ScheduleService, { TaskCallbacks } from './schedule';
import config from '../config'; import config from '../config';
import { LOG_END_SYMBOL } from '../config/const';
@Service() @Service()
export default class ScriptService { export default class ScriptService {
@@ -55,7 +56,7 @@ export default class ScriptService {
type: 'manuallyRunScript', type: 'manuallyRunScript',
message: `${str}\n## 执行结束... ${new Date() message: `${str}\n## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false }) .toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `, .replace(' 24:', ' 00:')}${LOG_END_SYMBOL}`,
}); });
return { code: 200 }; return { code: 200 };
+3 -2
View File
@@ -29,6 +29,7 @@ import { SimpleIntervalSchedule } from 'toad-scheduler';
import SockService from './sock'; import SockService from './sock';
import SshKeyService from './sshKey'; import SshKeyService from './sshKey';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { LOG_END_SYMBOL } from '../config/const';
@Service() @Service()
export default class SubscriptionService { export default class SubscriptionService {
@@ -232,7 +233,7 @@ export default class SubscriptionService {
absolutePath, absolutePath,
`\n## 执行结束... ${endTime.format( `\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss',
)} 耗时 ${diff}`, )} 耗时 ${diff}${LOG_END_SYMBOL}`,
); );
await SubscriptionModel.update( await SubscriptionModel.update(
@@ -353,7 +354,7 @@ export default class SubscriptionService {
fs.appendFileSync( fs.appendFileSync(
`${absolutePath}`, `${absolutePath}`,
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `, `${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')}${LOG_END_SYMBOL}`,
); );
} }
+2 -2
View File
@@ -125,12 +125,12 @@
"qrcode.react": "^1.0.1", "qrcode.react": "^1.0.1",
"query-string": "^7.1.1", "query-string": "^7.1.1",
"rc-tween-one": "^3.0.6", "rc-tween-one": "^3.0.6",
"react": "18.x", "react": "18.2.0",
"react-codemirror2": "^7.2.1", "react-codemirror2": "^7.2.1",
"react-diff-viewer": "^3.1.1", "react-diff-viewer": "^3.1.1",
"react-dnd": "^14.0.2", "react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0", "react-dnd-html5-backend": "^14.0.0",
"react-dom": "18.x", "react-dom": "18.2.0",
"react-split-pane": "^0.1.92", "react-split-pane": "^0.1.92",
"sockjs-client": "^1.6.0", "sockjs-client": "^1.6.0",
"ts-node": "^10.6.0", "ts-node": "^10.6.0",
+5
View File
@@ -19,6 +19,11 @@ RepoFileExtensions="js py"
## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890 ## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890
ProxyUrl="" ProxyUrl=""
## 资源告警阙值,默认CPU 80%、内存80%、磁盘90%
CpuWarn=80
MemoryWarn=80
DiskWarn=90
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天 ## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
CommandTimeoutTime="1h" CommandTimeoutTime="1h"
+1 -1
View File
@@ -154,7 +154,7 @@ update_cron() {
code=$(echo $api | jq -r .code) code=$(echo $api | jq -r .code)
message=$(echo $api | jq -r .message) message=$(echo $api | jq -r .message)
if [[ $code != 200 ]]; then if [[ $code != 200 ]]; then
echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path echo -e "\n## 更新任务状态失败(${message})\n" >>$dir_log/$log_path
fi fi
} }
+3 -4
View File
@@ -35,11 +35,10 @@ fi
cp -f "$repo_path/jbot/requirements.txt" "$dir_data" cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
cd $dir_data cd $dir_data
cat requirements.txt | while read LREAD cat requirements.txt | while read LREAD; do
do if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
pip3 --default-timeout=100 install ${LREAD} pip3 --default-timeout=100 install ${LREAD}
fi fi
done done
echo -e "\npython3依赖安装成功...\n" echo -e "\npython3依赖安装成功...\n"
+266
View File
@@ -0,0 +1,266 @@
#!/usr/bin/env bash
## 导入通用变量与函数
dir_shell=$QL_DIR/shell
. $dir_shell/share.sh
. $dir_shell/api.sh
random_delay() {
local random_delay_max=$RandomDelay
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
local file_param=$1
local file_extensions=${RandomDelayFileExtensions-"js"}
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
if [[ -n $file_extensions ]]; then
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
return
fi
fi
local current_min
current_min=$(date "+%-M")
for minute in $ignored_minutes; do
if [[ $current_min -eq $minute ]]; then
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
return
fi
done
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
sleep $delay_second
fi
}
## scripts目录下所有可运行脚本数组
gen_array_scripts() {
local dir_current=$(pwd)
local i="-1"
cd $dir_scripts
for file in $(ls); do
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
let i++
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1)
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
fi
done
cd $dir_current
}
## 使用说明
usage() {
gen_array_scripts
echo -e "task命令运行本程序自动添加进crontab的脚本,需要输入脚本的绝对路径或去掉 “$dir_scripts/” 目录后的相对路径(定时任务中请写作相对路径),用法为:"
echo -e "1.$cmd_task <file_name> # 依次执行,如果设置了随机延迟,将随机延迟一定秒数"
echo -e "2.$cmd_task <file_name> now # 依次执行,无论是否设置了随机延迟,均立即运行,前台会输出日志,同时记录在日志文件中"
echo -e "3.$cmd_task <file_name> conc <环境变量名称> <账号编号,空格分隔>(可选的) # 并发执行,无论是否设置了随机延迟,均立即运行,前台不产生日志,直接记录在日志文件中,且可指定账号执行"
echo -e "4.$cmd_task <file_name> desi <环境变量名称> <账号编号,空格分隔> # 指定账号执行,无论是否设置了随机延迟,均立即运行"
if [[ ${#array_scripts[*]} -gt 0 ]]; then
echo -e "\n当前有以下脚本可以运行:"
for ((i = 0; i < ${#array_scripts[*]}; i++)); do
echo -e "$(($i + 1)). ${array_scripts_name[i]}${array_scripts[i]}"
done
else
echo -e "\n暂无脚本可以执行"
fi
}
## run nohup,$1:文件名,不含路径,带后缀
run_nohup() {
local file_name=$1
nohup node $file_name &>$log_path &
}
check_server() {
cpu_use=$(top -b -n 1 | grep CPU | grep -v -E 'grep|PID' | awk '{print $2}' | cut -f 1 -d "%")
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 "%")
if [[ $cpu_use -gt $cpu_warn ]] || [[ $mem_free -lt $mem_warn ]] || [[ $disk_use -gt $disk_warn ]]; then
local resource=$(top -b -n 1 | grep -v -E 'grep|Mem|idle|Load' | awk '{$2="";$3="";$4="";$5="";$7="";print $0}' | head -n 10)
notify_api "服务器资源异常警告" "当前CPU占用 $cpu_use% 内存占用 $mem_use% 磁盘占用 $disk_use% \n资源占用详情 \n\n $resource"
fi
}
handle_task_before() {
begin_time=$(format_time "$time_format" "$time")
begin_timestamp=$(format_timestamp "$time_format" "$time")
echo -e "## 开始执行... $begin_time\n"
[[ $is_macos -eq 0 ]] && check_server
[[ -f $task_error_log_path ]] && cat $task_error_log_path
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
. $file_task_before "$@"
}
handle_task_after() {
. $file_task_after "$@"
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"
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time"
echo -e "\n     "
}
## 正常运行单个脚本,$1:传入参数
run_normal() {
local file_param=$1
if [[ $# -eq 1 ]]; then
random_delay "$file_param"
fi
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ${file_param} != /* ]] && [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
$timeoutCmd $which_program $file_param
}
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
run_concurrent() {
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]]; then
echo -e "\n 缺少并发运行的环境变量参数"
exit 1
fi
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval $timeoutCmd $which_program $file_param &>$single_log_path &
done
wait
for i in "${!array[@]}"; do
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
cat $single_log_path
[[ -f $single_log_path ]] && rm -f $single_log_path
done
}
run_designated() {
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
echo -e "\n 缺少单独运行的参数 task xxx.js desi Test 1 3"
exit 1
fi
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
$timeoutCmd $which_program $file_param
}
## 运行其他命令
run_else() {
local file_param="$1"
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
shift
local params=$(echo "$@" | sed 's/ /\" \"/g')
$timeoutCmd $which_program $file_param \"$params\"
}
## 命令检测
main() {
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
case $# in
1)
run_normal "$1"
;;
*)
case $2 in
now)
run_normal "$1" "$2"
;;
conc)
run_concurrent "$1" "$3" "$*"
;;
desi)
run_designated "$1" "$3" "$*"
;;
*)
run_else "$@"
;;
esac
;;
esac
elif [[ $# -eq 0 ]]; then
echo
usage
else
run_else "$@"
fi
}
handle_task_before "$@"
main "$@"
handle_task_after "$@"
+4
View File
@@ -78,6 +78,10 @@ import_config() {
else else
default_cron="$(random_range 0 59) $(random_range 0 23) * * *" default_cron="$(random_range 0 59) $(random_range 0 23) * * *"
fi fi
cpu_warn=${CpuWarn:-80}
mem_warn=${MemoryWarn:-80}
disk_warn=${DiskWarn:-90}
} }
set_proxy() { set_proxy() {
+19 -298
View File
@@ -21,78 +21,12 @@ define_program() {
fi fi
} }
random_delay() {
local random_delay_max=$RandomDelay
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
local file_param=$1
local file_extensions=${RandomDelayFileExtensions-"js"}
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
if [[ -n $file_extensions ]]; then
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
return
fi
fi
local current_min
current_min=$(date "+%-M")
for minute in $ignored_minutes; do
if [[ $current_min -eq $minute ]]; then
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
return
fi
done
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
sleep $delay_second
fi
}
## scripts目录下所有可运行脚本数组
gen_array_scripts() {
local dir_current=$(pwd)
local i="-1"
cd $dir_scripts
for file in $(ls); do
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
let i++
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1)
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
fi
done
cd $dir_current
}
## 使用说明
usage() {
define_cmd
gen_array_scripts
echo -e "task命令运行本程序自动添加进crontab的脚本,需要输入脚本的绝对路径或去掉 “$dir_scripts/” 目录后的相对路径(定时任务中请写作相对路径),用法为:"
echo -e "1.$cmd_task <file_name> # 依次执行,如果设置了随机延迟,将随机延迟一定秒数"
echo -e "2.$cmd_task <file_name> now # 依次执行,无论是否设置了随机延迟,均立即运行,前台会输出日志,同时记录在日志文件中"
echo -e "3.$cmd_task <file_name> conc <环境变量名称> <账号编号,空格分隔>(可选的) # 并发执行,无论是否设置了随机延迟,均立即运行,前台不产生日志,直接记录在日志文件中,且可指定账号执行"
echo -e "4.$cmd_task <file_name> desi <环境变量名称> <账号编号,空格分隔> # 指定账号执行,无论是否设置了随机延迟,均立即运行"
if [[ ${#array_scripts[*]} -gt 0 ]]; then
echo -e "\n当前有以下脚本可以运行:"
for ((i = 0; i < ${#array_scripts[*]}; i++)); do
echo -e "$(($i + 1)). ${array_scripts_name[i]}${array_scripts[i]}"
done
else
echo -e "\n暂无脚本可以执行"
fi
}
## run nohup,$1:文件名,不含路径,带后缀
run_nohup() {
local file_name=$1
nohup node $file_name &>$log_path &
}
handle_log_path() { handle_log_path() {
define_program "$file_param" local file_param=$1
if [[ -z $file_param ]];then
file_param="task"
fi
local suffix="" local suffix=""
if [[ ! -z $ID ]]; then if [[ ! -z $ID ]]; then
@@ -118,241 +52,28 @@ handle_log_path() {
make_dir "$dir_log/$log_dir" make_dir "$dir_log/$log_dir"
} }
## 正常运行单个脚本,$1:传入参数 format_params() {
run_normal() { time_format="%Y-%m-%d %H:%M:%S"
local file_param=$1 timeoutCmd=""
if [[ $# -eq 1 ]]; then if type timeout &>/dev/null; then
random_delay "$file_param" timeoutCmd="timeout -k 10s $command_timeout_time "
fi 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
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
eval . $file_task_before "$@" $cmd
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval $timeoutCmd $which_program $file_param $cmd
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local diff_time=$(expr $end_timestamp - $begin_timestamp)
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
} }
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行 show_log="false"
run_concurrent() { while getopts ":l" opt; do
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]]; then
echo -e "\n 缺少并发运行的环境变量参数"
exit 1
fi
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<< ${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
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
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
eval . $file_task_before "$@" $cmd
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval $timeoutCmd $which_program $file_param &>$single_log_path &
done
wait
for i in "${!array[@]}"; do
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval cat $single_log_path $cmd
[[ -f $single_log_path ]] && rm -f $single_log_path
done
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local diff_time=$(( $end_timestamp - $begin_timestamp ))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
}
run_designated() {
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
echo -e "\n 缺少单独运行的参数 task xxx.js desi Test 1 3"
exit 1
fi
handle_log_path
local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<< ${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
eval . $file_task_before "$@" $cmd
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval $timeoutCmd $which_program $file_param $cmd
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local diff_time=$(( $end_timestamp - $begin_timestamp ))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
}
## 运行其他命令
run_else() {
local file_param="$1"
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
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
eval . $file_task_before "$@" $cmd
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
shift
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
eval . $file_task_after "$file_param" "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local diff_time=$(( $end_timestamp - $begin_timestamp ))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
}
## 命令检测
main() {
show_log="false"
while getopts ":l" opt
do
case $opt in case $opt in
l) l)
show_log="true" show_log="true"
;; ;;
esac esac
done done
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1)) [[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
timeoutCmd="" format_params
if type timeout &>/dev/null; then define_program "$@"
timeoutCmd="timeout -k 10s $command_timeout_time " handle_log_path "$@"
fi eval . $dir_shell/otask.sh "$@" "$cmd"
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
time_format="%Y-%m-%d %H:%M:%S"
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
case $# in
1)
run_normal "$1"
;;
*)
case $2 in
now)
run_normal "$1" "$2"
;;
conc)
run_concurrent "$1" "$3" "$*"
;;
desi)
run_designated "$1" "$3" "$*"
;;
*)
run_else "$@"
;;
esac
;;
esac
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
elif [[ $# -eq 0 ]]; then
echo
usage
else
run_else "$@"
fi
}
main "$@"
exit 0 exit 0
+36 -33
View File
@@ -416,8 +416,7 @@ get_uniq_path() {
main() { main() {
## for ql update ## for ql update
show_log="false" show_log="false"
while getopts ":l" opt while getopts ":l" opt; do
do
case $opt in case $opt in
l) l)
show_log="true" show_log="true"
@@ -433,30 +432,40 @@ main() {
local p5=$5 local p5=$5
local p6=$6 local p6=$6
local p7=$7 local p7=$7
local log_dir="${p1}"
make_dir "$dir_log/$log_dir"
local log_time=$(date "+%Y-%m-%d-%H-%M-%S") local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
local log_path="$dir_log/update/${log_time}_$p1.log" local log_path="${log_dir}/${log_time}.log"
local begin_time=$(date '+%Y-%m-%d %H:%M:%S') local file_path="$dir_log/$log_path"
cmd=">> $file_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
eval echo -e "请移除 -l 参数" $cmd
exit 1
fi
local time_format="%Y-%m-%d %H:%M:%S"
local time=$(date "+$time_format")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
case $p1 in case $p1 in
update) update)
cmd=">> $log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
eval echo -e "## 开始执行... $begin_time\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
eval update_qinglong "$2" $cmd eval update_qinglong "$2" $cmd
;; ;;
extra) extra)
echo -e "## 开始执行... $begin_time\n" >>$log_path eval run_extra_shell $cmd
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
run_extra_shell >>$log_path
;; ;;
repo) repo)
get_uniq_path "$p2" "$p6" get_uniq_path "$p2" "$p6"
if [[ -n $p2 ]]; then if [[ -n $p2 ]]; then
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7" update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
else else
echo -e "命令输入错误...\n" eval echo -e "命令输入错误...\\\n"
usage eval usage $cmd
fi fi
;; ;;
raw) raw)
@@ -464,47 +473,41 @@ main() {
if [[ -n $p2 ]]; then if [[ -n $p2 ]]; then
update_raw "$p2" update_raw "$p2"
else else
echo -e "命令输入错误...\n" eval echo -e "命令输入错误...\\\n"
usage eval usage $cmd
fi fi
;; ;;
rmlog) rmlog)
echo -e "## 开始执行... $begin_time\n" >>$log_path eval . $dir_shell/rmlog.sh "$p2" $cmd
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
. $dir_shell/rmlog.sh "$p2" >>$log_path
;; ;;
bot) bot)
echo -e "## 开始执行... $begin_time\n" >>$log_path eval . $dir_shell/bot.sh $cmd
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
. $dir_shell/bot.sh >>$log_path
;; ;;
check) check)
echo -e "## 开始执行... $begin_time\n" >>$log_path eval . $dir_shell/check.sh $cmd
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
. $dir_shell/check.sh >>$log_path
;; ;;
resetlet) resetlet)
echo -e "## 开始执行... $begin_time\n" >>$log_path
auth_value=$(cat $file_auth_user | jq '.retries =0' -c) auth_value=$(cat $file_auth_user | jq '.retries =0' -c)
echo -e "重置登录错误次数成功 \n $auth_value" >>$log_path echo -e "重置登录错误次数成功 \n $auth_value" >>$log_path
echo "$auth_value" >$file_auth_user echo "$auth_value" >$file_auth_user
;; ;;
resettfa) resettfa)
echo -e "## 开始执行... $begin_time\n" >>$log_path
auth_value=$(cat $file_auth_user | jq '.twoFactorActivated =false' | jq '.twoFactorActived =false' -c) auth_value=$(cat $file_auth_user | jq '.twoFactorActivated =false' | jq '.twoFactorActived =false' -c)
echo -e "禁用两步验证成功 \n $auth_value" >>$log_path echo -e "禁用两步验证成功 \n $auth_value" >>$log_path
echo "$auth_value" >$file_auth_user echo "$auth_value" >$file_auth_user
;; ;;
*) *)
echo -e "命令输入错误...\n" eval echo -e "命令输入错误...\\\n" $cmd
usage eval usage $cmd
;; ;;
esac esac
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local diff_time=$(diff_time "%Y-%m-%d %H:%M:%S" "$begin_time" "$end_time") if [[ -f $file_path ]]; then
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then local end_timestamp=$(date "+%s")
echo -e "\n## 执行结束... $end_time 耗时 $diff_time" >>$log_path local diff_time=$(($end_timestamp - $begin_timestamp))
cat $log_path [[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n          " $cmd
cat $file_path
fi fi
} }
+48
View File
@@ -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]);
};
+1
View File
@@ -80,6 +80,7 @@ const Config = () => {
loading={loading} loading={loading}
extra={[ extra={[
<TreeSelect <TreeSelect
treeExpandAction="click"
className="config-select" className="config-select"
value={select} value={select}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }} dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+3 -2
View File
@@ -7,6 +7,7 @@ import {
CheckCircleOutlined, CheckCircleOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-layout'; import { PageLoading } from '@ant-design/pro-layout';
import { logEnded } from '@/utils';
enum CrontabStatus { enum CrontabStatus {
'running', 'running',
@@ -49,9 +50,9 @@ const CronLogModal = ({
const log = data as string; const log = data as string;
setValue(log || '暂无日志'); setValue(log || '暂无日志');
setExecuting( setExecuting(
log && !log.includes('执行结束') && !log.includes('重启面板'), log && !logEnded(log) && !log.includes('重启面板'),
); );
if (log && !log.includes('执行结束') && !log.includes('重启面板')) { if (log && !logEnded(log) && !log.includes('重启面板')) {
setTimeout(() => { setTimeout(() => {
getCronLog(); getCronLog();
}, 2000); }, 2000);
+40
View File
@@ -9,6 +9,8 @@ import {
Typography, Typography,
Tooltip, Tooltip,
Input, Input,
UploadProps,
Upload,
} from 'antd'; } from 'antd';
import { import {
EditOutlined, EditOutlined,
@@ -16,6 +18,7 @@ import {
SyncOutlined, SyncOutlined,
CheckCircleOutlined, CheckCircleOutlined,
StopOutlined, StopOutlined,
UploadOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import config from '@/utils/config'; import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout'; import { PageContainer } from '@ant-design/pro-layout';
@@ -250,6 +253,7 @@ const Env = () => {
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]); const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const [tableScrollHeight, setTableScrollHeight] = useState<number>(); const [tableScrollHeight, setTableScrollHeight] = useState<number>();
const [importLoading, setImportLoading] = useState(false);
const getEnvs = () => { const getEnvs = () => {
setLoading(true); setLoading(true);
@@ -472,6 +476,33 @@ const Env = () => {
setSearchText(value.trim()); setSearchText(value.trim());
}; };
const uploadProps: UploadProps = {
accept: 'application/json',
beforeUpload: async (file) => {
const formData = new FormData();
formData.append('env', file);
setImportLoading(true);
try {
const { code, data } = await request.post(
`${config.apiPrefix}envs/upload`,
{
data: formData,
},
);
if (code === 200) {
message.success(`成功上传${data.length}个环境变量`);
getEnvs();
}
setImportLoading(false);
} catch (error: any) {
setImportLoading(false);
}
return false;
},
fileList: [],
};
useEffect(() => { useEffect(() => {
getEnvs(); getEnvs();
}, [searchText]); }, [searchText]);
@@ -494,6 +525,15 @@ const Env = () => {
loading={loading} loading={loading}
onSearch={onSearch} onSearch={onSearch}
/>, />,
<Upload {...uploadProps}>
<Button
type="primary"
icon={<UploadOutlined />}
loading={importLoading}
>
</Button>
</Upload>,
<Button key="2" type="primary" onClick={() => addEnv()}> <Button key="2" type="primary" onClick={() => addEnv()}>
</Button>, </Button>,
+29 -47
View File
@@ -21,44 +21,16 @@ import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts'; import { SharedContext } from '@/layouts';
import { DeleteOutlined } from '@ant-design/icons'; import { DeleteOutlined } from '@ant-design/icons';
import { depthFirstSearch } from '@/utils'; import { depthFirstSearch } from '@/utils';
import { debounce } from 'lodash'; import { debounce, uniq } from 'lodash';
import useFilterTreeData from '@/hooks/useFilterTreeData';
const { Text } = Typography; const { Text } = Typography;
function getFilterData(keyword: string, data: any) {
const expandedKeys: string[] = [];
if (keyword) {
const tree: any = [];
data.forEach((item: any) => {
if (item.title.toLocaleLowerCase().includes(keyword)) {
tree.push(item);
} else {
const children: any[] = [];
(item.children || []).forEach((subItem: any) => {
if (subItem.title.toLocaleLowerCase().includes(keyword)) {
children.push(subItem);
}
});
if (children.length > 0) {
tree.push({
...item,
children,
});
expandedKeys.push(item.key);
}
}
});
return { tree, expandedKeys };
}
return { tree: data, expandedKeys };
}
const Log = () => { const Log = () => {
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>(); const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
const [value, setValue] = useState('请选择日志文件'); const [value, setValue] = useState('请选择日志文件');
const [select, setSelect] = useState<string>(''); const [select, setSelect] = useState<string>('');
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [filterData, setFilterData] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [height, setHeight] = useState<number>(); const [height, setHeight] = useState<number>();
const treeDom = useRef<any>(); const treeDom = useRef<any>();
@@ -73,7 +45,6 @@ const Log = () => {
.then(({ code, data }) => { .then(({ code, data }) => {
if (code === 200) { if (code === 200) {
setData(data); setData(data);
setFilterData(data);
} }
}) })
.finally(() => setLoading(false)); .finally(() => setLoading(false));
@@ -115,22 +86,26 @@ const Log = () => {
const keyword = e.target.value; const keyword = e.target.value;
debounceSearch(keyword); debounceSearch(keyword);
}, },
[data, setFilterData], [data],
); );
const debounceSearch = useCallback( const debounceSearch = useCallback(
debounce((keyword) => { debounce((keyword) => {
setSearchValue(keyword); setSearchValue(keyword);
const { tree, expandedKeys } = getFilterData(
keyword.toLocaleLowerCase(),
data,
);
setFilterData(tree);
setExpandedKeys(expandedKeys);
}, 300), }, 300),
[data, setFilterData], [data],
); );
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
data,
searchValue,
{ treeNodeFilterProp: 'title' },
);
useEffect(() => {
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
}, [searchExpandedKeys]);
const deleteFile = () => { const deleteFile = () => {
Modal.confirm({ Modal.confirm({
title: `确认删除`, title: `确认删除`,
@@ -187,18 +162,19 @@ const Log = () => {
setValue('请选择脚本文件'); setValue('请选择脚本文件');
}; };
useEffect(() => { const onExpand = (expKeys: any) => {
const word = searchValue || ''; setExpandedKeys(expKeys);
const { tree } = getFilterData(word.toLocaleLowerCase(), data); };
setFilterData(tree);
}, [data]);
useEffect(() => { useEffect(() => {
getLogs(); getLogs();
if (treeDom && treeDom.current) { }, []);
useEffect(() => {
if (treeDom.current) {
setHeight(treeDom.current.clientHeight); setHeight(treeDom.current.clientHeight);
} }
}, []); }, [treeDom.current, data]);
return ( return (
<PageContainer <PageContainer
@@ -209,13 +185,16 @@ const Log = () => {
isPhone isPhone
? [ ? [
<TreeSelect <TreeSelect
treeExpandAction="click"
className="log-select" className="log-select"
value={select} value={select}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }} dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={data} treeData={data}
placeholder="请选择日志" placeholder="请选择日志"
fieldNames={{ value: 'key', label: 'title' }} fieldNames={{ value: 'key' }}
treeNodeFilterProp="title"
showSearch showSearch
allowClear
onSelect={onSelect} onSelect={onSelect}
/>, />,
] ]
@@ -236,6 +215,7 @@ const Log = () => {
> >
<div className={`${styles['log-container']} log-container`}> <div className={`${styles['log-container']} log-container`}>
{!isPhone && ( {!isPhone && (
/*// @ts-ignore*/
<SplitPane split="vertical" size={200} maxSize={-100}> <SplitPane split="vertical" size={200} maxSize={-100}>
<div className={styles['left-tree-container']}> <div className={styles['left-tree-container']}>
{data.length > 0 ? ( {data.length > 0 ? (
@@ -256,6 +236,8 @@ const Log = () => {
selectedKeys={[select]} selectedKeys={[select]}
showLine={{ showLeafIcon: true }} showLine={{ showLeafIcon: true }}
onSelect={onTreeSelect} onSelect={onTreeSelect}
expandedKeys={expandedKeys}
onExpand={onExpand}
></Tree> ></Tree>
</div> </div>
</> </>
+12 -1
View File
@@ -7,6 +7,7 @@ import Editor from '@monaco-editor/react';
import SaveModal from './saveModal'; import SaveModal from './saveModal';
import SettingModal from './setting'; import SettingModal from './setting';
import { useTheme } from '@/utils/hooks'; import { useTheme } from '@/utils/hooks';
import { logEnded } from '@/utils';
const { Option } = Select; const { Option } = Select;
const LangMap: any = { const LangMap: any = {
@@ -57,6 +58,11 @@ const EditModal = ({
if (node.key === selectedKey || !value) { if (node.key === selectedKey || !value) {
return; return;
} }
if (node.type === 'directory') {
return;
}
const newMode = LangMap[value.slice(-3)] || ''; const newMode = LangMap[value.slice(-3)] || '';
setCNode(node); setCNode(node);
setLanguage(newMode); setLanguage(newMode);
@@ -123,7 +129,7 @@ const EditModal = ({
return; return;
} }
if (_message.includes('执行结束')) { if (logEnded(_message)) {
setTimeout(() => { setTimeout(() => {
setIsRunning(false); setIsRunning(false);
}, 300); }, 300);
@@ -136,10 +142,13 @@ const EditModal = ({
}, [socketMessage]); }, [socketMessage]);
useEffect(() => { useEffect(() => {
setLog('');
if (currentNode) { if (currentNode) {
setCNode(currentNode); setCNode(currentNode);
setValue(content as string); setValue(content as string);
setSelectedKey(currentNode.key); setSelectedKey(currentNode.key);
const newMode = LangMap[currentNode.title.slice(-3)] || '';
setLanguage(newMode);
} }
}, [content, currentNode]); }, [content, currentNode]);
@@ -150,6 +159,7 @@ const EditModal = ({
title={ title={
<> <>
<TreeSelect <TreeSelect
treeExpandAction="click"
style={{ marginRight: 8, width: 150 }} style={{ marginRight: 8, width: 150 }}
value={selectedKey} value={selectedKey}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }} dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
@@ -222,6 +232,7 @@ const EditModal = ({
onClose={cancel} onClose={cancel}
open={visible} open={visible}
> >
{/* @ts-ignore */}
<SplitPane <SplitPane
split="vertical" split="vertical"
minSize={200} minSize={200}
+31 -51
View File
@@ -37,37 +37,11 @@ import { history, useOutletContext, useLocation } from '@umijs/max';
import { parse } from 'query-string'; import { parse } from 'query-string';
import { depthFirstSearch } from '@/utils'; import { depthFirstSearch } from '@/utils';
import { SharedContext } from '@/layouts'; import { SharedContext } from '@/layouts';
import useFilterTreeData from '@/hooks/useFilterTreeData';
import { uniq } from 'lodash';
const { Text } = Typography; const { Text } = Typography;
function getFilterData(keyword: string, data: any) {
const expandedKeys: string[] = [];
if (keyword) {
const tree: any = [];
data.forEach((item: any) => {
if (item.title.toLocaleLowerCase().includes(keyword)) {
tree.push(item);
} else {
const children: any[] = [];
(item.children || []).forEach((subItem: any) => {
if (subItem.title.toLocaleLowerCase().includes(keyword)) {
children.push(subItem);
}
});
if (children.length > 0) {
tree.push({
...item,
children,
});
expandedKeys.push(item.key);
}
}
});
return { tree, expandedKeys };
}
return { tree: data, expandedKeys };
}
const LangMap: any = { const LangMap: any = {
'.py': 'python', '.py': 'python',
'.js': 'javascript', '.js': 'javascript',
@@ -81,7 +55,6 @@ const Script = () => {
const [value, setValue] = useState('请选择脚本文件'); const [value, setValue] = useState('请选择脚本文件');
const [select, setSelect] = useState<string>(''); const [select, setSelect] = useState<string>('');
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [filterData, setFilterData] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [mode, setMode] = useState(''); const [mode, setMode] = useState('');
const [height, setHeight] = useState<number>(); const [height, setHeight] = useState<number>();
@@ -101,7 +74,6 @@ const Script = () => {
.then(({ code, data }) => { .then(({ code, data }) => {
if (code === 200) { if (code === 200) {
setData(data); setData(data);
setFilterData(data);
initGetScript(); initGetScript();
} }
}) })
@@ -110,7 +82,11 @@ const Script = () => {
const getDetail = (node: any) => { const getDetail = (node: any) => {
request request
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`) .get(
`${config.apiPrefix}scripts/${encodeURIComponent(node.title)}?path=${
node.parent || ''
}`,
)
.then(({ code, data }) => { .then(({ code, data }) => {
if (code === 200) { if (code === 200) {
setValue(data); setValue(data);
@@ -153,10 +129,6 @@ const Script = () => {
getDetail(node); getDetail(node);
}; };
const onExpand = (expKeys: any) => {
setExpandedKeys(expKeys);
};
const onTreeSelect = useCallback( const onTreeSelect = useCallback(
(keys: Key[], e: any) => { (keys: Key[], e: any) => {
const content = editorRef.current const content = editorRef.current
@@ -187,22 +159,30 @@ const Script = () => {
const keyword = e.target.value; const keyword = e.target.value;
debounceSearch(keyword); debounceSearch(keyword);
}, },
[data, setFilterData], [data],
); );
const debounceSearch = useCallback( const debounceSearch = useCallback(
debounce((keyword) => { debounce((keyword) => {
setSearchValue(keyword); setSearchValue(keyword);
const { tree, expandedKeys } = getFilterData(
keyword.toLocaleLowerCase(),
data,
);
setExpandedKeys(expandedKeys);
setFilterData(tree);
}, 300), }, 300),
[data, setFilterData], [data],
); );
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
data,
searchValue,
{ treeNodeFilterProp: 'title' },
);
useEffect(() => {
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
}, [searchExpandedKeys]);
const onExpand = (expKeys: any) => {
setExpandedKeys(expKeys);
};
const editFile = () => { const editFile = () => {
setTimeout(() => { setTimeout(() => {
setIsEditing(true); setIsEditing(true);
@@ -368,17 +348,14 @@ const Script = () => {
}; };
useEffect(() => { useEffect(() => {
const word = searchValue || ''; getScripts();
const { tree } = getFilterData(word.toLocaleLowerCase(), data); }, []);
setFilterData(tree);
}, [data]);
useEffect(() => { useEffect(() => {
getScripts(); if (treeDom.current) {
if (treeDom && treeDom.current) {
setHeight(treeDom.current.clientHeight); setHeight(treeDom.current.clientHeight);
} }
}, []); }, [treeDom.current, data]);
const action = (key: string | number) => { const action = (key: string | number) => {
switch (key) { switch (key) {
@@ -459,8 +436,10 @@ const Script = () => {
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }} dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={data} treeData={data}
placeholder="请选择脚本" placeholder="请选择脚本"
fieldNames={{ value: 'key', label: 'title' }} fieldNames={{ value: 'key' }}
treeNodeFilterProp="title"
showSearch showSearch
allowClear
onSelect={onSelect} onSelect={onSelect}
/>, />,
<Dropdown overlay={menu} trigger={['click']}> <Dropdown overlay={menu} trigger={['click']}>
@@ -516,6 +495,7 @@ const Script = () => {
> >
<div className={`${styles['log-container']} log-container`}> <div className={`${styles['log-container']} log-container`}>
{!isPhone && ( {!isPhone && (
/*// @ts-ignore*/
<SplitPane split="vertical" size={200} maxSize={-100}> <SplitPane split="vertical" size={200} maxSize={-100}>
<div className={styles['left-tree-container']}> <div className={styles['left-tree-container']}>
{data.length > 0 ? ( {data.length > 0 ? (
+4 -2
View File
@@ -102,6 +102,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
}; };
const showUpdatingModal = () => { const showUpdatingModal = () => {
setValue('');
modalRef.current = Modal.info({ modalRef.current = Modal.info({
width: 600, width: 600,
maskClosable: false, maskClosable: false,
@@ -159,12 +160,13 @@ const CheckUpdate = ({ socketMessage }: any) => {
</div> </div>
), ),
}); });
setValue(newMessage);
if (updateFailed) { if (updateFailed && !value.includes('失败,请检查')) {
message.error('更新失败,请检查网络及日志或稍后再试'); message.error('更新失败,请检查网络及日志或稍后再试');
} }
setValue(newMessage);
document.getElementById('log-identifier') && document.getElementById('log-identifier') &&
document document
.getElementById('log-identifier')! .getElementById('log-identifier')!
+3 -2
View File
@@ -7,6 +7,7 @@ import {
CheckCircleOutlined, CheckCircleOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-layout'; import { PageLoading } from '@ant-design/pro-layout';
import { logEnded } from '@/utils';
const SubscriptionLogModal = ({ const SubscriptionLogModal = ({
subscription, subscription,
@@ -43,8 +44,8 @@ const SubscriptionLogModal = ({
) { ) {
const log = data as string; const log = data as string;
setValue(log || '暂无日志'); setValue(log || '暂无日志');
setExecuting(log && !log.includes('执行结束')); setExecuting(log && !logEnded(log));
if (log && !log.includes('执行结束')) { if (log && !logEnded(log)) {
setTimeout(() => { setTimeout(() => {
getCronLog(); getCronLog();
}, 2000); }, 2000);
+1
View File
@@ -0,0 +1 @@
export const LOG_END_SYMBOL = '\n          ';
+7
View File
@@ -1,3 +1,5 @@
import { LOG_END_SYMBOL } from './const';
export default function browserType() { export default function browserType() {
// 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本 // 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
const ua = navigator.userAgent.toLowerCase(); const ua = navigator.userAgent.toLowerCase();
@@ -273,3 +275,8 @@ export function depthFirstSearch<
return c; return c;
} }
export function logEnded(log: string): boolean {
const endTips = [LOG_END_SYMBOL, '执行结束'];
return endTips.some((x) => log.includes(x));
}
+7 -8
View File
@@ -1,9 +1,8 @@
export const version = '2.14.5'; export const version = '2.14.8';
export const changeLogLink = 'https://t.me/jiao_long/333'; export const changeLogLink = 'https://t.me/jiao_long/335';
export const changeLog = `2.14.5 版本说明 export const changeLog = `2.14.8 版本说明
1. 日志管理支持删除日志目录和日志文件 1. 支持环境变量导入,导入格式参考导出文件
2. 脚本管理支持删除文件夹 2. 修复创建任务,命令中的引号丢失
3. 修复任务详情获取不到日志列表 3. 修复调试选择脚本及语言识别
4. 修复移动端脚本高亮 4. 修复ql和task命令日志打印
5. 修复安装Node依赖提示ERR_PNPM_REGISTRIES_MISMATCH
`; `;
+3
View File
@@ -7,6 +7,9 @@
"./src/types", "./src/types",
"./node_modules/celebrate/lib/index.d.ts" "./node_modules/celebrate/lib/index.d.ts"
], ],
"paths": {
"@/*": ["./back/*"],
},
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,