mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 03:10:48 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dd8d9df91 | ||
|
|
2dbee94e85 | ||
|
|
9001797037 | ||
|
|
5e7104d5a3 | ||
|
|
7c33b22d63 | ||
|
|
b7cb1e379c | ||
|
|
274a6ce52e | ||
|
|
8079e4e7b8 | ||
|
|
920efb8fab | ||
|
|
92aeb2b9da | ||
|
|
ced76cf343 | ||
|
|
3750d6ecd5 | ||
|
|
7444f30384 | ||
|
|
b0fd5ba47f | ||
|
|
cc098bc2e5 |
-92
@@ -1,92 +0,0 @@
|
|||||||
# 安装说明
|
|
||||||
|
|
||||||
## 前置要求
|
|
||||||
```bash
|
|
||||||
1.已安装docker-ce
|
|
||||||
2.选装docker-compose
|
|
||||||
```
|
|
||||||
## 安装方式1
|
|
||||||
```bash
|
|
||||||
1. 新建一个文件夹,用于存放相关数据
|
|
||||||
2. 下载本仓库中的`docker-compose.yml`至本地,或是复制文件内容后在本地自行建立并粘贴内容
|
|
||||||
3. 使用docker-compose启动
|
|
||||||
4. 浏览器输入ip:5700即可进入面板
|
|
||||||
|
|
||||||
# 新建数据文件夹
|
|
||||||
mkdir qinglong
|
|
||||||
cd qinglong
|
|
||||||
# 下载docker-compose.yml文件
|
|
||||||
wget https://raw.githubusercontent.com/whyour/qinglong/develop/docker-compose.yml
|
|
||||||
# 启动
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安装方式2
|
|
||||||
```bash
|
|
||||||
# 复制下列命令在ssh执行(先安装docker)
|
|
||||||
# 注:$PWD请修改为实际你想安装的路径
|
|
||||||
docker run -dit \
|
|
||||||
-v $PWD/ql/config:/ql/config \
|
|
||||||
-v $PWD/ql/log:/ql/log \
|
|
||||||
-v $PWD/ql/db:/ql/db \
|
|
||||||
-v $PWD/ql/repo:/ql/repo \
|
|
||||||
-v $PWD/ql/raw:/ql/raw \
|
|
||||||
-v $PWD/ql/scripts:/ql/scripts \
|
|
||||||
-v $PWD/ql/jbot:/ql/jbot \
|
|
||||||
-p 5700:5700 \
|
|
||||||
--name qinglong \
|
|
||||||
--hostname qinglong \
|
|
||||||
--restart unless-stopped \
|
|
||||||
whyour/qinglong:latest
|
|
||||||
```
|
|
||||||
## 登录
|
|
||||||
```bash
|
|
||||||
|
|
||||||
打开浏览器访问宿主机ip的5700端口即可
|
|
||||||
例如http://192.168.100.123:5700即ip:5700
|
|
||||||
|
|
||||||
首次登录
|
|
||||||
账号:admin 密码:admin
|
|
||||||
会生成`auth.json`
|
|
||||||
|
|
||||||
在ssh输入
|
|
||||||
1.docker exec -it qinglong bash
|
|
||||||
2.cat /ql/config/auth.json
|
|
||||||
|
|
||||||
cat查看之后返回的结果类似如下字段
|
|
||||||
|
|
||||||
{"username":"admin","password":"Xb-ZYP526wmg4_h6q1WqIO"}
|
|
||||||
# admin即为登录名;Xb-ZYP526wmg4_h6q1WqIO为登录密码
|
|
||||||
```
|
|
||||||
|
|
||||||
输入此处记录的`username`及`password`,即可成功登录qinglong面板,登录后即可正常使用
|
|
||||||
|
|
||||||
## 拉取脚本
|
|
||||||
```bash
|
|
||||||
示例
|
|
||||||
ql repo https://github.com/xxx.git #拉取仓库
|
|
||||||
ql raw https://raw.githubusercontent.com/xxx #拉取单个脚本
|
|
||||||
```
|
|
||||||
## 备份
|
|
||||||
|
|
||||||
所有数据都将保存在`docker-compose.yml`所在的同级目录的`data`文件夹中,如需要备份,请直接备份`docker-compose.yml`及`data`文件夹即可
|
|
||||||
|
|
||||||
```bash
|
|
||||||
root@debian:/opt/qinglong# ls -lah
|
|
||||||
总用量 8.0K
|
|
||||||
drwxr-xr-x 3 root root 4.0K 8月 30 01:29 .
|
|
||||||
drwxr-xr-x 4 root root 4.0K 8月 30 00:51 ..
|
|
||||||
drwxr-xr-x 8 root root 4.0K 8月 30 01:30 data
|
|
||||||
-rw-r--r-- 1 root root 386 8月 30 01:29 docker-compose.yml
|
|
||||||
```
|
|
||||||
## 更新
|
|
||||||
|
|
||||||
在面板执行"更新面板"任务即可
|
|
||||||
|
|
||||||
或者
|
|
||||||
```bash
|
|
||||||
cd qinglong
|
|
||||||
docker-compose down
|
|
||||||
docker pull whyour/qinglong:latest
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/whyour/qinglong">
|
<a href="https://github.com/whyour/qinglong">
|
||||||
<img width="150" src="/public/images/qinglong.png">
|
<img width="150" src="https://z3.ax1x.com/2021/11/18/I7MpAe.png">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -8,12 +8,10 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
A timed task management panel that supports typescript, javaScript, python3, and shell.(支持python3、javaScript、shell、typescript 的定时任务管理面板)
|
支持python3、javaScript、shell、typescript 的定时任务管理面板(A timed task management panel that supports typescript, javaScript, python3, and shell.)
|
||||||
|
|
||||||
[![docker version][docker-version-image]][docker-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url] [![donate][donate-image]][donate-url]
|
[![docker version][docker-version-image]][docker-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
|
||||||
|
|
||||||
[donate-image]: https://img.shields.io/badge/donate-wechat-green?style=flat
|
|
||||||
[donate-url]: https://qinglong.whyour.cn/nice.png
|
|
||||||
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
|
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
|
||||||
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
[docker-version-image]: https://img.shields.io/docker/v/whyour/qinglong?style=flat
|
[docker-version-image]: https://img.shields.io/docker/v/whyour/qinglong?style=flat
|
||||||
@@ -24,31 +22,151 @@ A timed task management panel that supports typescript, javaScript, python3, and
|
|||||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
[](https://whyour.cn)
|
||||||
<img width="49%" src="/public/images/login.png">
|
|
||||||
<img width="49%" src="/public/images/home.png">
|
简体中文 | [English](./README-en.md)
|
||||||
</p>
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 支持多种脚本语言(python3、javaScript、shell、typescript)
|
||||||
|
- 支持在线管理脚本、环境变量、配置文件
|
||||||
|
- 支持在线查看任务日志
|
||||||
|
- 支持秒级任务设置
|
||||||
|
- 支持系统级通知
|
||||||
|
- 支持暗黑模式
|
||||||
|
- 支持手机端操作
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
### 本机部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 待完善
|
||||||
|
```
|
||||||
|
|
||||||
|
### docker 部署
|
||||||
|
|
||||||
|
1. docker 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo curl -sSL get.docker.com | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动容器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -dit \
|
||||||
|
-v $PWD/ql/config:/ql/config \
|
||||||
|
-v $PWD/ql/log:/ql/log \
|
||||||
|
-v $PWD/ql/db:/ql/db \
|
||||||
|
-v $PWD/ql/repo:/ql/repo \
|
||||||
|
-v $PWD/ql/raw:/ql/raw \
|
||||||
|
-v $PWD/ql/scripts:/ql/scripts \
|
||||||
|
-p 5700:5700 \
|
||||||
|
--name qinglong \
|
||||||
|
--hostname qinglong \
|
||||||
|
--restart unless-stopped \
|
||||||
|
whyour/qinglong:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### docker-compose 部署
|
||||||
|
|
||||||
|
1. docker-compose 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动容器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir qinglong
|
||||||
|
wget https://raw.githubusercontent.com/whyour/qinglong/master/docker-compose.yml
|
||||||
|
|
||||||
|
# 启动
|
||||||
|
docker-compose up -d
|
||||||
|
# 停止
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
1. 内置命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 更新并重启青龙
|
||||||
|
ql update
|
||||||
|
# 运行自定义脚本extra.sh
|
||||||
|
ql extra
|
||||||
|
# 添加单个脚本文件
|
||||||
|
ql raw <file_url>
|
||||||
|
# 添加单个仓库的指定脚本
|
||||||
|
ql repo <repo_url> <whitelist> <blacklist> <dependence> <branch>
|
||||||
|
# 删除旧日志
|
||||||
|
ql rmlog <days>
|
||||||
|
# 启动tg-bot
|
||||||
|
ql bot
|
||||||
|
# 检测青龙环境并修复
|
||||||
|
ql check
|
||||||
|
# 重置登录错误次数
|
||||||
|
ql resetlet
|
||||||
|
# 禁用两步登录
|
||||||
|
ql resettfa
|
||||||
|
|
||||||
|
# 依次执行,如果设置了随机延迟,将随机延迟一定秒数
|
||||||
|
task <file_path>
|
||||||
|
# 依次执行,无论是否设置了随机延迟,均立即运行,前台会输出日,同时记录在日志文件中
|
||||||
|
task <file_path> now
|
||||||
|
# 并发执行,无论是否设置了随机延迟,均立即运行,前台不产生日,直接记录在日志文件中,且可指定账号执行
|
||||||
|
task <file_path> conc <env_name> <account_number>(可选的)
|
||||||
|
# 指定账号执行,无论是否设置了随机延迟,均立即运行
|
||||||
|
task <file_path> desi <env_name> <account_number>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 参数说明
|
||||||
|
|
||||||
|
* file_url: 脚本地址
|
||||||
|
* repo_url: 仓库地址
|
||||||
|
* whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串
|
||||||
|
* blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串
|
||||||
|
* dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响
|
||||||
|
* branch: 拉取仓库的分支
|
||||||
|
* days: 需要保留的日志的天数
|
||||||
|
* file_path: 任务执行时的文件路径
|
||||||
|
* env_name: 任务执行时需要并发或者指定时的环境变量名称
|
||||||
|
* account_number: 任务执行时指定某个环境变量需要执行的账号序号
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
- [nevinee](https://gitee.com/evine)
|
||||||
|
- [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)
|
||||||
|
- [darkreader](https://github.com/darkreader/darkreader)
|
||||||
|
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone git@github.com:whyour/qinglong.git
|
||||||
|
$ cd qinglong
|
||||||
|
$ cp .env.example .env
|
||||||
|
$ yarn install
|
||||||
|
$ yarn start-back
|
||||||
|
$ yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
打开你的浏览器,访问 http://127.0.0.1:5601
|
||||||
|
|
||||||
|
## 交流
|
||||||
|
|
||||||
|
[telegram频道](https://t.me/jiao_long)
|
||||||
|
|
||||||
|
## 名称来源
|
||||||
|
|
||||||
青龙,又名苍龙,在中国传统文化中是四象之一、[天之四灵](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5)之一,根据五行学说,它是代表东方的灵兽,为青色的龙,五行属木,代表的季节是春季,八卦主震。苍龙与应龙一样,都是身具羽翼。《张果星宗》称“又有辅翼,方为真龙”。
|
青龙,又名苍龙,在中国传统文化中是四象之一、[天之四灵](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5)之一,根据五行学说,它是代表东方的灵兽,为青色的龙,五行属木,代表的季节是春季,八卦主震。苍龙与应龙一样,都是身具羽翼。《张果星宗》称“又有辅翼,方为真龙”。
|
||||||
|
|
||||||
《后汉书·律历志下》记载:日周于天,一寒一暑,四时备成,万物毕改,摄提迁次,青龙移辰,谓之岁。
|
《后汉书·律历志下》记载:日周于天,一寒一暑,四时备成,万物毕改,摄提迁次,青龙移辰,谓之岁。
|
||||||
|
|
||||||
在中国[二十八宿](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%85%AB%E5%AE%BF)中,青龙是东方七宿(角、亢、氐、房、心、尾、箕)的总称。 在早期星宿信仰中,祂是最尊贵的天神。 但被道教信仰吸纳入其神系后,神格大跌,道教将其称为“孟章”,在不同的道经中有“帝君”、“圣将”、“神将”和“捕鬼将”等称呼,与白虎监兵神君一起,是道教的护卫天神。
|
在中国[二十八宿](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%85%AB%E5%AE%BF)中,青龙是东方七宿(角、亢、氐、房、心、尾、箕)的总称。 在早期星宿信仰中,祂是最尊贵的天神。 但被道教信仰吸纳入其神系后,神格大跌,道教将其称为“孟章”,在不同的道经中有“帝君”、“圣将”、“神将”和“捕鬼将”等称呼,与白虎监兵神君一起,是道教的护卫天神。
|
||||||
|
|
||||||
## 如何安装
|
|
||||||
|
|
||||||
请查看[INSTALL.md](INSTALL.md)
|
|
||||||
|
|
||||||
## 多谢
|
|
||||||
|
|
||||||
* [nevinee](https://gitee.com/evine)
|
|
||||||
|
|
||||||
* [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)
|
|
||||||
|
|
||||||
* [darkreader](https://github.com/darkreader/darkreader)
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import config from '../config';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import ScriptService from '../services/script';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
@@ -237,4 +238,30 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/scripts/run',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
filename: Joi.string().required(),
|
||||||
|
path: Joi.string().optional().allow(''),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
let { filename, path } = req.body as {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
const filePath = `${path}/${filename}`;
|
||||||
|
const scriptService = Container.get(ScriptService);
|
||||||
|
const result = await scriptService.runScript(filePath);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import Logger from './loaders/logger';
|
|||||||
async function startServer() {
|
async function startServer() {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
await require('./loaders/initFile').default();
|
||||||
|
|
||||||
await require('./loaders/sentry').default({ expressApp: app });
|
await require('./loaders/sentry').default({ expressApp: app });
|
||||||
|
|
||||||
await require('./loaders/db').default();
|
await require('./loaders/db').default();
|
||||||
|
|||||||
@@ -32,16 +32,10 @@ const authDbFile = path.join(rootPath, 'db/auth.db');
|
|||||||
const dependenceDbFile = path.join(rootPath, 'db/dependence.db');
|
const dependenceDbFile = path.join(rootPath, 'db/dependence.db');
|
||||||
const versionFile = path.join(rootPath, 'src/version.ts');
|
const versionFile = path.join(rootPath, 'src/version.ts');
|
||||||
|
|
||||||
const configFound = dotenv.config({ path: confFile });
|
|
||||||
|
|
||||||
if (envFound.error) {
|
if (envFound.error) {
|
||||||
throw new Error("⚠️ Couldn't find .env file ⚠️");
|
throw new Error("⚠️ Couldn't find .env file ⚠️");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configFound.error) {
|
|
||||||
throw new Error("⚠️ Couldn't find config.sh file ⚠️");
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
port: parseInt(process.env.PORT as string, 10),
|
port: parseInt(process.env.PORT as string, 10),
|
||||||
cronPort: parseInt(process.env.CRON_PORT as string, 10),
|
cronPort: parseInt(process.env.CRON_PORT as string, 10),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum NotificationMode {
|
export enum NotificationMode {
|
||||||
|
'gotify' = 'gotify',
|
||||||
'goCqHttpBot' = 'goCqHttpBot',
|
'goCqHttpBot' = 'goCqHttpBot',
|
||||||
'serverChan' = 'serverChan',
|
'serverChan' = 'serverChan',
|
||||||
'bark' = 'bark',
|
'bark' = 'bark',
|
||||||
@@ -15,6 +16,12 @@ abstract class NotificationBaseInfo {
|
|||||||
public type!: NotificationMode;
|
public type!: NotificationMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GotifyNotification extends NotificationBaseInfo {
|
||||||
|
public gotifyUrl = '';
|
||||||
|
public gotifyToken = '';
|
||||||
|
public gotifyPriority = 0;
|
||||||
|
}
|
||||||
|
|
||||||
export class GoCqHttpBotNotification extends NotificationBaseInfo {
|
export class GoCqHttpBotNotification extends NotificationBaseInfo {
|
||||||
public goCqHttpBotUrl = '';
|
public goCqHttpBotUrl = '';
|
||||||
public goCqHttpBotToken = '';
|
public goCqHttpBotToken = '';
|
||||||
@@ -70,6 +77,7 @@ export class EmailNotification extends NotificationBaseInfo {
|
|||||||
|
|
||||||
export interface NotificationInfo
|
export interface NotificationInfo
|
||||||
extends GoCqHttpBotNotification,
|
extends GoCqHttpBotNotification,
|
||||||
|
GotifyNotification,
|
||||||
ServerChanNotification,
|
ServerChanNotification,
|
||||||
BarkNotification,
|
BarkNotification,
|
||||||
TelegramBotNotification,
|
TelegramBotNotification,
|
||||||
|
|||||||
+2
-1
@@ -13,4 +13,5 @@ export class SockMessage {
|
|||||||
export type SockMessageType =
|
export type SockMessageType =
|
||||||
| 'ping'
|
| 'ping'
|
||||||
| 'installDependence'
|
| 'installDependence'
|
||||||
| 'updateSystemVersion';
|
| 'updateSystemVersion'
|
||||||
|
| 'manuallyRunScript';
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import Logger from './logger';
|
||||||
|
import { fileExist } from '../config/util';
|
||||||
|
|
||||||
|
const rootPath = process.cwd();
|
||||||
|
const confFile = path.join(rootPath, 'config/config.sh');
|
||||||
|
const sampleConfigFile = path.join(rootPath, 'sample/config.sample.sh');
|
||||||
|
const sampleAuthFile = path.join(rootPath, 'sample/auth.sample.json');
|
||||||
|
const authConfigFile = path.join(rootPath, 'config/auth.json');
|
||||||
|
const configPath = path.join(rootPath, 'config/');
|
||||||
|
const scriptPath = path.join(rootPath, 'scripts/');
|
||||||
|
const logPath = path.join(rootPath, 'log/');
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
const authFileExist = await fileExist(authConfigFile);
|
||||||
|
const confFileExist = await fileExist(confFile);
|
||||||
|
const scriptDirExist = await fileExist(scriptPath);
|
||||||
|
const logDirExist = await fileExist(logPath);
|
||||||
|
const configDirExist = await fileExist(configPath);
|
||||||
|
|
||||||
|
if (!configDirExist) {
|
||||||
|
fs.mkdirSync(configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authFileExist) {
|
||||||
|
fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confFileExist) {
|
||||||
|
fs.writeFileSync(confFile, fs.readFileSync(sampleConfigFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scriptDirExist) {
|
||||||
|
fs.mkdirSync(scriptPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!logDirExist) {
|
||||||
|
fs.mkdirSync(logPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
dotenv.config({ path: confFile });
|
||||||
|
|
||||||
|
Logger.info('✌️ Init file down');
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ export default class NotificationService {
|
|||||||
private userService!: UserService;
|
private userService!: UserService;
|
||||||
|
|
||||||
private modeMap = new Map([
|
private modeMap = new Map([
|
||||||
|
['gotify', this.gotify],
|
||||||
['goCqHttpBot', this.goCqHttpBot],
|
['goCqHttpBot', this.goCqHttpBot],
|
||||||
['serverChan', this.serverChan],
|
['serverChan', this.serverChan],
|
||||||
['bark', this.bark],
|
['bark', this.bark],
|
||||||
@@ -67,6 +68,25 @@ export default class NotificationService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async gotify() {
|
||||||
|
const { gotifyUrl, gotifyToken, gotifyPriority } = this.params;
|
||||||
|
const res: any = await got
|
||||||
|
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
|
||||||
|
timeout: this.timeout,
|
||||||
|
retry: 0,
|
||||||
|
body: `title=${encodeURIComponent(
|
||||||
|
this.title,
|
||||||
|
)}&message=${encodeURIComponent(
|
||||||
|
this.content,
|
||||||
|
)}&priority=${gotifyPriority}`,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
return typeof res.id === 'number';
|
||||||
|
}
|
||||||
|
|
||||||
private async goCqHttpBot() {
|
private async goCqHttpBot() {
|
||||||
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import SockService from './sock';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ScriptService {
|
||||||
|
constructor(
|
||||||
|
@Inject('logger') private logger: winston.Logger,
|
||||||
|
private sockService: SockService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async runScript(path: string) {
|
||||||
|
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message: `开始执行脚本`,
|
||||||
|
});
|
||||||
|
cp.stdout.on('data', (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message: data.toString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.stderr.on('data', (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message: data.toString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('error', (err) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message: JSON.stringify(err),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { code: 200 };
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-4
@@ -8,9 +8,7 @@ services:
|
|||||||
- ./data/db:/ql/db
|
- ./data/db:/ql/db
|
||||||
- ./data/scripts:/ql/scripts
|
- ./data/scripts:/ql/scripts
|
||||||
- ./data/repo:/ql/repo
|
- ./data/repo:/ql/repo
|
||||||
|
- ./data/raw:/ql/raw
|
||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:5700:5700"
|
- "0.0.0.0:5700:5700"
|
||||||
environment:
|
restart: unless-stopped
|
||||||
- ENABLE_HANGUP=true
|
|
||||||
- ENABLE_WEB_PANEL=true
|
|
||||||
restart: always
|
|
||||||
|
|||||||
+1
-3
@@ -1,4 +1,4 @@
|
|||||||
FROM node:lts-alpine
|
FROM node:lts-alpine3.12
|
||||||
ARG QL_MAINTAINER="whyour"
|
ARG QL_MAINTAINER="whyour"
|
||||||
LABEL maintainer="${QL_MAINTAINER}"
|
LABEL maintainer="${QL_MAINTAINER}"
|
||||||
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
||||||
@@ -8,8 +8,6 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
|||||||
SHELL=/bin/bash \
|
SHELL=/bin/bash \
|
||||||
PS1="\u@\h:\w \$ " \
|
PS1="\u@\h:\w \$ " \
|
||||||
QL_DIR=/ql \
|
QL_DIR=/ql \
|
||||||
QL_MAINTAINER=${QL_MAINTAINER} \
|
|
||||||
QL_URL=${QL_URL} \
|
|
||||||
QL_BRANCH=${QL_BRANCH}
|
QL_BRANCH=${QL_BRANCH}
|
||||||
WORKDIR ${QL_DIR}
|
WORKDIR ${QL_DIR}
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 178 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 172 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 115 KiB |
@@ -112,4 +112,12 @@ export GOBOT_URL=""
|
|||||||
export GOBOT_TOKEN=""
|
export GOBOT_TOKEN=""
|
||||||
export GOBOT_QQ=""
|
export GOBOT_QQ=""
|
||||||
|
|
||||||
|
## 10. gotify
|
||||||
|
## gotify_url 填写gotify地址,如https://push.example.de:8080
|
||||||
|
## gotify_token 填写gotify的消息应用token
|
||||||
|
## gotify_priority 填写推送消息优先级,默认为0
|
||||||
|
export GOTIFY_URL="";
|
||||||
|
export GOTIFY_TOKEN="";
|
||||||
|
export GOTIFY_PRIORITY=0;
|
||||||
|
|
||||||
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
||||||
|
|||||||
+1
-1
@@ -400,7 +400,7 @@ gen_list_repo() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -d $dir_dep ]]; then
|
if [[ -d $dir_dep ]]; then
|
||||||
cp $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null
|
cp -rf $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for file in ${files}; do
|
for file in ${files}; do
|
||||||
|
|||||||
@@ -79,6 +79,6 @@ export default {
|
|||||||
fixSiderbar: true,
|
fixSiderbar: true,
|
||||||
contentWidth: 'Fixed',
|
contentWidth: 'Fixed',
|
||||||
splitMenus: false,
|
splitMenus: false,
|
||||||
logo: '/images/qinglong.png',
|
logo: 'https://z3.ax1x.com/2021/11/18/I7MpAe.png',
|
||||||
siderWidth: 180,
|
siderWidth: 180,
|
||||||
} as any;
|
} as any;
|
||||||
|
|||||||
@@ -235,6 +235,7 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
transition: background-color 5000s ease-in-out 0s;
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
-webkit-text-fill-color: @text-color;
|
-webkit-text-fill-color: @text-color;
|
||||||
|
caret-color: #e8e6f3 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
.ant-table-pagination.ant-pagination {
|
.ant-table-pagination.ant-pagination {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
td:nth-child(2) {
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import CronLogModal from './logModal';
|
|||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
import { diffTime } from '@/utils/date';
|
import { diffTime } from '@/utils/date';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { getTableScroll } from '@/utils/index';
|
||||||
|
import { history } from 'umi';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@@ -76,7 +77,11 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
width: 150,
|
width: 150,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render: (text: string, record: any) => (
|
render: (text: string, record: any) => (
|
||||||
<span>
|
<span
|
||||||
|
onClick={() => {
|
||||||
|
goToScriptManager(record);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{record.name || record._id}{' '}
|
{record.name || record._id}{' '}
|
||||||
{record.isPinned ? (
|
{record.isPinned ? (
|
||||||
<span>
|
<span>
|
||||||
@@ -329,6 +334,20 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
const [pageSize, setPageSize] = useState(20);
|
const [pageSize, setPageSize] = useState(20);
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||||
|
|
||||||
|
const goToScriptManager = (record: any) => {
|
||||||
|
const cmd = record.command.split(' ');
|
||||||
|
if (cmd[0] === 'task') {
|
||||||
|
let [p, s] = cmd[1].split('/');
|
||||||
|
if (!s) {
|
||||||
|
s = p;
|
||||||
|
p = '';
|
||||||
|
}
|
||||||
|
history.push(`/script?p=${p}&s=${s}`);
|
||||||
|
} else if (cmd[1] === 'repo') {
|
||||||
|
location.href = cmd[2];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getCrons = () => {
|
const getCrons = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
@@ -847,6 +866,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
defaultPageSize: 20,
|
defaultPageSize: 20,
|
||||||
showTotal: (total: number, range: number[]) =>
|
showTotal: (total: number, range: number[]) =>
|
||||||
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100, 200, 500, 1000],
|
||||||
}}
|
}}
|
||||||
dataSource={value}
|
dataSource={value}
|
||||||
rowKey="_id"
|
rowKey="_id"
|
||||||
|
|||||||
@@ -225,7 +225,11 @@ const Initialization = () => {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.top}>
|
<div className={styles.top}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<img alt="logo" className={styles.logo} src="/images/qinglong.png" />
|
<img
|
||||||
|
alt="logo"
|
||||||
|
className={styles.logo}
|
||||||
|
src="https://z3.ax1x.com/2021/11/18/I7MpAe.png"
|
||||||
|
/>
|
||||||
<span className={styles.title}>初始化配置</span>
|
<span className={styles.title}>初始化配置</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -124,7 +124,11 @@ const Login = () => {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.top}>
|
<div className={styles.top}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<img alt="logo" className={styles.logo} src="/images/qinglong.png" />
|
<img
|
||||||
|
alt="logo"
|
||||||
|
className={styles.logo}
|
||||||
|
src="https://z3.ax1x.com/2021/11/18/I7MpAe.png"
|
||||||
|
/>
|
||||||
<span className={styles.title}>
|
<span className={styles.title}>
|
||||||
{twoFactor ? '两步验证' : config.siteName}
|
{twoFactor ? '两步验证' : config.siteName}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -28,16 +28,19 @@ const EditModal = ({
|
|||||||
content,
|
content,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
visible,
|
||||||
|
socketMessage,
|
||||||
}: {
|
}: {
|
||||||
treeData?: any;
|
treeData?: any;
|
||||||
currentFile?: string;
|
currentFile?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
socketMessage: any;
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [language, setLanguage] = useState<string>('javascript');
|
const [language, setLanguage] = useState<string>('javascript');
|
||||||
const [fileName, setFileName] = useState<string>('');
|
const [fileName, setFileName] = useState<string>('');
|
||||||
|
const [selectedKey, setSelectedKey] = useState<string>('');
|
||||||
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
||||||
const [settingModalVisible, setSettingModalVisible] =
|
const [settingModalVisible, setSettingModalVisible] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@@ -50,10 +53,14 @@ const EditModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
|
if (node.value === fileName || !value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newMode = LangMap[value.slice(-3)] || '';
|
const newMode = LangMap[value.slice(-3)] || '';
|
||||||
setFileName(value);
|
setFileName(value);
|
||||||
setLanguage(newMode);
|
setLanguage(newMode);
|
||||||
getDetail(node);
|
getDetail(node);
|
||||||
|
setSelectedKey(node.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
@@ -62,12 +69,39 @@ const EditModal = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const run = () => {};
|
const run = () => {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}scripts/run`, {
|
||||||
|
data: {
|
||||||
|
filename: fileName,
|
||||||
|
path: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!socketMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { type, message: _message, references } = socketMessage;
|
||||||
|
|
||||||
|
if (type !== 'manuallyRunScript') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
_message = `\n${_message}`;
|
||||||
|
}
|
||||||
|
setLog(`${log}${_message}`);
|
||||||
|
}, [socketMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentFile) {
|
if (currentFile) {
|
||||||
setFileName(currentFile);
|
setFileName(currentFile);
|
||||||
setValue(content as string);
|
setValue(content as string);
|
||||||
|
setSelectedKey(currentFile);
|
||||||
}
|
}
|
||||||
}, [currentFile, content]);
|
}, [currentFile, content]);
|
||||||
|
|
||||||
@@ -79,12 +113,11 @@ const EditModal = ({
|
|||||||
<span style={{ marginRight: 8 }}>{fileName}</span>
|
<span style={{ marginRight: 8 }}>{fileName}</span>
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
style={{ marginRight: 8, width: 120 }}
|
style={{ marginRight: 8, width: 120 }}
|
||||||
value={currentFile}
|
value={selectedKey}
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={treeData}
|
treeData={treeData}
|
||||||
placeholder="请选择脚本文件"
|
placeholder="请选择脚本文件"
|
||||||
showSearch
|
showSearch
|
||||||
key="value"
|
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import {
|
|||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import EditScriptNameModal from './editNameModal';
|
import EditScriptNameModal from './editNameModal';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import { history } from 'umi';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ const LangMap: any = {
|
|||||||
'.ts': 'typescript',
|
'.ts': 'typescript',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Script = ({ headerStyle, isPhone, theme }: any) => {
|
const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||||
const [title, setTitle] = useState('请选择脚本文件');
|
const [title, setTitle] = useState('请选择脚本文件');
|
||||||
const [value, setValue] = useState('请选择脚本文件');
|
const [value, setValue] = useState('请选择脚本文件');
|
||||||
const [select, setSelect] = useState<any>();
|
const [select, setSelect] = useState<any>();
|
||||||
@@ -94,6 +96,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
setData(data.data);
|
setData(data.data);
|
||||||
setFilterData(data.data);
|
setFilterData(data.data);
|
||||||
|
initGetScript();
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -106,6 +109,22 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initGetScript = () => {
|
||||||
|
const { p, s } = history.location.query as any;
|
||||||
|
if (s) {
|
||||||
|
const obj = {
|
||||||
|
node: {
|
||||||
|
title: s,
|
||||||
|
value: s,
|
||||||
|
key: p ? `${p}-${s}` : s,
|
||||||
|
parent: p,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setExpandedKeys([p]);
|
||||||
|
onTreeSelect([`${p}-${s}`], obj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
if (node.key === select || !value) {
|
if (node.key === select || !value) {
|
||||||
return;
|
return;
|
||||||
@@ -119,6 +138,10 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
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
|
||||||
@@ -147,6 +170,13 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
|
debounceSearch(keyword);
|
||||||
|
},
|
||||||
|
[data, setFilterData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const debounceSearch = useCallback(
|
||||||
|
debounce((keyword) => {
|
||||||
setSearchValue(keyword);
|
setSearchValue(keyword);
|
||||||
const { tree, expandedKeys } = getFilterData(
|
const { tree, expandedKeys } = getFilterData(
|
||||||
keyword.toLocaleLowerCase(),
|
keyword.toLocaleLowerCase(),
|
||||||
@@ -154,7 +184,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
);
|
);
|
||||||
setExpandedKeys(expandedKeys);
|
setExpandedKeys(expandedKeys);
|
||||||
setFilterData(tree);
|
setFilterData(tree);
|
||||||
},
|
}, 300),
|
||||||
[data, setFilterData],
|
[data, setFilterData],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -451,6 +481,8 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
showIcon={true}
|
showIcon={true}
|
||||||
height={height}
|
height={height}
|
||||||
selectedKeys={[select]}
|
selectedKeys={[select]}
|
||||||
|
expandedKeys={expandedKeys}
|
||||||
|
onExpand={onExpand}
|
||||||
showLine={{ showLeafIcon: true }}
|
showLine={{ showLeafIcon: true }}
|
||||||
onSelect={onTreeSelect}
|
onSelect={onTreeSelect}
|
||||||
></Tree>
|
></Tree>
|
||||||
@@ -495,6 +527,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
treeData={data}
|
treeData={data}
|
||||||
currentFile={select}
|
currentFile={select}
|
||||||
content={value}
|
content={value}
|
||||||
|
socketMessage={socketMessage}
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
setIsLogModalVisible(false);
|
setIsLogModalVisible(false);
|
||||||
}}
|
}}
|
||||||
|
|||||||
+9
-8
@@ -1,9 +1,10 @@
|
|||||||
export const version = '2.10.8';
|
export const version = '2.10.9';
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/227';
|
export const changeLogLink = 'https://t.me/jiao_long/228';
|
||||||
export const changeLog = `2.10.8 版本说明
|
export const changeLog = `2.10.9 版本说明
|
||||||
1. 脚本管理新建文件增加选择父目录
|
1. 任务管理支持任务名跳转脚本管理页,感谢 https://github.com/kilo5hz PR
|
||||||
2. 修复脚本管理更新文件
|
2. 系统通知支持gotify,感谢 https://github.com/kilo5hz PR
|
||||||
3. 增加gotify推送,感谢 https://github.com/kilo5hz PR
|
3. 定时任务列表pageSize增加200/500/1000,感谢 https://github.com/fzls PR
|
||||||
4. 修复不能复制deps目录文件
|
4. 修复deps目录依赖文件拷贝
|
||||||
5. 修复可能的玩客云问题
|
5. 修改alpine基础镜像版本,解决arm32位系统无法启动容器,感谢 https://github.com/lx200916
|
||||||
|
6. 修复调试功能
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user