mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-13 20:30:48 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c5223ba11 | ||
|
|
2f70e10be7 | ||
|
|
66706ce150 | ||
|
|
3d719baa54 | ||
|
|
926ad067c1 | ||
|
|
ff98c3a499 | ||
|
|
69fcf45231 | ||
|
|
c3704502bf | ||
|
|
a0613d0f39 | ||
|
|
8c0f46420e |
+11
-3
@@ -18,9 +18,9 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript
|
|||||||
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
||||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
|
|
||||||
[Demo](http://demo.dlww.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
|
[Demo](http://demo.ninesix.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
|
||||||
|
|
||||||
[演示](http://demo.dlww.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
|
[演示](http://demo.ninesix.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
@@ -74,6 +74,14 @@ docker run -dit \
|
|||||||
whyour/qinglong:latest
|
whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### BaoTa Panel one-click deployment (Recommended)
|
||||||
|
|
||||||
|
1. To install Pagoda Panel, go to the official website of [BaoTa Panel](https://www.bt.cn/u/EcDAFU), select the official version of the script to download and install.
|
||||||
|
|
||||||
|
2. After installation, login to Pagoda Panel, click `Docker` in the menu bar, the first time you enter, you will be prompted to install `Docker` service, click Install Now, follow the prompts to complete the installation.
|
||||||
|
|
||||||
|
3. After the installation is complete, find `Qinglong Panel` in the app shop, click Install, configure the domain name and other basic information to complete the installation.
|
||||||
|
|
||||||
### Docker-compose (Recommended)
|
### Docker-compose (Recommended)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -105,7 +113,7 @@ podman run -dit \
|
|||||||
docker.io/whyour/qinglong:latest
|
docker.io/whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Npm
|
### Npm (Local)
|
||||||
|
|
||||||
It is recommended to use a pure system installation to avoid losing the original system data, you need to install node/npm/python3/pip3/pnpm yourself
|
It is recommended to use a pure system installation to avoid losing the original system data, you need to install node/npm/python3/pip3/pnpm yourself
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript
|
|||||||
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
||||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
|
|
||||||
[Demo](http://demo.dlww.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
|
[Demo](http://demo.ninesix.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
|
||||||
|
|
||||||
[演示](http://demo.dlww.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
|
[演示](http://demo.ninesix.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
@@ -76,6 +76,14 @@ docker run -dit \
|
|||||||
whyour/qinglong:latest
|
whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 宝塔面板一键部署(推荐)
|
||||||
|
|
||||||
|
1. 安装宝塔面板,前往 [宝塔面板](https://www.bt.cn/u/EcDAFU) 官网,选择正式版的脚本下载安装
|
||||||
|
|
||||||
|
2. 安装后登录宝塔面板,在菜单栏中点击 `Docker`,首次进入会提示安装`Docker`服务,点击立即安装,按提示完成安装
|
||||||
|
|
||||||
|
3. 安装完成后在应用商店中找到`青龙面板`,点击安装,配置域名等基本信息即可完成安装
|
||||||
|
|
||||||
### docker-compose (推荐)
|
### docker-compose (推荐)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -107,7 +115,7 @@ podman run -dit \
|
|||||||
docker.io/whyour/qinglong:latest
|
docker.io/whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### npm
|
### npm (本地)
|
||||||
|
|
||||||
建议使用纯净系统安装,避免系统原有数据丢失,需要自己安装 node/npm/python3/pip3/pnpm
|
建议使用纯净系统安装,避免系统原有数据丢失,需要自己安装 node/npm/python3/pip3/pnpm
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getFileContentByName, getLastModifyFilePath } from '../config/util';
|
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
|
|||||||
+22
-16
@@ -1,10 +1,9 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import * as fs from 'fs';
|
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { getFileContentByName, readDirs, rmPath } from '../config/util';
|
import { getFileContentByName, readDirs, rmPath } from '../config/util';
|
||||||
import { join } from 'path';
|
import { join, resolve } from 'path';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
const blacklist = ['.tmp'];
|
const blacklist = ['.tmp'];
|
||||||
@@ -30,15 +29,19 @@ export default (app: Router) => {
|
|||||||
'/detail',
|
'/detail',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
if (blacklist.includes(req.path)) {
|
const finalPath = resolve(
|
||||||
|
config.logPath,
|
||||||
|
(req.query.path as string) || '',
|
||||||
|
(req.query.file as string) || '',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
blacklist.includes(req.query.path as string) ||
|
||||||
|
!finalPath.startsWith(config.logPath)
|
||||||
|
) {
|
||||||
return res.send({ code: 403, message: '暂无权限' });
|
return res.send({ code: 403, message: '暂无权限' });
|
||||||
}
|
}
|
||||||
const filePath = join(
|
const content = await getFileContentByName(finalPath);
|
||||||
config.logPath,
|
|
||||||
(req.query.path || '') as string,
|
|
||||||
req.query.file as string,
|
|
||||||
);
|
|
||||||
const content = await getFileContentByName(filePath);
|
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -50,15 +53,18 @@ export default (app: Router) => {
|
|||||||
'/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
if (blacklist.includes(req.path)) {
|
const finalPath = resolve(
|
||||||
|
config.logPath,
|
||||||
|
(req.query.path as string) || '',
|
||||||
|
(req.params.file as string) || '',
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
blacklist.includes(req.path) ||
|
||||||
|
!finalPath.startsWith(config.logPath)
|
||||||
|
) {
|
||||||
return res.send({ code: 403, message: '暂无权限' });
|
return res.send({ code: 403, message: '暂无权限' });
|
||||||
}
|
}
|
||||||
const filePath = join(
|
const content = await getFileContentByName(finalPath);
|
||||||
config.logPath,
|
|
||||||
(req.query.path || '') as string,
|
|
||||||
req.params.file,
|
|
||||||
);
|
|
||||||
const content = await getFileContentByName(filePath);
|
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
|
|||||||
+1
-8
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { fileExist, readDirs, readDir, rmPath } from '../config/util';
|
||||||
fileExist,
|
|
||||||
getFileContentByName,
|
|
||||||
readDirs,
|
|
||||||
getLastModifyFilePath,
|
|
||||||
readDir,
|
|
||||||
rmPath,
|
|
||||||
} from '../config/util';
|
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const tmpPath = path.join(rootPath, '.tmp/');
|
|||||||
const samplePath = path.join(rootPath, 'sample/');
|
const samplePath = path.join(rootPath, 'sample/');
|
||||||
const configPath = path.join(dataPath, 'config/');
|
const configPath = path.join(dataPath, 'config/');
|
||||||
const scriptPath = path.join(dataPath, 'scripts/');
|
const scriptPath = path.join(dataPath, 'scripts/');
|
||||||
|
const repoPath = path.join(dataPath, 'repo/');
|
||||||
const bakPath = path.join(dataPath, 'bak/');
|
const bakPath = path.join(dataPath, 'bak/');
|
||||||
const logPath = path.join(dataPath, 'log/');
|
const logPath = path.join(dataPath, 'log/');
|
||||||
const dbPath = path.join(dataPath, 'db/');
|
const dbPath = path.join(dataPath, 'db/');
|
||||||
@@ -101,6 +102,7 @@ export default {
|
|||||||
uploadPath,
|
uploadPath,
|
||||||
configPath,
|
configPath,
|
||||||
scriptPath,
|
scriptPath,
|
||||||
|
repoPath,
|
||||||
samplePath,
|
samplePath,
|
||||||
blackFileList: [
|
blackFileList: [
|
||||||
'auth.json',
|
'auth.json',
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ let pickedEnv: Record<string, string>;
|
|||||||
|
|
||||||
function getPickedEnv() {
|
function getPickedEnv() {
|
||||||
if (pickedEnv) return pickedEnv;
|
if (pickedEnv) return pickedEnv;
|
||||||
const picked = pick(process.env, ['QlBaseUrl', 'DeployEnv']);
|
const picked = pick(process.env, ['QlBaseUrl', 'DeployEnv', 'QL_DIR']);
|
||||||
if (picked.QlBaseUrl) {
|
if (picked.QlBaseUrl) {
|
||||||
if (!picked.QlBaseUrl.startsWith('/')) {
|
if (!picked.QlBaseUrl.startsWith('/')) {
|
||||||
picked.QlBaseUrl = `/${picked.QlBaseUrl}`
|
picked.QlBaseUrl = `/${picked.QlBaseUrl}`;
|
||||||
}
|
}
|
||||||
if (!picked.QlBaseUrl.endsWith('/')) {
|
if (!picked.QlBaseUrl.endsWith('/')) {
|
||||||
picked.QlBaseUrl = `${picked.QlBaseUrl}/`
|
picked.QlBaseUrl = `${picked.QlBaseUrl}/`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pickedEnv = picked as Record<string, string>;
|
pickedEnv = picked as Record<string, string>;
|
||||||
|
|||||||
+14
-2
@@ -11,9 +11,21 @@ export default class ConfigService {
|
|||||||
|
|
||||||
public async getFile(filePath: string, res: Response) {
|
public async getFile(filePath: string, res: Response) {
|
||||||
let content = '';
|
let content = '';
|
||||||
if (config.blackFileList.includes(filePath) || !filePath) {
|
const avaliablePath = [config.rootPath, config.configPath].map((x) =>
|
||||||
res.send({ code: 403, message: '文件无法访问' });
|
path.resolve(x, filePath),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.blackFileList.includes(filePath) ||
|
||||||
|
avaliablePath.every(
|
||||||
|
(x) =>
|
||||||
|
!x.startsWith(config.scriptPath) && !x.startsWith(config.configPath),
|
||||||
|
) ||
|
||||||
|
!filePath
|
||||||
|
) {
|
||||||
|
return res.send({ code: 403, message: '文件无法访问' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filePath.startsWith('sample/')) {
|
if (filePath.startsWith('sample/')) {
|
||||||
const res = await got.get(
|
const res = await got.get(
|
||||||
`https://gitlab.com/whyour/qinglong/-/raw/master/${filePath}`,
|
`https://gitlab.com/whyour/qinglong/-/raw/master/${filePath}`,
|
||||||
|
|||||||
@@ -65,11 +65,13 @@ export default class ScriptService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getFile(filePath: string, fileName: string) {
|
public async getFile(filePath: string, fileName: string) {
|
||||||
let _filePath = join(config.scriptPath, filePath, fileName);
|
const finalPath = path.resolve(config.scriptPath, filePath, fileName);
|
||||||
if (filePath.startsWith(config.dataPath)) {
|
|
||||||
_filePath = join(filePath, fileName);
|
if (!finalPath.startsWith(config.scriptPath)) {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
const content = await getFileContentByName(_filePath);
|
|
||||||
|
const content = await getFileContentByName(finalPath);
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,9 @@ export default class SubscriptionService {
|
|||||||
}
|
}
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
const filePath = join(config.scriptPath, doc.alias);
|
const filePath = join(config.scriptPath, doc.alias);
|
||||||
|
const repoPath = join(config.repoPath, doc.alias);
|
||||||
await rmPath(filePath);
|
await rmPath(filePath);
|
||||||
|
await rmPath(repoPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-6
@@ -1143,14 +1143,20 @@ function webhookNotify(text, desp) {
|
|||||||
WEBHOOK_CONTENT_TYPE,
|
WEBHOOK_CONTENT_TYPE,
|
||||||
WEBHOOK_METHOD,
|
WEBHOOK_METHOD,
|
||||||
} = push_config;
|
} = push_config;
|
||||||
if (!WEBHOOK_URL.includes('$title') && !WEBHOOK_BODY.includes('$title')) {
|
if (
|
||||||
|
!WEBHOOK_METHOD ||
|
||||||
|
!WEBHOOK_URL ||
|
||||||
|
(!WEBHOOK_URL.includes('$title') && !WEBHOOK_BODY.includes('$title'))
|
||||||
|
) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = parseHeaders(WEBHOOK_HEADERS);
|
const headers = parseHeaders(WEBHOOK_HEADERS);
|
||||||
const body = parseBody(WEBHOOK_BODY, WEBHOOK_CONTENT_TYPE, (v) =>
|
const body = parseBody(WEBHOOK_BODY, WEBHOOK_CONTENT_TYPE, (v) =>
|
||||||
v?.replaceAll('$title', text)?.replaceAll('$content', desp),
|
v
|
||||||
|
?.replaceAll('$title', text?.replaceAll('\n', '\\n'))
|
||||||
|
?.replaceAll('$content', desp?.replaceAll('\n', '\\n')),
|
||||||
);
|
);
|
||||||
const bodyParam = formatBodyFun(WEBHOOK_CONTENT_TYPE, body);
|
const bodyParam = formatBodyFun(WEBHOOK_CONTENT_TYPE, body);
|
||||||
const options = {
|
const options = {
|
||||||
@@ -1162,7 +1168,6 @@ function webhookNotify(text, desp) {
|
|||||||
retry: 1,
|
retry: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (WEBHOOK_METHOD) {
|
|
||||||
const formatUrl = WEBHOOK_URL.replaceAll(
|
const formatUrl = WEBHOOK_URL.replaceAll(
|
||||||
'$title',
|
'$title',
|
||||||
encodeURIComponent(text),
|
encodeURIComponent(text),
|
||||||
@@ -1180,9 +1185,6 @@ function webhookNotify(text, desp) {
|
|||||||
resolve(resp.body);
|
resolve(resp.body);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -852,7 +852,9 @@ def custom_notify(title: str, content: str) -> None:
|
|||||||
body = parse_body(
|
body = parse_body(
|
||||||
WEBHOOK_BODY,
|
WEBHOOK_BODY,
|
||||||
WEBHOOK_CONTENT_TYPE,
|
WEBHOOK_CONTENT_TYPE,
|
||||||
lambda v: v.replace("$title", title).replace("$content", content),
|
lambda v: v.replace("$title", title.replace("\n", "\\n")).replace(
|
||||||
|
"$content", content.replace("\n", "\\n")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
formatted_url = WEBHOOK_URL.replace(
|
formatted_url = WEBHOOK_URL.replace(
|
||||||
"$title", urllib.parse.quote_plus(title)
|
"$title", urllib.parse.quote_plus(title)
|
||||||
|
|||||||
+16
-8
@@ -216,12 +216,12 @@ check_file() {
|
|||||||
if [[ -f $file_env ]]; then
|
if [[ -f $file_env ]]; then
|
||||||
get_env_array
|
get_env_array
|
||||||
if [[ $isJsOrPythonFile == 'true' ]]; then
|
if [[ $isJsOrPythonFile == 'true' ]]; then
|
||||||
PREV_NODE_OPTIONS="${NODE_OPTIONS:=}"
|
export PREV_NODE_OPTIONS="${NODE_OPTIONS:=}"
|
||||||
PREV_PYTHONPATH="${PYTHONPATH:=}"
|
export PREV_PYTHONPATH="${PYTHONPATH:=}"
|
||||||
if [[ $1 == *.js ]] || [[ $1 == *.ts ]] || [[ $1 == *.mjs ]]; then
|
if [[ $1 == *.js ]] || [[ $1 == *.ts ]] || [[ $1 == *.mjs ]]; then
|
||||||
export NODE_OPTIONS="${NODE_OPTIONS} -r ${file_preload_js}"
|
export NODE_OPTIONS="-r ${file_preload_js} ${NODE_OPTIONS}"
|
||||||
else
|
else
|
||||||
export PYTHONPATH="${PYTHONPATH}:${dir_preload}:${dir_config}"
|
export PYTHONPATH="${dir_preload}:${dir_config}:${PYTHONPATH}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
. $file_env
|
. $file_env
|
||||||
@@ -229,6 +229,17 @@ check_file() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_nounset() {
|
||||||
|
local output=$(set -o)
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ "nounset" ]] && [[ "$line" =~ "on" ]]; then
|
||||||
|
set_u_on="true"
|
||||||
|
set +u
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done <<<"$output"
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
||||||
if [[ $1 == *.sh ]]; then
|
if [[ $1 == *.sh ]]; then
|
||||||
@@ -270,10 +281,7 @@ if [[ $isJsOrPythonFile == 'false' ]]; then
|
|||||||
run_task_before "${task_shell_params[@]}"
|
run_task_before "${task_shell_params[@]}"
|
||||||
fi
|
fi
|
||||||
set_u_on="false"
|
set_u_on="false"
|
||||||
if set -o | grep -q 'nounset.*on'; then
|
check_nounset
|
||||||
set_u_on="true"
|
|
||||||
set +u
|
|
||||||
fi
|
|
||||||
main "${task_shell_params[@]}"
|
main "${task_shell_params[@]}"
|
||||||
if [[ "$set_u_on" == 'true' ]]; then
|
if [[ "$set_u_on" == 'true' ]]; then
|
||||||
set -u
|
set -u
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
require(`./env.js`);
|
require(`./env.js`);
|
||||||
|
|
||||||
function expandRange(rangeStr, max) {
|
function expandRange(rangeStr, max) {
|
||||||
const tempRangeStr = rangeStr
|
const tempRangeStr = rangeStr
|
||||||
.trim()
|
.trim()
|
||||||
@@ -28,11 +29,12 @@ function run() {
|
|||||||
file_task_before_js,
|
file_task_before_js,
|
||||||
dir_scripts,
|
dir_scripts,
|
||||||
task_before,
|
task_before,
|
||||||
|
PREV_NODE_OPTIONS
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
require(file_task_before_js);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
process.env.NODE_OPTIONS = PREV_NODE_OPTIONS;
|
||||||
|
|
||||||
const splitStr = '__sitecustomize__';
|
const splitStr = '__sitecustomize__';
|
||||||
const fileName = process.argv[1].replace(`${dir_scripts}/`, '');
|
const fileName = process.argv[1].replace(`${dir_scripts}/`, '');
|
||||||
let command = `bash -c "source ${file_task_before} ${fileName}`;
|
let command = `bash -c "source ${file_task_before} ${fileName}`;
|
||||||
@@ -44,7 +46,7 @@ function run() {
|
|||||||
console.log('执行前置命令\n');
|
console.log('执行前置命令\n');
|
||||||
}
|
}
|
||||||
const res = execSync(
|
const res = execSync(
|
||||||
`${command} && echo -e '${splitStr}' && NODE_OPTIONS= node -p 'JSON.stringify(process.env)'"`,
|
`${command} && echo -e '${splitStr}' && node -p 'JSON.stringify(process.env)'"`,
|
||||||
{
|
{
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
},
|
},
|
||||||
@@ -60,10 +62,19 @@ function run() {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!error.message.includes('spawnSync /bin/sh E2BIG')) {
|
if (!error.message.includes('spawnSync /bin/sh E2BIG')) {
|
||||||
console.log(`run task before error: `, error);
|
console.log(`❌ run task before error: `, error);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`❌ The environment variable is too large. It is recommended to use task_before.js instead of task_before.sh\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (task_before) {
|
||||||
|
console.log('执行前置命令结束\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require(file_task_before_js);
|
||||||
|
|
||||||
if (envParam && numParam) {
|
if (envParam && numParam) {
|
||||||
const array = (process.env[envParam] || '').split('&');
|
const array = (process.env[envParam] || '').split('&');
|
||||||
const runArr = expandRange(numParam, array.length);
|
const runArr = expandRange(numParam, array.length);
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ def expand_range(range_str, max_value):
|
|||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
import task_before
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
prev_pythonpath = os.getenv("PREV_PYTHONPATH", "")
|
||||||
|
os.environ["PYTHONPATH"] = prev_pythonpath
|
||||||
|
|
||||||
split_str = "__sitecustomize__"
|
split_str = "__sitecustomize__"
|
||||||
file_name = sys.argv[0].replace(f"{os.getenv('dir_scripts')}/", "")
|
file_name = sys.argv[0].replace(f"{os.getenv('dir_scripts')}/", "")
|
||||||
command = f'bash -c "source {os.getenv("file_task_before")} {file_name}'
|
command = f'bash -c "source {os.getenv("file_task_before")} {file_name}'
|
||||||
@@ -48,7 +49,10 @@ def run():
|
|||||||
command += f" && eval '{escape_task_before}'"
|
command += f" && eval '{escape_task_before}'"
|
||||||
print("执行前置命令\n")
|
print("执行前置命令\n")
|
||||||
|
|
||||||
python_command = "PYTHONPATH= python3 -c 'import os, json; print(json.dumps(dict(os.environ)))'"
|
prev_pythonpath = os.getenv("PREV_PYTHONPATH", "")
|
||||||
|
python_command = (
|
||||||
|
"python3 -c 'import os, json; print(json.dumps(dict(os.environ)))'"
|
||||||
|
)
|
||||||
command += f" && echo -e '{split_str}' && {python_command}\""
|
command += f" && echo -e '{split_str}' && {python_command}\""
|
||||||
|
|
||||||
res = subprocess.check_output(command, shell=True, encoding="utf-8")
|
res = subprocess.check_output(command, shell=True, encoding="utf-8")
|
||||||
@@ -68,10 +72,18 @@ def run():
|
|||||||
except OSError as error:
|
except OSError as error:
|
||||||
error_message = str(error)
|
error_message = str(error)
|
||||||
if "Argument list too long" not in error_message:
|
if "Argument list too long" not in error_message:
|
||||||
print(f"run task before error: {error}")
|
print(f"❌ run task before error: {error}")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"❌ The environment variable is too large. It is recommended to use task_before.py instead of task_before.sh\n"
|
||||||
|
)
|
||||||
|
if task_before:
|
||||||
|
print("执行前置命令结束")
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
print(f"run task before error: {error}")
|
print(f"run task before error: {error}")
|
||||||
|
|
||||||
|
import task_before
|
||||||
|
|
||||||
env_param = os.getenv("envParam")
|
env_param = os.getenv("envParam")
|
||||||
num_param = os.getenv("numParam")
|
num_param = os.getenv("numParam")
|
||||||
|
|
||||||
|
|||||||
@@ -856,6 +856,10 @@ const Crontab = () => {
|
|||||||
icon: <SettingOutlined />,
|
icon: <SettingOutlined />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
style: {
|
||||||
|
maxHeight: 350,
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCronViews = () => {
|
const getCronViews = () => {
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ const Script = () => {
|
|||||||
};
|
};
|
||||||
const item = findNode(_data, (c) => c.key === obj.node.key);
|
const item = findNode(_data, (c) => c.key === obj.node.key);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
obj.node = item;
|
||||||
setExpandedKeys([p as string]);
|
setExpandedKeys([p as string]);
|
||||||
onTreeSelect([vkey], obj);
|
onTreeSelect([vkey], obj);
|
||||||
}
|
}
|
||||||
@@ -643,7 +644,7 @@ const Script = () => {
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
glyphMargin: false,
|
glyphMargin: false,
|
||||||
accessibilitySupport: 'off'
|
accessibilitySupport: 'off',
|
||||||
}}
|
}}
|
||||||
onMount={(editor) => {
|
onMount={(editor) => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
|||||||
+3
-2
@@ -313,8 +313,9 @@ export function getCommandScript(
|
|||||||
['.js', '.ts', '.sh', '.py'].some((y) => x.endsWith(y)),
|
['.js', '.ts', '.sh', '.py'].some((y) => x.endsWith(y)),
|
||||||
);
|
);
|
||||||
if (!scriptsPart) return;
|
if (!scriptsPart) return;
|
||||||
if (scriptsPart.startsWith('/ql/data/scripts')) {
|
const scriptDir = `${window.__ENV__QL_DIR}/data/scripts`;
|
||||||
scriptsPart = scriptsPart.replace('/ql/data/scripts/', '');
|
if (scriptsPart.startsWith(scriptDir)) {
|
||||||
|
scriptsPart = scriptsPart.replace(scriptDir, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
let p: string, s: string;
|
let p: string, s: string;
|
||||||
|
|||||||
Vendored
+1
@@ -12,4 +12,5 @@ declare module '*.svg' {
|
|||||||
interface Window {
|
interface Window {
|
||||||
__ENV__QlBaseUrl: string;
|
__ENV__QlBaseUrl: string;
|
||||||
__ENV__DeployEnv: string;
|
__ENV__DeployEnv: string;
|
||||||
|
__ENV__QL_DIR: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-11
@@ -1,12 +1,8 @@
|
|||||||
version: 2.17.10
|
version: 2.17.11
|
||||||
changeLogLink: https://t.me/jiao_long/421
|
changeLogLink: https://t.me/jiao_long/422
|
||||||
publishTime: 2024-08-30 23:00
|
publishTime: 2024-09-13 23:00
|
||||||
changeLog: |
|
changeLog: |
|
||||||
1. 系统日志增加时间筛选和清空操作
|
1. 修复无法获取设置的环境变量 PYTHONPATH 和 NODE_OPTIONS
|
||||||
2. 单个任务运行中实例限制最多 5 个,防止任务堆积过多,运行实例超过 5 个时,会发频繁通知
|
2. 修复定时任务视图过多时,无法看到视图管理
|
||||||
3. 修复 shell 任务无法正常结束
|
3. 修复自定义通知 json 解析
|
||||||
4. 修改 python 任务 smtp 参数说明,详见 config.sh
|
4. 修复可能产生目录遍历攻击 API
|
||||||
5. 修复任务执行前命令 (task_before) 执行报错
|
|
||||||
6. 修复删除日志命令可能失败
|
|
||||||
7. 其他样式修复
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user