mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 19:05:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddee69d291 | ||
|
|
28960c9fab | ||
|
|
449301e519 | ||
|
|
28dab3dc2f | ||
|
|
7995d98db5 | ||
|
|
1f319f3533 | ||
|
|
7d2dbaa62d | ||
|
|
ed5999526b | ||
|
|
2409f75491 | ||
|
|
0c840095d1 | ||
|
|
3eaf0b4856 | ||
|
|
394735a5d9 | ||
|
|
5bfa10bfe7 | ||
|
|
3f12d9cbd5 | ||
|
|
54d89754df | ||
|
|
9470524615 | ||
|
|
b5faa6eaaa | ||
|
|
eab476cfe8 | ||
|
|
b773def012 | ||
|
|
a9f8b23a7a | ||
|
|
61bb6535fb | ||
|
|
eb0bdffe06 | ||
|
|
e129485285 | ||
|
|
aac7de172e | ||
|
|
e19d2412fc | ||
|
|
7cba2cf76c | ||
|
|
55d7a49dbc | ||
|
|
f695864fd8 | ||
|
|
ae4883fbe7 | ||
|
|
836dfd2861 | ||
|
|
6e0523c6d7 | ||
|
|
cd9ba084ae | ||
|
|
c8a0c0de67 | ||
|
|
5dd8d9df91 | ||
|
|
2dbee94e85 | ||
|
|
9001797037 | ||
|
|
5e7104d5a3 | ||
|
|
7c33b22d63 | ||
|
|
b7cb1e379c | ||
|
|
274a6ce52e | ||
|
|
8079e4e7b8 | ||
|
|
920efb8fab | ||
|
|
92aeb2b9da | ||
|
|
ced76cf343 | ||
|
|
3750d6ecd5 | ||
|
|
7444f30384 | ||
|
|
b0fd5ba47f | ||
|
|
cc098bc2e5 | ||
|
|
1cfcf8d194 | ||
|
|
d1e9f741d0 | ||
|
|
0cde9d07ee | ||
|
|
a266425348 | ||
|
|
6dbe02e321 | ||
|
|
39979879c9 | ||
|
|
ba3f604a5c | ||
|
|
9572582401 | ||
|
|
2bcef0ba75 | ||
|
|
31cd7c4007 | ||
|
|
1c4c1799b0 | ||
|
|
2d558df15e | ||
|
|
7b769da4e2 |
@@ -105,7 +105,9 @@ jobs:
|
|||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
build-args: MAINTAINER=${{ github.repository_owner }}, QL_BRANCH=${{ github.ref_name }}
|
build-args: |
|
||||||
|
MAINTAINER=${{ github.repository_owner }}
|
||||||
|
QL_BRANCH=${{ github.ref_name }}
|
||||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x
|
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x
|
||||||
context: docker/
|
context: docker/
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default defineConfig({
|
|||||||
scripts: [
|
scripts: [
|
||||||
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
||||||
'https://gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js',
|
'https://gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js',
|
||||||
'https://cdn.jsdelivr.net/npm/darkreader@4/darkreader.min.js',
|
'https://cdn.jsdelivr.net/npm/darkreader@4.9.40/darkreader.min.js',
|
||||||
'https://cdn.jsdelivr.net/npm/codemirror@5/lib/codemirror.min.js',
|
'https://cdn.jsdelivr.net/npm/codemirror@5/lib/codemirror.min.js',
|
||||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/shell/shell.js',
|
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/shell/shell.js',
|
||||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/python/python.js',
|
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/python/python.js',
|
||||||
|
|||||||
-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)
|
|
||||||
|
|||||||
+4
-4
@@ -8,10 +8,10 @@ import { celebrate, Joi } from 'celebrate';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/configs', route);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/configs/files',
|
'/files',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -32,7 +32,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/configs/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -58,7 +58,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/configs/save',
|
'/save',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
|
|||||||
+26
-30
@@ -7,26 +7,22 @@ import cron_parser from 'cron-parser';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/crons', route);
|
||||||
route.get(
|
|
||||||
'/crons',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const cronService = Container.get(CronService);
|
||||||
const cronService = Container.get(CronService);
|
const data = await cronService.crontabs(req.query.searchValue as string);
|
||||||
const data = await cronService.crontabs(
|
return res.send({ code: 200, data });
|
||||||
req.query.searchValue as string,
|
} catch (e) {
|
||||||
);
|
logger.error('🔥 error: %o', e);
|
||||||
return res.send({ code: 200, data });
|
return next(e);
|
||||||
} catch (e) {
|
}
|
||||||
logger.error('🔥 error: %o', e);
|
});
|
||||||
return next(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/crons',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
command: Joi.string().required(),
|
command: Joi.string().required(),
|
||||||
@@ -52,7 +48,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/run',
|
'/run',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -70,7 +66,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/stop',
|
'/stop',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -88,7 +84,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/disable',
|
'/disable',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -106,7 +102,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/enable',
|
'/enable',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -124,7 +120,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/crons/:id/log',
|
'/:id/log',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.string().required(),
|
||||||
@@ -144,7 +140,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons',
|
'',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
command: Joi.string().optional(),
|
command: Joi.string().optional(),
|
||||||
@@ -174,7 +170,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/crons',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -192,7 +188,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/pin',
|
'/pin',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -210,7 +206,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/unpin',
|
'/unpin',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -228,7 +224,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/crons/import',
|
'/import',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -243,7 +239,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/crons/:id',
|
'/:id',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.string().required(),
|
||||||
@@ -263,7 +259,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/crons/status',
|
'/status',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
ids: Joi.array().items(Joi.string().required()),
|
ids: Joi.array().items(Joi.string().required()),
|
||||||
|
|||||||
+19
-21
@@ -6,24 +6,22 @@ import { celebrate, Joi } from 'celebrate';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/dependencies', route);
|
||||||
route.get(
|
|
||||||
'/dependencies',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const dependenceService = Container.get(DependenceService);
|
||||||
const dependenceService = Container.get(DependenceService);
|
const data = await dependenceService.dependencies(req.query as any);
|
||||||
const data = await dependenceService.dependencies(req.query as any);
|
return res.send({ code: 200, data });
|
||||||
return res.send({ code: 200, data });
|
} catch (e) {
|
||||||
} catch (e) {
|
logger.error('🔥 error: %o', e);
|
||||||
logger.error('🔥 error: %o', e);
|
return next(e);
|
||||||
return next(e);
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/dependencies',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(
|
body: Joi.array().items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
@@ -47,7 +45,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/dependencies',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
@@ -70,7 +68,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/dependencies',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -88,7 +86,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/dependencies/force',
|
'/force',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -106,7 +104,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/dependencies/:id',
|
'/:id',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.string().required(),
|
||||||
@@ -126,7 +124,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/dependencies/reinstall',
|
'/reinstall',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
|
|||||||
+21
-23
@@ -6,24 +6,22 @@ import { celebrate, Joi } from 'celebrate';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/envs', route);
|
||||||
route.get(
|
|
||||||
'/envs',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const envService = Container.get(EnvService);
|
||||||
const envService = Container.get(EnvService);
|
const data = await envService.envs(req.query.searchValue as string);
|
||||||
const data = await envService.envs(req.query.searchValue as string);
|
return res.send({ code: 200, data });
|
||||||
return res.send({ code: 200, data });
|
} catch (e) {
|
||||||
} catch (e) {
|
logger.error('🔥 error: %o', e);
|
||||||
logger.error('🔥 error: %o', e);
|
return next(e);
|
||||||
return next(e);
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/envs',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(
|
body: Joi.array().items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
@@ -47,7 +45,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
value: Joi.string().required(),
|
value: Joi.string().required(),
|
||||||
@@ -70,7 +68,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/envs',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -88,7 +86,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/:id/move',
|
'/:id/move',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.string().required(),
|
||||||
@@ -112,7 +110,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/disable',
|
'/disable',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -130,7 +128,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/enable',
|
'/enable',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.array().items(Joi.string().required()),
|
body: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
@@ -148,7 +146,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/envs/name',
|
'/name',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
ids: Joi.array().items(Joi.string().required()),
|
ids: Joi.array().items(Joi.string().required()),
|
||||||
@@ -169,7 +167,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/envs/:id',
|
'/:id',
|
||||||
celebrate({
|
celebrate({
|
||||||
params: Joi.object({
|
params: Joi.object({
|
||||||
id: Joi.string().required(),
|
id: Joi.string().required(),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import cron from './cron';
|
|||||||
import script from './script';
|
import script from './script';
|
||||||
import open from './open';
|
import open from './open';
|
||||||
import dependence from './dependence';
|
import dependence from './dependence';
|
||||||
|
import system from './system';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -18,6 +19,7 @@ export default () => {
|
|||||||
script(app);
|
script(app);
|
||||||
open(app);
|
open(app);
|
||||||
dependence(app);
|
dependence(app);
|
||||||
|
system(app);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|||||||
+33
-35
@@ -7,44 +7,42 @@ import { getFileContentByName } from '../config/util';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/logs', route);
|
||||||
route.get(
|
|
||||||
'/logs',
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const logger: Logger = Container.get('logger');
|
||||||
const logger: Logger = Container.get('logger');
|
try {
|
||||||
try {
|
const fileList = fs.readdirSync(config.logPath, 'utf-8');
|
||||||
const fileList = fs.readdirSync(config.logPath, 'utf-8');
|
const dirs = [];
|
||||||
const dirs = [];
|
for (let i = 0; i < fileList.length; i++) {
|
||||||
for (let i = 0; i < fileList.length; i++) {
|
const stat = fs.lstatSync(config.logPath + fileList[i]);
|
||||||
const stat = fs.lstatSync(config.logPath + fileList[i]);
|
if (stat.isDirectory()) {
|
||||||
if (stat.isDirectory()) {
|
const fileListTmp = fs.readdirSync(
|
||||||
const fileListTmp = fs.readdirSync(
|
`${config.logPath}/${fileList[i]}`,
|
||||||
`${config.logPath}/${fileList[i]}`,
|
'utf-8',
|
||||||
'utf-8',
|
);
|
||||||
);
|
dirs.push({
|
||||||
dirs.push({
|
name: fileList[i],
|
||||||
name: fileList[i],
|
isDir: true,
|
||||||
isDir: true,
|
files: fileListTmp.reverse(),
|
||||||
files: fileListTmp.reverse(),
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
dirs.push({
|
||||||
dirs.push({
|
name: fileList[i],
|
||||||
name: fileList[i],
|
isDir: false,
|
||||||
isDir: false,
|
files: [],
|
||||||
files: [],
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
res.send({ code: 200, dirs });
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
|
||||||
}
|
}
|
||||||
},
|
res.send({ code: 200, dirs });
|
||||||
);
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/logs/:dir/:file',
|
'/:dir/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -61,7 +59,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/logs/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
|
|||||||
+77
-17
@@ -9,14 +9,15 @@ import { Logger } from 'winston';
|
|||||||
import config from '../config';
|
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, { join } from 'path';
|
||||||
|
import ScriptService from '../services/script';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/scripts', route);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/scripts/files',
|
'/files',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -43,7 +44,7 @@ export default (app: Router) => {
|
|||||||
children.push({
|
children.push({
|
||||||
title: childFile,
|
title: childFile,
|
||||||
value: childFile,
|
value: childFile,
|
||||||
key: childFile,
|
key: `${fileOrDir}/${childFile}`,
|
||||||
mtime: statObj.mtimeMs,
|
mtime: statObj.mtimeMs,
|
||||||
parent: fileOrDir,
|
parent: fileOrDir,
|
||||||
});
|
});
|
||||||
@@ -79,14 +80,16 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/scripts/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const path = req.query.path ? `${req.query.path}/` : '';
|
const filePath = join(
|
||||||
const content = getFileContentByName(
|
config.scriptPath,
|
||||||
`${config.scriptPath}${path}${req.params.file}`,
|
req.query.path as string,
|
||||||
|
req.params.file,
|
||||||
);
|
);
|
||||||
|
const content = getFileContentByName(filePath);
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
logger.error('🔥 error: %o', e);
|
||||||
@@ -96,11 +99,11 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/scripts',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
path: Joi.string().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
content: Joi.string().allow(''),
|
content: Joi.string().allow(''),
|
||||||
originFilename: Joi.string().allow(''),
|
originFilename: Joi.string().allow(''),
|
||||||
}),
|
}),
|
||||||
@@ -120,6 +123,9 @@ export default (app: Router) => {
|
|||||||
if (!path.endsWith('/')) {
|
if (!path.endsWith('/')) {
|
||||||
path += '/';
|
path += '/';
|
||||||
}
|
}
|
||||||
|
if (!path.startsWith('/')) {
|
||||||
|
path = `${config.scriptPath}${path}`;
|
||||||
|
}
|
||||||
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
||||||
return res.send({
|
return res.send({
|
||||||
code: 430,
|
code: 430,
|
||||||
@@ -153,11 +159,11 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/scripts',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
path: Joi.string().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
content: Joi.string().required(),
|
content: Joi.string().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -169,7 +175,7 @@ export default (app: Router) => {
|
|||||||
content: string;
|
content: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
const filePath = `${config.scriptPath}${path}/${filename}`;
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
fs.writeFileSync(filePath, content);
|
fs.writeFileSync(filePath, content);
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -180,19 +186,21 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/scripts',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
|
path: Joi.string().allow(''),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
let { filename } = req.body as {
|
let { filename, path } = req.body as {
|
||||||
filename: string;
|
filename: string;
|
||||||
|
path: string;
|
||||||
};
|
};
|
||||||
const filePath = `${config.scriptPath}${filename}`;
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -203,7 +211,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/scripts/download',
|
'/download',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
@@ -232,4 +240,56 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/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 = join(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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/stop',
|
||||||
|
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 = join(path, filename);
|
||||||
|
const scriptService = Container.get(ScriptService);
|
||||||
|
const result = await scriptService.stopScript(filePath);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import config from '../config';
|
||||||
|
import SystemService from '../services/system';
|
||||||
|
import { celebrate, Joi } from 'celebrate';
|
||||||
|
import { getFileContentByName } from '../config/util';
|
||||||
|
import UserService from '../services/user';
|
||||||
|
const route = Router();
|
||||||
|
|
||||||
|
export default (app: Router) => {
|
||||||
|
app.use('/system', route);
|
||||||
|
|
||||||
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const userService = Container.get(UserService);
|
||||||
|
const authInfo = await userService.getUserInfo();
|
||||||
|
const envDbContent = getFileContentByName(config.envDbFile);
|
||||||
|
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
||||||
|
|
||||||
|
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||||
|
const version = currentVersionFile.match(versionRegx)![1];
|
||||||
|
|
||||||
|
let isInitialized = true;
|
||||||
|
if (
|
||||||
|
Object.keys(authInfo).length === 2 &&
|
||||||
|
authInfo.username === 'admin' &&
|
||||||
|
authInfo.password === 'admin' &&
|
||||||
|
envDbContent.length === 0
|
||||||
|
) {
|
||||||
|
isInitialized = false;
|
||||||
|
}
|
||||||
|
res.send({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
isInitialized,
|
||||||
|
version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/log/remove',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const data = await systemService.getLogRemoveFrequency();
|
||||||
|
res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/log/remove',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
frequency: Joi.number().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.updateLogRemoveFrequency(
|
||||||
|
req.body.frequency,
|
||||||
|
);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/update-check',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.checkUpdate();
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/update',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.updateSystem();
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
+29
-130
@@ -9,7 +9,8 @@ import { getFileContentByName } from '../config/util';
|
|||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/', route);
|
app.use('/user', route);
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/login',
|
'/login',
|
||||||
celebrate({
|
celebrate({
|
||||||
@@ -47,7 +48,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user',
|
'/',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
username: Joi.string().required(),
|
username: Joi.string().required(),
|
||||||
@@ -67,29 +68,26 @@ export default (app: Router) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
'/user',
|
const logger: Logger = Container.get('logger');
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
try {
|
||||||
const logger: Logger = Container.get('logger');
|
const userService = Container.get(UserService);
|
||||||
try {
|
const authInfo = await userService.getUserInfo();
|
||||||
const userService = Container.get(UserService);
|
res.send({
|
||||||
const authInfo = await userService.getUserInfo();
|
code: 200,
|
||||||
res.send({
|
data: {
|
||||||
code: 200,
|
username: authInfo.username,
|
||||||
data: {
|
twoFactorActivated: authInfo.twoFactorActivated,
|
||||||
username: authInfo.username,
|
},
|
||||||
twoFactorActivated: authInfo.twoFactorActivated,
|
});
|
||||||
},
|
} catch (e) {
|
||||||
});
|
logger.error('🔥 error: %o', e);
|
||||||
} catch (e) {
|
return next(e);
|
||||||
logger.error('🔥 error: %o', e);
|
}
|
||||||
return next(e);
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/user/two-factor/init',
|
'/two-factor/init',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -104,7 +102,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/two-factor/active',
|
'/two-factor/active',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
code: Joi.string().required(),
|
code: Joi.string().required(),
|
||||||
@@ -124,7 +122,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/two-factor/deactive',
|
'/two-factor/deactive',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -139,7 +137,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/two-factor/login',
|
'/two-factor/login',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
code: Joi.string().required(),
|
code: Joi.string().required(),
|
||||||
@@ -161,7 +159,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/user/login-log',
|
'/login-log',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -176,7 +174,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/user/notification',
|
'/notification',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -191,7 +189,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/user/notification',
|
'/notification',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -205,40 +203,8 @@ export default (app: Router) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
|
||||||
'/system',
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
|
||||||
const userService = Container.get(UserService);
|
|
||||||
const authInfo = await userService.getUserInfo();
|
|
||||||
const envDbContent = getFileContentByName(config.envDbFile);
|
|
||||||
|
|
||||||
let isInitialized = true;
|
|
||||||
if (
|
|
||||||
Object.keys(authInfo).length === 2 &&
|
|
||||||
authInfo.username === 'admin' &&
|
|
||||||
authInfo.password === 'admin' &&
|
|
||||||
envDbContent.length === 0
|
|
||||||
) {
|
|
||||||
isInitialized = false;
|
|
||||||
}
|
|
||||||
res.send({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
isInitialized,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 初始化api
|
|
||||||
route.put(
|
route.put(
|
||||||
'/init/user',
|
'/init',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
username: Joi.string().required(),
|
username: Joi.string().required(),
|
||||||
@@ -259,7 +225,7 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/init/notification',
|
'/notification/init',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -272,71 +238,4 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
|
||||||
'/system/log/remove',
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
|
||||||
const userService = Container.get(UserService);
|
|
||||||
const data = await userService.getLogRemoveFrequency();
|
|
||||||
res.send({ code: 200, data });
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.put(
|
|
||||||
'/system/log/remove',
|
|
||||||
celebrate({
|
|
||||||
body: Joi.object({
|
|
||||||
frequency: Joi.number().required(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
|
||||||
const userService = Container.get(UserService);
|
|
||||||
const result = await userService.updateLogRemoveFrequency(
|
|
||||||
req.body.frequency,
|
|
||||||
);
|
|
||||||
res.send(result);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.put(
|
|
||||||
'/system/update-check',
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
|
||||||
const userService = Container.get(UserService);
|
|
||||||
const result = await userService.checkUpdate();
|
|
||||||
res.send(result);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
route.put(
|
|
||||||
'/system/update',
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
|
||||||
const userService = Container.get(UserService);
|
|
||||||
const result = await userService.updateSystem();
|
|
||||||
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();
|
||||||
|
|||||||
+4
-10
@@ -5,7 +5,7 @@ import { createRandomString } from './util';
|
|||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
const lastVersionFile =
|
const lastVersionFile =
|
||||||
'https://ghproxy.com/https://raw.githubusercontent.com/whyour/qinglong/master/src/version.ts';
|
'https://raw.githubusercontent.com/whyour/qinglong/master/src/version.ts';
|
||||||
|
|
||||||
const envFound = dotenv.config();
|
const envFound = dotenv.config();
|
||||||
const rootPath = process.cwd();
|
const rootPath = process.cwd();
|
||||||
@@ -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),
|
||||||
@@ -83,12 +77,12 @@ export default {
|
|||||||
writePathList: [configPath, scriptPath],
|
writePathList: [configPath, scriptPath],
|
||||||
bakPath,
|
bakPath,
|
||||||
apiWhiteList: [
|
apiWhiteList: [
|
||||||
'/api/login',
|
'/api/user/login',
|
||||||
'/open/auth/token',
|
'/open/auth/token',
|
||||||
'/api/user/two-factor/login',
|
'/api/user/two-factor/login',
|
||||||
'/api/system',
|
'/api/system',
|
||||||
'/api/init/user',
|
'/api/user/init',
|
||||||
'/api/init/notification',
|
'/api/user/notification/init',
|
||||||
],
|
],
|
||||||
versionFile,
|
versionFile,
|
||||||
lastVersionFile,
|
lastVersionFile,
|
||||||
|
|||||||
@@ -231,3 +231,40 @@ export async function fileExist(file: any) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function concurrentRun(
|
||||||
|
fnList: Array<() => Promise<any>> = [],
|
||||||
|
max = 5,
|
||||||
|
) {
|
||||||
|
if (!fnList.length) return;
|
||||||
|
|
||||||
|
const replyList: any[] = []; // 收集任务执行结果
|
||||||
|
const startTime = new Date().getTime(); // 记录任务执行开始时间
|
||||||
|
|
||||||
|
// 任务执行程序
|
||||||
|
const schedule = async (index: number) => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
const fn = fnList[index];
|
||||||
|
if (!fn) return resolve(null);
|
||||||
|
|
||||||
|
// 执行当前异步任务
|
||||||
|
const reply = await fn();
|
||||||
|
replyList[index] = reply;
|
||||||
|
|
||||||
|
// 执行完当前任务后,继续执行任务池的剩余任务
|
||||||
|
await schedule(index + max);
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务池执行程序
|
||||||
|
const scheduleList = new Array(max)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => schedule(index));
|
||||||
|
|
||||||
|
// 使用 Promise.all 批量执行
|
||||||
|
const r = await Promise.all(scheduleList);
|
||||||
|
const cost = (new Date().getTime() - startTime) / 1000;
|
||||||
|
|
||||||
|
return replyList;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import dependencyInjectorLoader from './dependencyInjector';
|
|||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import initData from './initData';
|
import initData from './initData';
|
||||||
import { Application } from 'express';
|
import { Application } from 'express';
|
||||||
|
import linkDeps from './deps';
|
||||||
|
|
||||||
export default async ({ expressApp }: { expressApp: Application }) => {
|
export default async ({ expressApp }: { expressApp: Application }) => {
|
||||||
await dependencyInjectorLoader({
|
await dependencyInjectorLoader({
|
||||||
@@ -15,4 +16,7 @@ export default async ({ expressApp }: { expressApp: Application }) => {
|
|||||||
|
|
||||||
await initData();
|
await initData();
|
||||||
Logger.info('✌️ init data loaded');
|
Logger.info('✌️ init data loaded');
|
||||||
|
|
||||||
|
await linkDeps();
|
||||||
|
Logger.info('✌️ link deps');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import chokidar from 'chokidar';
|
||||||
|
import config from '../config/index';
|
||||||
|
|
||||||
|
function linkToNodeModule(src: string, dst?: string) {
|
||||||
|
const target = path.join(config.rootPath, 'node_modules', dst || src);
|
||||||
|
const source = path.join(config.rootPath, src);
|
||||||
|
|
||||||
|
fs.lstat(target, (err, stat) => {
|
||||||
|
if (!stat) {
|
||||||
|
fs.symlink(source, target, 'dir', (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async (src: string = 'deps') => {
|
||||||
|
linkToNodeModule(src);
|
||||||
|
|
||||||
|
const source = path.join(config.rootPath, src);
|
||||||
|
const watcher = chokidar.watch(source, {
|
||||||
|
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
||||||
|
persistent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher
|
||||||
|
.on('add', (path) => linkToNodeModule(src))
|
||||||
|
.on('change', (path) => linkToNodeModule(src));
|
||||||
|
};
|
||||||
@@ -98,7 +98,7 @@ export default ({ app }: { app: Application }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(async (req, res, next) => {
|
app.use(async (req, res, next) => {
|
||||||
if (!['/api/init/user', '/api/init/notification'].includes(req.path)) {
|
if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ export default async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
||||||
dependenceDb.find({ status: { $in: [0, 1, 2] } }).exec((err, docs) => {
|
dependenceDb.find({ status: { $in: [0, 1, 2] } }).exec(async (err, docs) => {
|
||||||
const groups = _.groupBy(docs, 'type');
|
const groups = _.groupBy(docs, 'type');
|
||||||
for (const key in groups) {
|
for (const key in groups) {
|
||||||
if (Object.prototype.hasOwnProperty.call(groups, key)) {
|
if (Object.prototype.hasOwnProperty.call(groups, key)) {
|
||||||
const group = groups[key];
|
const group = groups[key];
|
||||||
const depIds = group.map((x) => x._id);
|
const depIds = group.map((x) => x._id);
|
||||||
for (const dep of depIds) {
|
for (const dep of depIds) {
|
||||||
dependenceService.reInstall([dep]);
|
await dependenceService.reInstall([dep]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@ import Logger from './logger';
|
|||||||
|
|
||||||
export default ({ expressApp }: { expressApp: Application }) => {
|
export default ({ expressApp }: { expressApp: Application }) => {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://e14681bce55f4849b11024a7d424b711@o1051273.ingest.sentry.io/6047906',
|
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
||||||
integrations: [
|
integrations: [
|
||||||
new Sentry.Integrations.Http({ tracing: true }),
|
new Sentry.Integrations.Http({ tracing: true }),
|
||||||
new Tracing.Integrations.Express({ app: expressApp }),
|
new Tracing.Integrations.Express({ app: expressApp }),
|
||||||
|
|||||||
+5
-2
@@ -44,8 +44,11 @@ const run = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
app
|
app
|
||||||
.listen(config.cronPort, () => {
|
.listen(config.cronPort, async () => {
|
||||||
run();
|
await require('./loaders/sentry').default({ expressApp: app });
|
||||||
|
await require('./loaders/db').default();
|
||||||
|
|
||||||
|
await run();
|
||||||
Logger.info(`
|
Logger.info(`
|
||||||
################################################
|
################################################
|
||||||
🛡️ Schedule listening on port: ${config.cronPort} 🛡️
|
🛡️ Schedule listening on port: ${config.cronPort} 🛡️
|
||||||
|
|||||||
+33
-23
@@ -6,7 +6,7 @@ import { Crontab, CrontabStatus } from '../data/cron';
|
|||||||
import { exec, execSync, spawn } from 'child_process';
|
import { exec, execSync, spawn } from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
import { getFileContentByName } from '../config/util';
|
import { getFileContentByName, concurrentRun } from '../config/util';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { promises, existsSync } from 'fs';
|
import { promises, existsSync } from 'fs';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
@@ -93,17 +93,21 @@ export default class CronService {
|
|||||||
last_running_time: number;
|
last_running_time: number;
|
||||||
last_execution_time: number;
|
last_execution_time: number;
|
||||||
}) {
|
}) {
|
||||||
|
const options: any = {
|
||||||
|
status,
|
||||||
|
pid,
|
||||||
|
log_path,
|
||||||
|
last_execution_time,
|
||||||
|
};
|
||||||
|
if (last_running_time > 0) {
|
||||||
|
options.last_running_time = last_running_time;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.cronDb.update(
|
this.cronDb.update(
|
||||||
{ _id: { $in: ids } },
|
{ _id: { $in: ids } },
|
||||||
{
|
{
|
||||||
$set: {
|
$set: options,
|
||||||
status,
|
|
||||||
pid,
|
|
||||||
log_path,
|
|
||||||
last_running_time,
|
|
||||||
last_execution_time,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{ multi: true, returnUpdatedDocs: true },
|
{ multi: true, returnUpdatedDocs: true },
|
||||||
(err) => {
|
(err) => {
|
||||||
@@ -155,7 +159,9 @@ export default class CronService {
|
|||||||
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
||||||
let query = {};
|
let query = {};
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const reg = new RegExp(searchText, 'i');
|
const encodeText = encodeURIComponent(searchText);
|
||||||
|
const reg = new RegExp(`${searchText}|${encodeText}`, 'i');
|
||||||
|
|
||||||
query = {
|
query = {
|
||||||
$or: [
|
$or: [
|
||||||
{
|
{
|
||||||
@@ -194,10 +200,10 @@ export default class CronService {
|
|||||||
{ $set: { status: CrontabStatus.queued } },
|
{ $set: { status: CrontabStatus.queued } },
|
||||||
{ multi: true },
|
{ multi: true },
|
||||||
);
|
);
|
||||||
for (let i = 0; i < ids.length; i++) {
|
concurrentRun(
|
||||||
const id = ids[i];
|
ids.map((id) => () => this.runSingle(id)),
|
||||||
this.queue.add(() => this.runSingle(id));
|
10,
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(ids: string[]) {
|
public async stop(ids: string[]) {
|
||||||
@@ -235,7 +241,7 @@ export default class CronService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async killTask(name: string) {
|
public async killTask(name: string) {
|
||||||
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
|
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
try {
|
try {
|
||||||
@@ -245,16 +251,20 @@ export default class CronService {
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pids = pid.match(/\(\d+/g);
|
let pids = pid.match(/\(\d+/g);
|
||||||
const killLogs = [];
|
const killLogs = [];
|
||||||
for (const id of pids) {
|
if (pids && pids.length > 0) {
|
||||||
const c = `kill -9 ${id.slice(1)}`;
|
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
|
||||||
const { stdout, stderr } = await execAsync(c);
|
pids = pids.slice(0, 3);
|
||||||
if (stderr) {
|
for (const id of pids) {
|
||||||
killLogs.push(stderr);
|
const c = `kill -9 ${id.slice(1)}`;
|
||||||
}
|
const { stdout, stderr } = await execAsync(c);
|
||||||
if (stdout) {
|
if (stderr) {
|
||||||
killLogs.push(stdout);
|
killLogs.push(stderr);
|
||||||
|
}
|
||||||
|
if (stdout) {
|
||||||
|
killLogs.push(stdout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
|
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
|
||||||
|
|||||||
+95
-89
@@ -131,7 +131,7 @@ export default class DependenceService {
|
|||||||
{ $set: { status: DependenceStatus.installing, log: [] } },
|
{ $set: { status: DependenceStatus.installing, log: [] } },
|
||||||
{ multi: true, returnUpdatedDocs: true },
|
{ multi: true, returnUpdatedDocs: true },
|
||||||
async (err, num, docs: Dependence[]) => {
|
async (err, num, docs: Dependence[]) => {
|
||||||
this.installOrUninstallDependencies(docs);
|
await this.installOrUninstallDependencies(docs);
|
||||||
resolve(docs);
|
resolve(docs);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -178,103 +178,109 @@ export default class DependenceService {
|
|||||||
dependencies: Dependence[],
|
dependencies: Dependence[],
|
||||||
isInstall: boolean = true,
|
isInstall: boolean = true,
|
||||||
) {
|
) {
|
||||||
if (dependencies.length === 0) {
|
return new Promise((resolve) => {
|
||||||
return;
|
if (dependencies.length === 0) {
|
||||||
}
|
resolve(null);
|
||||||
const depNames = dependencies.map((x) => x.name).join(' ');
|
return;
|
||||||
const depRunCommand = (
|
}
|
||||||
isInstall
|
const depNames = dependencies.map((x) => x.name).join(' ');
|
||||||
? InstallDependenceCommandTypes
|
const depRunCommand = (
|
||||||
: unInstallDependenceCommandTypes
|
isInstall
|
||||||
)[dependencies[0].type as any];
|
? InstallDependenceCommandTypes
|
||||||
const actionText = isInstall ? '安装' : '删除';
|
: unInstallDependenceCommandTypes
|
||||||
const depIds = dependencies.map((x) => x._id) as string[];
|
)[dependencies[0].type as any];
|
||||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
const actionText = isInstall ? '安装' : '删除';
|
||||||
const startTime = Date.now();
|
const depIds = dependencies.map((x) => x._id) as string[];
|
||||||
this.sockService.sendMessage({
|
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||||
type: 'installDependence',
|
const startTime = Date.now();
|
||||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
|
||||||
startTime,
|
|
||||||
).toLocaleString()}`,
|
|
||||||
references: depIds,
|
|
||||||
});
|
|
||||||
this.updateLog(
|
|
||||||
depIds,
|
|
||||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
|
||||||
startTime,
|
|
||||||
).toLocaleString()}\n`,
|
|
||||||
);
|
|
||||||
cp.stdout.on('data', (data) => {
|
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'installDependence',
|
type: 'installDependence',
|
||||||
message: data.toString(),
|
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||||
references: depIds,
|
startTime,
|
||||||
});
|
).toLocaleString()}`,
|
||||||
this.updateLog(depIds, data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
cp.stderr.on('data', (data) => {
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'installDependence',
|
|
||||||
message: data.toString(),
|
|
||||||
references: depIds,
|
|
||||||
});
|
|
||||||
this.updateLog(depIds, data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
cp.on('error', (err) => {
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'installDependence',
|
|
||||||
message: JSON.stringify(err),
|
|
||||||
references: depIds,
|
|
||||||
});
|
|
||||||
this.updateLog(depIds, JSON.stringify(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
cp.on('close', (code) => {
|
|
||||||
const endTime = Date.now();
|
|
||||||
const isSucceed = code === 0;
|
|
||||||
const resultText = isSucceed ? '成功' : '失败';
|
|
||||||
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'installDependence',
|
|
||||||
message: `依赖${actionText}${resultText},结束时间 ${new Date(
|
|
||||||
endTime,
|
|
||||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
this.updateLog(
|
this.updateLog(
|
||||||
depIds,
|
depIds,
|
||||||
`依赖${actionText}${resultText},结束时间 ${new Date(
|
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||||
endTime,
|
startTime,
|
||||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
).toLocaleString()}\n`,
|
||||||
);
|
);
|
||||||
|
cp.stdout.on('data', (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: data.toString(),
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
this.updateLog(depIds, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
let status = null;
|
cp.stderr.on('data', (data) => {
|
||||||
if (isSucceed) {
|
this.sockService.sendMessage({
|
||||||
status = isInstall
|
type: 'installDependence',
|
||||||
? DependenceStatus.installed
|
message: data.toString(),
|
||||||
: DependenceStatus.removed;
|
references: depIds,
|
||||||
} else {
|
});
|
||||||
status = isInstall
|
this.updateLog(depIds, data.toString());
|
||||||
? DependenceStatus.installFailed
|
});
|
||||||
: DependenceStatus.removeFailed;
|
|
||||||
}
|
|
||||||
this.dependenceDb.update(
|
|
||||||
{ _id: { $in: depIds } },
|
|
||||||
{
|
|
||||||
$set: { status },
|
|
||||||
$unset: { pid: true },
|
|
||||||
},
|
|
||||||
{ multi: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果删除依赖成功,3秒后删除数据库记录
|
cp.on('error', (err) => {
|
||||||
if (isSucceed && !isInstall) {
|
this.sockService.sendMessage({
|
||||||
setTimeout(() => {
|
type: 'installDependence',
|
||||||
this.removeDb(depIds);
|
message: JSON.stringify(err),
|
||||||
}, 5000);
|
references: depIds,
|
||||||
}
|
});
|
||||||
|
this.updateLog(depIds, JSON.stringify(err));
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('close', (code) => {
|
||||||
|
const endTime = Date.now();
|
||||||
|
const isSucceed = code === 0;
|
||||||
|
const resultText = isSucceed ? '成功' : '失败';
|
||||||
|
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: `依赖${actionText}${resultText},结束时间 ${new Date(
|
||||||
|
endTime,
|
||||||
|
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
this.updateLog(
|
||||||
|
depIds,
|
||||||
|
`依赖${actionText}${resultText},结束时间 ${new Date(
|
||||||
|
endTime,
|
||||||
|
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = null;
|
||||||
|
if (isSucceed) {
|
||||||
|
status = isInstall
|
||||||
|
? DependenceStatus.installed
|
||||||
|
: DependenceStatus.removed;
|
||||||
|
} else {
|
||||||
|
status = isInstall
|
||||||
|
? DependenceStatus.installFailed
|
||||||
|
: DependenceStatus.removeFailed;
|
||||||
|
}
|
||||||
|
this.dependenceDb.update(
|
||||||
|
{ _id: { $in: depIds } },
|
||||||
|
{
|
||||||
|
$set: { status },
|
||||||
|
$unset: { pid: true },
|
||||||
|
},
|
||||||
|
{ multi: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果删除依赖成功,3秒后删除数据库记录
|
||||||
|
if (isSucceed && !isInstall) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.removeDb(depIds);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -113,7 +113,9 @@ export default class EnvService {
|
|||||||
): Promise<Env[]> {
|
): Promise<Env[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const reg = new RegExp(searchText);
|
const encodeText = encodeURIComponent(searchText);
|
||||||
|
const reg = new RegExp(`${searchText}|${encodeText}`, 'i');
|
||||||
|
|
||||||
condition = {
|
condition = {
|
||||||
$or: [
|
$or: [
|
||||||
{
|
{
|
||||||
@@ -219,11 +221,17 @@ export default class EnvService {
|
|||||||
|
|
||||||
// 忽略不符合bash要求的环境变量名称
|
// 忽略不符合bash要求的环境变量名称
|
||||||
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) {
|
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) {
|
||||||
env_string += `export ${key}="${_(group)
|
let value = _(group)
|
||||||
.filter((x) => x.status !== EnvStatus.disabled)
|
.filter((x) => x.status !== EnvStatus.disabled)
|
||||||
.map('value')
|
.map('value')
|
||||||
.join('&')
|
.join('&')
|
||||||
.replace(/ /g, '')}"\n`;
|
.replace(/ /g, '');
|
||||||
|
if (/"/.test(value)) {
|
||||||
|
value = `'${value}'`;
|
||||||
|
} else {
|
||||||
|
value = `"${value}"`;
|
||||||
|
}
|
||||||
|
env_string += `export ${key}=${value}\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -97,7 +97,9 @@ export default class OpenService {
|
|||||||
): Promise<App[]> {
|
): Promise<App[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const reg = new RegExp(searchText);
|
const encodeText = encodeURIComponent(searchText);
|
||||||
|
const reg = new RegExp(`${searchText}|${encodeText}`, 'i');
|
||||||
|
|
||||||
condition = {
|
condition = {
|
||||||
$or: [
|
$or: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import SockService from './sock';
|
||||||
|
import CronService from './cron';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ScriptService {
|
||||||
|
constructor(
|
||||||
|
@Inject('logger') private logger: winston.Logger,
|
||||||
|
private sockService: SockService,
|
||||||
|
private cronService: CronService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async runScript(path: string) {
|
||||||
|
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
|
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),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('close', (err) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message: `执行结束`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { code: 200 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stopScript(path: string) {
|
||||||
|
const err = await this.cronService.killTask(`task -l ${path} now`);
|
||||||
|
const str = err ? `\n${err}` : '';
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message: `${str}\n## 执行结束... ${new Date()
|
||||||
|
.toLocaleString('zh', { hour12: false })
|
||||||
|
.replace(' 24:', ' 00:')} `,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { code: 200 };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import config from '../config';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { AuthDataType, AuthInfo, LoginStatus } from '../data/auth';
|
||||||
|
import { NotificationInfo } from '../data/notify';
|
||||||
|
import NotificationService from './notify';
|
||||||
|
import ScheduleService from './schedule';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import SockService from './sock';
|
||||||
|
import got from 'got';
|
||||||
|
import { dbs } from '../loaders/db';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class SystemService {
|
||||||
|
@Inject((type) => NotificationService)
|
||||||
|
private notificationService!: NotificationService;
|
||||||
|
private authDb = dbs.authDb;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject('logger') private logger: winston.Logger,
|
||||||
|
private scheduleService: ScheduleService,
|
||||||
|
private sockService: SockService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async getLogRemoveFrequency() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.authDb
|
||||||
|
.find({ type: AuthDataType.removeLogFrequency })
|
||||||
|
.exec((err, docs) => {
|
||||||
|
if (err || docs.length === 0) {
|
||||||
|
resolve({});
|
||||||
|
} else {
|
||||||
|
resolve(docs[0].info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateAuthDb(payload: AuthInfo): Promise<any> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.authDb.update(
|
||||||
|
{ type: payload.type },
|
||||||
|
{ ...payload },
|
||||||
|
{ upsert: true, returnUpdatedDocs: true },
|
||||||
|
(err, num, doc: any) => {
|
||||||
|
if (err) {
|
||||||
|
resolve({} as NotificationInfo);
|
||||||
|
} else {
|
||||||
|
resolve({ ...doc.info, _id: doc._id });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateNotificationMode(notificationInfo: NotificationInfo) {
|
||||||
|
const code = Math.random().toString().slice(-6);
|
||||||
|
const isSuccess = await this.notificationService.testNotify(
|
||||||
|
notificationInfo,
|
||||||
|
'青龙',
|
||||||
|
`【蛟龙】测试通知 https://t.me/jiao_long`,
|
||||||
|
);
|
||||||
|
if (isSuccess) {
|
||||||
|
const result = await this.updateAuthDb({
|
||||||
|
type: AuthDataType.notification,
|
||||||
|
info: { ...notificationInfo },
|
||||||
|
});
|
||||||
|
return { code: 200, data: { ...result, code } };
|
||||||
|
} else {
|
||||||
|
return { code: 400, data: '通知发送失败,请检查参数' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateLogRemoveFrequency(frequency: number) {
|
||||||
|
const result = await this.updateAuthDb({
|
||||||
|
type: AuthDataType.removeLogFrequency,
|
||||||
|
info: { frequency },
|
||||||
|
});
|
||||||
|
const cron = {
|
||||||
|
_id: result._id,
|
||||||
|
name: '删除日志',
|
||||||
|
command: `ql rmlog ${frequency}`,
|
||||||
|
schedule: `5 23 */${frequency} * *`,
|
||||||
|
};
|
||||||
|
await this.scheduleService.cancelSchedule(cron);
|
||||||
|
if (frequency > 0) {
|
||||||
|
await this.scheduleService.generateSchedule(cron);
|
||||||
|
}
|
||||||
|
return { code: 200, data: { ...cron } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkUpdate() {
|
||||||
|
try {
|
||||||
|
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
||||||
|
const logRegx = /.*export const changeLog = \`((.*\n.*)+)\`;/;
|
||||||
|
|
||||||
|
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||||
|
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
||||||
|
|
||||||
|
let lastVersion = '';
|
||||||
|
let lastLog = '';
|
||||||
|
try {
|
||||||
|
const result = await Promise.race([
|
||||||
|
got.get(config.lastVersionFile, { timeout: 1000, retry: 0 }),
|
||||||
|
got.get(`https://ghproxy.com/${config.lastVersionFile}`, {
|
||||||
|
timeout: 5000,
|
||||||
|
retry: 0,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const lastVersionFileContent = result.body;
|
||||||
|
lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
||||||
|
lastLog = lastVersionFileContent.match(logRegx)
|
||||||
|
? lastVersionFileContent.match(logRegx)![1]
|
||||||
|
: '';
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
hasNewVersion: this.checkHasNewVersion(currentVersion, lastVersion),
|
||||||
|
lastVersion,
|
||||||
|
lastLog,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
code: 400,
|
||||||
|
data: error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkHasNewVersion(curVersion: string, lastVersion: string) {
|
||||||
|
const curArr = curVersion.split('.').map((x) => parseInt(x, 10));
|
||||||
|
const lastArr = lastVersion.split('.').map((x) => parseInt(x, 10));
|
||||||
|
if (curArr[0] < lastArr[0]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (curArr[0] === lastArr[0] && curArr[1] < lastArr[1]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
curArr[0] === lastArr[0] &&
|
||||||
|
curArr[1] === lastArr[1] &&
|
||||||
|
curArr[2] < lastArr[2]
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateSystem() {
|
||||||
|
const cp = spawn('ql -l update', { shell: '/bin/bash' });
|
||||||
|
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: `开始更新系统`,
|
||||||
|
});
|
||||||
|
cp.stdout.on('data', (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: data.toString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.stderr.on('data', (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: data.toString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('error', (err) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: JSON.stringify(err),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { code: 200 };
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-7
@@ -403,13 +403,22 @@ export default class UserService {
|
|||||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||||
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
||||||
|
|
||||||
const lastVersionFileContent = await (
|
let lastVersion = '';
|
||||||
await got.get(config.lastVersionFile)
|
let lastLog = '';
|
||||||
).body;
|
try {
|
||||||
const lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
const result = await Promise.race([
|
||||||
const lastLog = lastVersionFileContent.match(logRegx)
|
got.get(config.lastVersionFile, { timeout: 1000, retry: 0 }),
|
||||||
? lastVersionFileContent.match(logRegx)![1]
|
got.get(`https://ghproxy.com/${config.lastVersionFile}`, {
|
||||||
: '';
|
timeout: 5000,
|
||||||
|
retry: 0,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const lastVersionFileContent = result.body;
|
||||||
|
lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
||||||
|
lastLog = lastVersionFileContent.match(logRegx)
|
||||||
|
? lastVersionFileContent.match(logRegx)![1]
|
||||||
|
: '';
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { CommandModule } from 'yargs';
|
||||||
|
export const updateCommand: CommandModule = {
|
||||||
|
command: 'update',
|
||||||
|
describe: 'Update and restart qinglong',
|
||||||
|
builder: (yargs) => {
|
||||||
|
return yargs.option('repositority', {
|
||||||
|
type: 'string',
|
||||||
|
alias: 'r',
|
||||||
|
describe: `Specify the release warehouse address of the package`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (argv) => {},
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as yargs from 'yargs';
|
||||||
|
import { green, red } from 'chalk';
|
||||||
|
import { updateCommand } from './commands/update';
|
||||||
|
|
||||||
|
yargs
|
||||||
|
.usage('Usage: ql [command] <options>')
|
||||||
|
.command(updateCommand)
|
||||||
|
.fail((err) => {
|
||||||
|
console.error(`${red(err)}`);
|
||||||
|
})
|
||||||
|
.alias('h', 'help')
|
||||||
|
.showHelp()
|
||||||
|
.recommendCommands().argv;
|
||||||
+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 \
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ link_shell
|
|||||||
|
|
||||||
echo -e "======================1. 检测配置文件========================\n"
|
echo -e "======================1. 检测配置文件========================\n"
|
||||||
make_dir /etc/nginx/conf.d
|
make_dir /etc/nginx/conf.d
|
||||||
|
make_dir /run/nginx
|
||||||
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
|
|||||||
+3
-1
@@ -29,6 +29,7 @@
|
|||||||
"@sentry/tracing": "^6.14.0",
|
"@sentry/tracing": "^6.14.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"celebrate": "^13.0.3",
|
"celebrate": "^13.0.3",
|
||||||
|
"chokidar": "^3.5.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cron-parser": "^3.5.0",
|
"cron-parser": "^3.5.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
@@ -50,7 +51,8 @@
|
|||||||
"sockjs": "^0.3.21",
|
"sockjs": "^0.3.21",
|
||||||
"typedi": "^0.8.0",
|
"typedi": "^0.8.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3",
|
||||||
|
"yargs": "^17.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^4.6.2",
|
"@ant-design/icons": "^4.6.2",
|
||||||
|
|||||||
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 变量名= 声明即可
|
||||||
|
|||||||
+57
-4
@@ -13,6 +13,13 @@
|
|||||||
const querystring = require('querystring');
|
const querystring = require('querystring');
|
||||||
const $ = new Env();
|
const $ = new Env();
|
||||||
const timeout = 15000; //超时时间(单位毫秒)
|
const timeout = 15000; //超时时间(单位毫秒)
|
||||||
|
// =======================================gotify通知设置区域==============================================
|
||||||
|
//gotify_url 填写gotify地址,如https://push.example.de:8080
|
||||||
|
//gotify_token 填写gotify的消息应用token
|
||||||
|
//gotify_priority 填写推送消息优先级,默认为0
|
||||||
|
let GOTIFY_URL = '';
|
||||||
|
let GOTIFY_TOKEN = '';
|
||||||
|
let GOTIFY_PRIORITY = 0;
|
||||||
// =======================================go-cqhttp通知设置区域===========================================
|
// =======================================go-cqhttp通知设置区域===========================================
|
||||||
//gobot_url 填写请求地址http://127.0.0.1/send_private_msg
|
//gobot_url 填写请求地址http://127.0.0.1/send_private_msg
|
||||||
//gobot_token 填写在go-cqhttp文件设置的访问密钥
|
//gobot_token 填写在go-cqhttp文件设置的访问密钥
|
||||||
@@ -84,6 +91,16 @@ let PUSH_PLUS_TOKEN = '';
|
|||||||
let PUSH_PLUS_USER = '';
|
let PUSH_PLUS_USER = '';
|
||||||
|
|
||||||
//==========================云端环境变量的判断与接收=========================
|
//==========================云端环境变量的判断与接收=========================
|
||||||
|
if (process.env.GOTIFY_URL) {
|
||||||
|
GOTIFY_URL = process.env.GOTIFY_URL;
|
||||||
|
}
|
||||||
|
if (process.env.GOTIFY_TOKEN) {
|
||||||
|
GOTIFY_TOKEN = process.env.GOTIFY_TOKEN;
|
||||||
|
}
|
||||||
|
if (process.env.GOTIFY_PRIORITY) {
|
||||||
|
GOTIFY_PRIORITY = process.env.GOTIFY_PRIORITY;
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.GOBOT_URL) {
|
if (process.env.GOBOT_URL) {
|
||||||
GOBOT_URL = process.env.GOBOT_URL;
|
GOBOT_URL = process.env.GOBOT_URL;
|
||||||
}
|
}
|
||||||
@@ -200,9 +217,45 @@ async function sendNotify(
|
|||||||
qywxamNotify(text, desp), //企业微信应用消息推送
|
qywxamNotify(text, desp), //企业微信应用消息推送
|
||||||
iGotNotify(text, desp, params), //iGot
|
iGotNotify(text, desp, params), //iGot
|
||||||
gobotNotify(text, desp),//go-cqhttp
|
gobotNotify(text, desp),//go-cqhttp
|
||||||
|
gotifyNotify(text, desp),//gotify
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gotifyNotify(text, desp) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (GOTIFY_URL && GOTIFY_TOKEN) {
|
||||||
|
const options = {
|
||||||
|
url: `${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}`,
|
||||||
|
body: `title=${encodeURIComponent(text)}&message=${encodeURIComponent(desp)}&priority=${GOTIFY_PRIORITY}`,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$.post(options, (err, resp, data) => {
|
||||||
|
try {
|
||||||
|
if (err) {
|
||||||
|
console.log('gotify发送通知调用API失败!!\n');
|
||||||
|
console.log(err);
|
||||||
|
} else {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
if (data.id) {
|
||||||
|
console.log('gotify发送通知消息成功🎉\n');
|
||||||
|
} else {
|
||||||
|
console.log(`${data.message}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.logErr(e, resp);
|
||||||
|
} finally {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function gobotNotify(text, desp, time = 2100) {
|
function gobotNotify(text, desp, time = 2100) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (GOBOT_URL) {
|
if (GOBOT_URL) {
|
||||||
@@ -673,8 +726,8 @@ function qywxamNotify(text, desp) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
console.log(
|
console.log(
|
||||||
'成员ID:' +
|
'成员ID:' +
|
||||||
ChangeUserId(desp) +
|
ChangeUserId(desp) +
|
||||||
'企业微信应用消息发送通知消息失败!!\n',
|
'企业微信应用消息发送通知消息失败!!\n',
|
||||||
);
|
);
|
||||||
console.log(err);
|
console.log(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -682,8 +735,8 @@ function qywxamNotify(text, desp) {
|
|||||||
if (data.errcode === 0) {
|
if (data.errcode === 0) {
|
||||||
console.log(
|
console.log(
|
||||||
'成员ID:' +
|
'成员ID:' +
|
||||||
ChangeUserId(desp) +
|
ChangeUserId(desp) +
|
||||||
'企业微信应用消息发送通知消息成功🎉。\n',
|
'企业微信应用消息发送通知消息成功🎉。\n',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${data.errmsg}\n`);
|
console.log(`${data.errmsg}\n`);
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ push_config = {
|
|||||||
# /send_group_msg 时填入 group_id=QQ群
|
# /send_group_msg 时填入 group_id=QQ群
|
||||||
'GOBOT_TOKEN': '', # go-cqhttp 的 access_token
|
'GOBOT_TOKEN': '', # go-cqhttp 的 access_token
|
||||||
|
|
||||||
|
'GOTIFY_URL': '', # gotify地址,如https://push.example.de:8080
|
||||||
|
'GOTIFY_TOKEN': '', # gotify的消息应用token
|
||||||
|
'GOTIFY_PRIORITY': 0, # 推送消息优先级,默认为0
|
||||||
|
|
||||||
'IGOT_PUSH_KEY': '', # iGot 聚合推送的 IGOT_PUSH_KEY
|
'IGOT_PUSH_KEY': '', # iGot 聚合推送的 IGOT_PUSH_KEY
|
||||||
|
|
||||||
'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版
|
'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版
|
||||||
@@ -194,6 +198,25 @@ def go_cqhttp(title: str, content: str) -> None:
|
|||||||
print("go-cqhttp 推送失败!")
|
print("go-cqhttp 推送失败!")
|
||||||
|
|
||||||
|
|
||||||
|
def gotify(title:str,content:str) -> None:
|
||||||
|
"""
|
||||||
|
使用 gotify 推送消息。
|
||||||
|
"""
|
||||||
|
if not push_config.get("GOTIFY_URL") or not push_config.get("GOTIFY_TOKEN"):
|
||||||
|
print("gotify 服务的 GOTIFY_URL 或 GOTIFY_TOKEN 未设置!!\n取消推送")
|
||||||
|
return
|
||||||
|
print("gotify 服务启动")
|
||||||
|
|
||||||
|
url = f'{push_config.get("GOTIFY_URL")}/message?token={push_config.get("GOTIFY_TOKEN")}'
|
||||||
|
data = {"title": title,"message": content,"priority": push_config.get("GOTIFY_PRIORITY")}
|
||||||
|
response = requests.post(url,data=data).json()
|
||||||
|
|
||||||
|
if response.get("id"):
|
||||||
|
print("gotify 推送成功!")
|
||||||
|
else:
|
||||||
|
print("gotify 推送失败!")
|
||||||
|
|
||||||
|
|
||||||
def iGot(title: str, content: str) -> None:
|
def iGot(title: str, content: str) -> None:
|
||||||
"""
|
"""
|
||||||
使用 iGot 推送消息。
|
使用 iGot 推送消息。
|
||||||
@@ -262,6 +285,7 @@ def pushplus_bot(title: str, content: str) -> None:
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
url_old = "http://pushplus.hxtrip.com/send"
|
url_old = "http://pushplus.hxtrip.com/send"
|
||||||
|
headers["Accept"] = "application/json"
|
||||||
response = requests.post(url=url_old, data=body, headers=headers).json()
|
response = requests.post(url=url_old, data=body, headers=headers).json()
|
||||||
|
|
||||||
if response["code"] == 200:
|
if response["code"] == 200:
|
||||||
@@ -473,6 +497,8 @@ if push_config.get("FSKEY"):
|
|||||||
notify_function.append(feishu_bot)
|
notify_function.append(feishu_bot)
|
||||||
if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"):
|
if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"):
|
||||||
notify_function.append(go_cqhttp)
|
notify_function.append(go_cqhttp)
|
||||||
|
if push_config.get("GOTIFY_URL") and push_config.get("GOTIFY_TOKEN"):
|
||||||
|
notify_function.append(gotify)
|
||||||
if push_config.get("IGOT_PUSH_KEY"):
|
if push_config.get("IGOT_PUSH_KEY"):
|
||||||
notify_function.append(iGot)
|
notify_function.append(iGot)
|
||||||
if push_config.get("PUSH_KEY"):
|
if push_config.get("PUSH_KEY"):
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ cp -f "$repo_path/jbot/requirements.txt" "$dir_root"
|
|||||||
cd $dir_root
|
cd $dir_root
|
||||||
cat requirements.txt | while read LREAD
|
cat requirements.txt | while read LREAD
|
||||||
do
|
do
|
||||||
if test ! -z "$(pip3 show "${LREAD%%=*}" 1>/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
|
||||||
|
|||||||
+4
-10
@@ -39,17 +39,11 @@ copy_dep() {
|
|||||||
reload_pm2() {
|
reload_pm2() {
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
|
|
||||||
if test -z "$(pm2 info panel 1>/dev/null)"; then
|
pm2 delete panel --source-map-support --time &>/dev/null
|
||||||
pm2 reload panel --source-map-support --time &>/dev/null
|
pm2 start $dir_root/build/app.js -n panel --source-map-support --time &>/dev/null
|
||||||
else
|
|
||||||
pm2 start $dir_root/build/app.js -n panel --source-map-support --time &>/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test -z "$(pm2 info schedule 1>/dev/null)"; then
|
pm2 delete schedule --source-map-support --time &>/dev/null
|
||||||
pm2 reload schedule --source-map-support --time &>/dev/null
|
pm2 start $dir_root/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
||||||
else
|
|
||||||
pm2 start $dir_root/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pm2_log() {
|
pm2_log() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ dir_repo=$dir_root/repo
|
|||||||
dir_raw=$dir_root/raw
|
dir_raw=$dir_root/raw
|
||||||
dir_log=$dir_root/log
|
dir_log=$dir_root/log
|
||||||
dir_db=$dir_root/db
|
dir_db=$dir_root/db
|
||||||
|
dir_dep=$dir_root/deps
|
||||||
dir_list_tmp=$dir_log/.tmp
|
dir_list_tmp=$dir_log/.tmp
|
||||||
dir_code=$dir_log/code
|
dir_code=$dir_log/code
|
||||||
dir_update_log=$dir_log/update
|
dir_update_log=$dir_log/update
|
||||||
@@ -34,6 +35,8 @@ file_notify_js=$dir_scripts/sendNotify.js
|
|||||||
task_error_log_path=$dir_log/task_error.log
|
task_error_log_path=$dir_log/task_error.log
|
||||||
nginx_app_conf=$dir_root/docker/front.conf
|
nginx_app_conf=$dir_root/docker/front.conf
|
||||||
nginx_conf=$dir_root/docker/nginx.conf
|
nginx_conf=$dir_root/docker/nginx.conf
|
||||||
|
dep_notify_py=$dir_dep/notify.py
|
||||||
|
dep_notify_js=$dir_dep/sendNotify.js
|
||||||
|
|
||||||
## 清单文件
|
## 清单文件
|
||||||
list_crontab_user=$dir_config/crontab.list
|
list_crontab_user=$dir_config/crontab.list
|
||||||
@@ -154,6 +157,7 @@ fix_config() {
|
|||||||
make_dir $dir_repo
|
make_dir $dir_repo
|
||||||
make_dir $dir_raw
|
make_dir $dir_raw
|
||||||
make_dir $dir_update_log
|
make_dir $dir_update_log
|
||||||
|
make_dir $dir_dep
|
||||||
|
|
||||||
if [[ ! -s $file_config_user ]]; then
|
if [[ ! -s $file_config_user ]]; then
|
||||||
echo -e "复制一份 $file_config_sample 为 $file_config_user,随后请按注释编辑你的配置文件:$file_config_user\n"
|
echo -e "复制一份 $file_config_sample 为 $file_config_user,随后请按注释编辑你的配置文件:$file_config_user\n"
|
||||||
@@ -208,6 +212,19 @@ fix_config() {
|
|||||||
cat /dev/null > /etc/nginx/conf.d/default.conf
|
cat /dev/null > /etc/nginx/conf.d/default.conf
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ! -s $dep_notify_js ]]; then
|
||||||
|
echo -e "复制一份 $file_notify_js_sample 为 $dep_notify_js\n"
|
||||||
|
cp -fv $file_notify_js_sample $dep_notify_js
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -s $dep_notify_py ]]; then
|
||||||
|
echo -e "复制一份 $file_notify_py_sample 为 $dep_notify_py\n"
|
||||||
|
cp -fv $file_notify_py_sample $dep_notify_py
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
npm_install_sub() {
|
npm_install_sub() {
|
||||||
|
|||||||
+23
-10
@@ -106,7 +106,7 @@ run_normal() {
|
|||||||
|
|
||||||
cd $dir_scripts
|
cd $dir_scripts
|
||||||
local relative_path="${first_param%/*}"
|
local relative_path="${first_param%/*}"
|
||||||
if [[ ! -z ${relative_path} ]]; then
|
if [[ ! -z ${relative_path} ]] && [[ ${first_param} =~ "/" ]]; then
|
||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
first_param=${first_param/$relative_path\//}
|
first_param=${first_param/$relative_path\//}
|
||||||
fi
|
fi
|
||||||
@@ -174,7 +174,7 @@ run_concurrent() {
|
|||||||
|
|
||||||
cd $dir_scripts
|
cd $dir_scripts
|
||||||
local relative_path="${first_param%/*}"
|
local relative_path="${first_param%/*}"
|
||||||
if [[ ! -z ${relative_path} ]]; then
|
if [[ ! -z ${relative_path} ]] && [[ ${first_param} =~ "/" ]]; then
|
||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
first_param=${first_param/$relative_path\//}
|
first_param=${first_param/$relative_path\//}
|
||||||
fi
|
fi
|
||||||
@@ -248,7 +248,7 @@ run_designated() {
|
|||||||
|
|
||||||
cd $dir_scripts
|
cd $dir_scripts
|
||||||
local relative_path="${file_param%/*}"
|
local relative_path="${file_param%/*}"
|
||||||
if [[ ! -z ${relative_path} ]]; then
|
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
file_param=${file_param/$relative_path\//}
|
file_param=${file_param/$relative_path\//}
|
||||||
fi
|
fi
|
||||||
@@ -264,13 +264,18 @@ run_designated() {
|
|||||||
|
|
||||||
## 运行其他命令
|
## 运行其他命令
|
||||||
run_else() {
|
run_else() {
|
||||||
local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
local file_param="$1"
|
||||||
local log_dir_tmp="${1##*/}"
|
define_program "$file_param"
|
||||||
local log_dir="$dir_log/${log_dir_tmp%%.*}"
|
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||||
|
log_dir_tmp="${file_param##*/}"
|
||||||
|
log_dir_tmp_path="${file_param%%/*}"
|
||||||
|
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||||
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
|
log_dir="$dir_log/${log_dir_tmp%%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $log_path"
|
cmd="&>> $log_path"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
make_dir "$log_dir"
|
make_dir $log_dir
|
||||||
|
|
||||||
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s" -d "$begin_time")
|
local begin_timestamp=$(date "+%s" -d "$begin_time")
|
||||||
@@ -278,13 +283,21 @@ run_else() {
|
|||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||||
|
|
||||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $first_param" | perl -pe "s|.*ID=(.*) $cmd_task $first_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
local id=$(cat $list_crontab_user | grep -E "$cmd_task $@" | perl -pe "s|.*ID=(.*) $cmd_task $@\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||||
eval . $file_task_before "$@" $cmd
|
eval . $file_task_before "$@" $cmd
|
||||||
|
|
||||||
eval timeout -k 10s $command_timeout_time "$@" $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 . $file_task_after "$@" $cmd
|
shift
|
||||||
|
eval timeout -k 10s $command_timeout_time $which_program "$file_param" "$@" $cmd
|
||||||
|
|
||||||
|
eval . $file_task_after "$file_param" "$@" $cmd
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local end_timestamp=$(date "+%s" -d "$end_time")
|
local end_timestamp=$(date "+%s" -d "$end_time")
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
||||||
|
|||||||
+13
-19
@@ -318,17 +318,11 @@ patch_version() {
|
|||||||
reload_pm2() {
|
reload_pm2() {
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
|
|
||||||
if [[ $(pm2 info panel 2>/dev/null) ]]; then
|
pm2 delete panel --source-map-support --time &>/dev/null
|
||||||
pm2 reload panel --source-map-support --time &>/dev/null
|
pm2 start $dir_root/build/app.js -n panel --source-map-support --time &>/dev/null
|
||||||
else
|
|
||||||
pm2 start $dir_root/build/app.js -n panel --source-map-support --time &>/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $(pm2 info schedule 2>/dev/null) ]]; then
|
pm2 delete schedule --source-map-support --time &>/dev/null
|
||||||
pm2 reload schedule --source-map-support --time &>/dev/null
|
pm2 start $dir_root/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
||||||
else
|
|
||||||
pm2 start $dir_root/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## 对比脚本
|
## 对比脚本
|
||||||
@@ -391,16 +385,11 @@ gen_list_repo() {
|
|||||||
if [[ $blackword ]]; then
|
if [[ $blackword ]]; then
|
||||||
files=$(echo "$files" | egrep -v $blackword)
|
files=$(echo "$files" | egrep -v $blackword)
|
||||||
fi
|
fi
|
||||||
if [[ $dependence ]]; then
|
|
||||||
cd ${dir_scripts}
|
|
||||||
depInScripts=$(eval $cmd | sed 's/^..//' | egrep -v $uniq_path | egrep $dependence)
|
|
||||||
for dep in ${depInScripts}; do
|
|
||||||
file_path=$(dirname $dep)
|
|
||||||
[[ ! $file_path =~ "/" ]] && file_path=""
|
|
||||||
make_dir "${dir_scripts}/${uniq_path}/${file_path#*/}"
|
|
||||||
cp -f $dep "${dir_scripts}/${uniq_path}/${file_path#*/}"
|
|
||||||
done
|
|
||||||
|
|
||||||
|
cp -f $file_notify_js "${dir_scripts}/${uniq_path}"
|
||||||
|
cp -f $file_notify_py "${dir_scripts}/${uniq_path}"
|
||||||
|
|
||||||
|
if [[ $dependence ]]; then
|
||||||
cd ${repo_path}
|
cd ${repo_path}
|
||||||
results=$(eval $cmd | sed 's/^..//' | egrep $dependence)
|
results=$(eval $cmd | sed 's/^..//' | egrep $dependence)
|
||||||
for _file in ${results}; do
|
for _file in ${results}; do
|
||||||
@@ -409,6 +398,11 @@ gen_list_repo() {
|
|||||||
cp -f $_file "${dir_scripts}/${uniq_path}/${file_path}"
|
cp -f $_file "${dir_scripts}/${uniq_path}/${file_path}"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -d $dir_dep ]]; then
|
||||||
|
cp -rf $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
for file in ${files}; do
|
for file in ${files}; do
|
||||||
filename=$(basename $file)
|
filename=$(basename $file)
|
||||||
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
||||||
|
|||||||
@@ -79,6 +79,5 @@ export default {
|
|||||||
fixSiderbar: true,
|
fixSiderbar: true,
|
||||||
contentWidth: 'Fixed',
|
contentWidth: 'Fixed',
|
||||||
splitMenus: false,
|
splitMenus: false,
|
||||||
logo: '/images/qinglong.png',
|
|
||||||
siderWidth: 180,
|
siderWidth: 180,
|
||||||
} as any;
|
} as any;
|
||||||
|
|||||||
+21
-2
@@ -152,8 +152,8 @@
|
|||||||
|
|
||||||
.ant-layout-content.ant-pro-basicLayout-content.ant-pro-basicLayout-has-header {
|
.ant-layout-content.ant-pro-basicLayout-content.ant-pro-basicLayout-has-header {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
min-height: calc(100vh - 66px);
|
min-height: calc(100vh - 72px);
|
||||||
min-height: calc(100vh - var(--vh-offset, 0px) - 66px);
|
min-height: calc(100vh - var(--vh-offset, 0px) - 72px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Resizer {
|
.Resizer {
|
||||||
@@ -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 {
|
||||||
@@ -292,3 +293,21 @@
|
|||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-pro-sider-logo {
|
||||||
|
padding: 16px 8px !important;
|
||||||
|
h1 {
|
||||||
|
margin-left: 5px !important;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 32px !important;
|
||||||
|
border-radius: 52% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-global-header-logo {
|
||||||
|
a img {
|
||||||
|
// 移动端logo被拉伸
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+19
-11
@@ -20,14 +20,14 @@ import './index.less';
|
|||||||
import vhCheck from 'vh-check';
|
import vhCheck from 'vh-check';
|
||||||
import { version, changeLogLink, changeLog } from '../version';
|
import { version, changeLogLink, changeLog } from '../version';
|
||||||
import { useCtx, useTheme } from '@/utils/hooks';
|
import { useCtx, useTheme } from '@/utils/hooks';
|
||||||
import { message, Badge, Modal, Avatar, Dropdown, Menu, Popover } from 'antd';
|
import { message, Badge, Modal, Avatar, Dropdown, Menu, Image } from 'antd';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import SockJS from 'sockjs-client';
|
import SockJS from 'sockjs-client';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Integrations } from '@sentry/tracing';
|
import { Integrations } from '@sentry/tracing';
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://ea2fede373244db99c536210b910d9da@o1051273.ingest.sentry.io/6047851',
|
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
||||||
integrations: [new Integrations.BrowserTracing()],
|
integrations: [new Integrations.BrowserTracing()],
|
||||||
release: version,
|
release: version,
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
@@ -44,7 +44,7 @@ export default function (props: any) {
|
|||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
request.post(`${config.apiPrefix}logout`).then(() => {
|
request.post(`${config.apiPrefix}user/logout`).then(() => {
|
||||||
localStorage.removeItem(config.authKey);
|
localStorage.removeItem(config.authKey);
|
||||||
history.push('/login');
|
history.push('/login');
|
||||||
});
|
});
|
||||||
@@ -232,6 +232,12 @@ export default function (props: any) {
|
|||||||
selectedKeys={[props.location.pathname]}
|
selectedKeys={[props.location.pathname]}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
ErrorBoundary={Sentry.ErrorBoundary}
|
ErrorBoundary={Sentry.ErrorBoundary}
|
||||||
|
logo={
|
||||||
|
<Image
|
||||||
|
preview={false}
|
||||||
|
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||||
|
/>
|
||||||
|
}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<span style={{ fontSize: 16 }}>控制面板</span>
|
<span style={{ fontSize: 16 }}>控制面板</span>
|
||||||
@@ -275,14 +281,16 @@ export default function (props: any) {
|
|||||||
}}
|
}}
|
||||||
onCollapse={setCollapsed}
|
onCollapse={setCollapsed}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
rightContentRender={() => (
|
rightContentRender={() =>
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
ctx.isPhone && (
|
||||||
<span className="side-menu-user-wrapper">
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
<span className="side-menu-user-wrapper">
|
||||||
<span style={{ marginLeft: 5 }}>admin</span>
|
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
||||||
</span>
|
<span style={{ marginLeft: 5 }}>admin</span>
|
||||||
</Dropdown>
|
</span>
|
||||||
)}
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
collapsedButtonRender={(collapsed) => (
|
collapsedButtonRender={(collapsed) => (
|
||||||
<span
|
<span
|
||||||
className="side-menu-container"
|
className="side-menu-container"
|
||||||
|
|||||||
@@ -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>
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
goToScriptManager(record);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{record.name || record._id}{' '}
|
{record.name || record._id}{' '}
|
||||||
{record.isPinned ? (
|
{record.isPinned ? (
|
||||||
<span>
|
<span>
|
||||||
@@ -85,7 +90,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
</span>
|
</a>
|
||||||
),
|
),
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a: any, b: any) => a.name.localeCompare(b.name),
|
compare: (a: any, b: any) => a.name.localeCompare(b.name),
|
||||||
@@ -329,6 +334,24 @@ 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(' ') as string[];
|
||||||
|
if (cmd[0] === 'task') {
|
||||||
|
if (cmd[1].startsWith('/ql/scripts')) {
|
||||||
|
cmd[1] = cmd[1].replace('/ql/scripts/', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -780,7 +803,7 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
<Button key="2" type="primary" onClick={() => addCron()}>
|
<Button key="2" type="primary" onClick={() => addCron()}>
|
||||||
添加任务
|
新建任务
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
header={{
|
header={{
|
||||||
@@ -847,6 +870,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"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const CronModal = ({
|
|||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(cron ? '更新Cron成功' : '添加Cron成功');
|
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
<Button key="2" type="primary" onClick={() => addDependence()}>
|
<Button key="2" type="primary" onClick={() => addDependence()}>
|
||||||
添加依赖
|
新建依赖
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
header={{
|
header={{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.diff-switch-file {
|
.diff-switch-file {
|
||||||
|
min-width: 768px;
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+18
-10
@@ -118,13 +118,17 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
key: 'value',
|
key: 'value',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
width: '44%',
|
width: '35%',
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
showTitle: false,
|
showTitle: false,
|
||||||
},
|
},
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip placement="topLeft" title={text}>
|
<Tooltip
|
||||||
|
placement="topLeft"
|
||||||
|
title={text}
|
||||||
|
trigger={['hover', 'click']}
|
||||||
|
>
|
||||||
<span>{text}</span>
|
<span>{text}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@@ -141,19 +145,23 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
dataIndex: 'timestamp',
|
dataIndex: 'timestamp',
|
||||||
key: 'timestamp',
|
key: 'timestamp',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
width: 164,
|
width: 165,
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
showTitle: false,
|
showTitle: false,
|
||||||
},
|
},
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
const language = navigator.language || navigator.languages[0];
|
const language = navigator.language || navigator.languages[0];
|
||||||
const date = new Date(text)
|
const date = new Date(text)
|
||||||
.toLocaleString(language, {
|
.toLocaleString(language, {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
})
|
})
|
||||||
.replace(' 24:', ' 00:')
|
.replace(' 24:', ' 00:');
|
||||||
return (
|
return (
|
||||||
<Tooltip placement="topLeft" title={date}>
|
<Tooltip
|
||||||
|
placement="topLeft"
|
||||||
|
title={date}
|
||||||
|
trigger={['hover', 'click']}
|
||||||
|
>
|
||||||
<span>{date}</span>
|
<span>{date}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@@ -477,7 +485,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
<Button key="2" type="primary" onClick={() => addEnv()}>
|
<Button key="2" type="primary" onClick={() => addEnv()}>
|
||||||
添加变量
|
新建变量
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
header={{
|
header={{
|
||||||
@@ -528,7 +536,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
dataSource={value}
|
dataSource={value}
|
||||||
rowKey="_id"
|
rowKey="_id"
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ x: 768, y: tableScrollHeight }}
|
scroll={{ x: 1000, y: tableScrollHeight }}
|
||||||
components={components}
|
components={components}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onRow={(record: any, index: number) => {
|
onRow={(record: any, index: number) => {
|
||||||
|
|||||||
Vendored
+2
-2
@@ -40,7 +40,7 @@ const EnvModal = ({
|
|||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(env ? '更新变量成功' : '添加变量成功');
|
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ const EnvModal = ({
|
|||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入环境变量名称', whitespace: true },
|
{ required: true, message: '请输入环境变量名称', whitespace: true },
|
||||||
{
|
{
|
||||||
pattern: /^[a-zA-Z_][0-9a-zA-Z_]+$/,
|
pattern: /^[a-zA-Z_][0-9a-zA-Z_]*$/,
|
||||||
message: '只能输入字母数字下划线,且不能以数字开头',
|
message: '只能输入字母数字下划线,且不能以数字开头',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const Initialization = () => {
|
|||||||
const submitAccountSetting = (values: any) => {
|
const submitAccountSetting = (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}init/user`, {
|
.put(`${config.apiPrefix}user/init`, {
|
||||||
data: {
|
data: {
|
||||||
username: values.username,
|
username: values.username,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
@@ -52,8 +52,9 @@ const Initialization = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitNotification = (values: any) => {
|
const submitNotification = (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}init/notification`, {
|
.put(`${config.apiPrefix}user/notification/init`, {
|
||||||
data: {
|
data: {
|
||||||
...values,
|
...values,
|
||||||
},
|
},
|
||||||
@@ -73,6 +74,10 @@ const Initialization = () => {
|
|||||||
setFields(_fields || []);
|
setFields(_fields || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.removeItem(config.authKey);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: '欢迎使用',
|
title: '欢迎使用',
|
||||||
@@ -225,7 +230,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://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||||
|
/>
|
||||||
<span className={styles.title}>初始化配置</span>
|
<span className={styles.title}>初始化配置</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ function getFilterData(keyword: string, data: any) {
|
|||||||
data.forEach((item: any) => {
|
data.forEach((item: any) => {
|
||||||
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
||||||
tree.push(item);
|
tree.push(item);
|
||||||
expandedKeys.push(...item.children.map((x: any) => x.key));
|
|
||||||
} else {
|
} else {
|
||||||
const children: any[] = [];
|
const children: any[] = [];
|
||||||
(item.children || []).forEach((subItem: any) => {
|
(item.children || []).forEach((subItem: any) => {
|
||||||
@@ -29,7 +28,7 @@ function getFilterData(keyword: string, data: any) {
|
|||||||
...item,
|
...item,
|
||||||
children,
|
children,
|
||||||
});
|
});
|
||||||
expandedKeys.push(...children.map((x) => x.key));
|
expandedKeys.push(item.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -47,6 +46,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [height, setHeight] = useState<number>();
|
const [height, setHeight] = useState<number>();
|
||||||
const treeDom = useRef<any>();
|
const treeDom = useRef<any>();
|
||||||
|
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
const getLogs = () => {
|
const getLogs = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -100,8 +100,12 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
const { tree } = getFilterData(keyword.toLocaleLowerCase(), data);
|
const { tree, expandedKeys } = getFilterData(
|
||||||
|
keyword.toLocaleLowerCase(),
|
||||||
|
data,
|
||||||
|
);
|
||||||
setFilterData(tree);
|
setFilterData(tree);
|
||||||
|
setExpandedKeys(expandedKeys);
|
||||||
},
|
},
|
||||||
[data, setFilterData],
|
[data, setFilterData],
|
||||||
);
|
);
|
||||||
@@ -127,7 +131,6 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
treeData={data}
|
treeData={data}
|
||||||
placeholder="请选择日志文件"
|
placeholder="请选择日志文件"
|
||||||
showSearch
|
showSearch
|
||||||
key="value"
|
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const Login = () => {
|
|||||||
setTwoFactor(false);
|
setTwoFactor(false);
|
||||||
setWaitTime(null);
|
setWaitTime(null);
|
||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}login`, {
|
.post(`${config.apiPrefix}user/login`, {
|
||||||
data: {
|
data: {
|
||||||
username: values.username,
|
username: values.username,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
@@ -113,6 +113,14 @@ const Login = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const codeInputChange = (e: React.ChangeEvent) => {
|
||||||
|
const { value } = e.target as any;
|
||||||
|
const regx = /^[0-9]{6}$/;
|
||||||
|
if (regx.test(value)) {
|
||||||
|
completeTowFactor({ code: value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isAuth = localStorage.getItem(config.authKey);
|
const isAuth = localStorage.getItem(config.authKey);
|
||||||
if (isAuth) {
|
if (isAuth) {
|
||||||
@@ -124,7 +132,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://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||||
|
/>
|
||||||
<span className={styles.title}>
|
<span className={styles.title}>
|
||||||
{twoFactor ? '两步验证' : config.siteName}
|
{twoFactor ? '两步验证' : config.siteName}
|
||||||
</span>
|
</span>
|
||||||
@@ -140,12 +152,15 @@ const Login = () => {
|
|||||||
{
|
{
|
||||||
pattern: /^[0-9]{6}$/,
|
pattern: /^[0-9]{6}$/,
|
||||||
message: '验证码为6位数字',
|
message: '验证码为6位数字',
|
||||||
validateTrigger: 'onBlur',
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
hasFeedback
|
|
||||||
>
|
>
|
||||||
<Input placeholder="6位数字" autoFocus autoComplete="off" />
|
<Input
|
||||||
|
placeholder="6位数字"
|
||||||
|
onChange={codeInputChange}
|
||||||
|
autoFocus
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
+100
-24
@@ -24,72 +24,131 @@ const prefixMap: any = {
|
|||||||
|
|
||||||
const EditModal = ({
|
const EditModal = ({
|
||||||
treeData,
|
treeData,
|
||||||
currentFile,
|
currentNode,
|
||||||
content,
|
content,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
visible,
|
||||||
|
socketMessage,
|
||||||
}: {
|
}: {
|
||||||
treeData?: any;
|
treeData?: any;
|
||||||
currentFile?: string;
|
|
||||||
content?: string;
|
content?: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
socketMessage: any;
|
||||||
|
currentNode: 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 [cNode, setCNode] = useState<any>();
|
||||||
|
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);
|
||||||
const [log, setLog] = useState<string>('');
|
const [log, setLog] = useState<string>('');
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
handleCancel();
|
handleCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
|
if (node.key === selectedKey || !value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newMode = LangMap[value.slice(-3)] || '';
|
const newMode = LangMap[value.slice(-3)] || '';
|
||||||
setFileName(value);
|
setCNode(node);
|
||||||
setLanguage(newMode);
|
setLanguage(newMode);
|
||||||
getDetail(node);
|
getDetail(node);
|
||||||
|
setSelectedKey(node.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request.get(`${config.apiPrefix}scripts/${node.value}`).then((data) => {
|
request
|
||||||
setValue(data.data);
|
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
|
||||||
});
|
.then((data) => {
|
||||||
|
setValue(data.data);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const run = () => {};
|
const run = () => {
|
||||||
|
setLog('');
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}scripts/run`, {
|
||||||
|
data: {
|
||||||
|
filename: cNode.value,
|
||||||
|
path: cNode.parent || '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
setIsRunning(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}scripts/stop`, {
|
||||||
|
data: {
|
||||||
|
filename: cNode.value,
|
||||||
|
path: cNode.parent || '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
setIsRunning(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentFile) {
|
if (!socketMessage) {
|
||||||
setFileName(currentFile);
|
return;
|
||||||
setValue(content as string);
|
|
||||||
}
|
}
|
||||||
}, [currentFile, content]);
|
|
||||||
|
let { type, message: _message, references } = socketMessage;
|
||||||
|
|
||||||
|
if (type !== 'manuallyRunScript') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_message === '执行结束') {
|
||||||
|
_message = '';
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsRunning(false);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
_message = `\n${_message}`;
|
||||||
|
}
|
||||||
|
setLog(`${log}${_message}`);
|
||||||
|
}, [socketMessage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentNode) {
|
||||||
|
setCNode(currentNode);
|
||||||
|
setValue(content as string);
|
||||||
|
setSelectedKey(currentNode.key);
|
||||||
|
}
|
||||||
|
}, [content, currentNode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
className="edit-modal"
|
className="edit-modal"
|
||||||
|
closable={false}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<span style={{ marginRight: 8 }}>{fileName}</span>
|
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
style={{ marginRight: 8, width: 120 }}
|
style={{ marginRight: 8, width: 150 }}
|
||||||
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
|
||||||
value={language}
|
value={language}
|
||||||
style={{ width: 120, marginRight: 8 }}
|
style={{ width: 110, marginRight: 8 }}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setLanguage(e);
|
setLanguage(e);
|
||||||
}}
|
}}
|
||||||
@@ -99,8 +158,12 @@ const EditModal = ({
|
|||||||
<Option value="shell">shell</Option>
|
<Option value="shell">shell</Option>
|
||||||
<Option value="python">python</Option>
|
<Option value="python">python</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Button type="primary" style={{ marginRight: 8 }} onClick={run}>
|
<Button
|
||||||
运行
|
type="primary"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
onClick={isRunning ? stop : run}
|
||||||
|
>
|
||||||
|
{isRunning ? '停止' : '运行'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -129,6 +192,16 @@ const EditModal = ({
|
|||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
onClick={() => {
|
||||||
|
stop();
|
||||||
|
handleCancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
退出
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
@@ -136,7 +209,12 @@ const EditModal = ({
|
|||||||
onClose={cancel}
|
onClose={cancel}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
>
|
>
|
||||||
<SplitPane split="vertical" minSize={200} defaultSize="50%">
|
<SplitPane
|
||||||
|
split="vertical"
|
||||||
|
minSize={200}
|
||||||
|
defaultSize="50%"
|
||||||
|
style={{ height: 'calc(100vh - 55px)' }}
|
||||||
|
>
|
||||||
<Editor
|
<Editor
|
||||||
language={language}
|
language={language}
|
||||||
value={value}
|
value={value}
|
||||||
@@ -151,9 +229,7 @@ const EditModal = ({
|
|||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div>
|
<pre style={{ height: '100%', whiteSpace: 'break-spaces' }}>{log}</pre>
|
||||||
<pre>{log}</pre>
|
|
||||||
</div>
|
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
<SaveModal
|
<SaveModal
|
||||||
visible={saveModalVisible}
|
visible={saveModalVisible}
|
||||||
@@ -164,7 +240,7 @@ const EditModal = ({
|
|||||||
content:
|
content:
|
||||||
editorRef.current &&
|
editorRef.current &&
|
||||||
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
||||||
filename: fileName,
|
filename: cNode?.value,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SettingModal
|
<SettingModal
|
||||||
|
|||||||
@@ -1,28 +1,42 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Modal, message, Input, Form } from 'antd';
|
import { Modal, message, Input, Form, Select } from 'antd';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
const EditScriptNameModal = ({
|
const EditScriptNameModal = ({
|
||||||
handleCancel,
|
handleCancel,
|
||||||
|
treeData,
|
||||||
visible,
|
visible,
|
||||||
}: {
|
}: {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
handleCancel: (file?: { filename: string }) => void;
|
treeData: any[];
|
||||||
|
handleCancel: (file?: {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
key: string;
|
||||||
|
}) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [dirs, setDirs] = useState<any[]>([]);
|
||||||
|
|
||||||
const handleOk = async (values: any) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
values.path = values.path || '';
|
||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}scripts`, {
|
.post(`${config.apiPrefix}scripts`, {
|
||||||
data: { filename: values.filename, content: '' },
|
data: { filename: values.filename, path: values.path, content: '' },
|
||||||
})
|
})
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success('保存文件成功');
|
message.success('保存文件成功');
|
||||||
handleCancel({ filename: values.filename });
|
handleCancel({
|
||||||
|
filename: values.filename,
|
||||||
|
path: values.path,
|
||||||
|
key: `${values.path}-${values.filename}`,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
@@ -33,6 +47,8 @@ const EditScriptNameModal = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
const originDirs = treeData.filter((x) => x.disabled);
|
||||||
|
setDirs([{ key: '' }, ...originDirs]);
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -56,10 +72,22 @@ const EditScriptNameModal = ({
|
|||||||
<Form form={form} layout="vertical" name="edit_name_modal">
|
<Form form={form} layout="vertical" name="edit_name_modal">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="filename"
|
name="filename"
|
||||||
|
label="文件名"
|
||||||
rules={[{ required: true, message: '请输入文件名' }]}
|
rules={[{ required: true, message: '请输入文件名' }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入文件名" />
|
<Input placeholder="请输入文件名" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="父目录"
|
||||||
|
name="path"
|
||||||
|
initialValue={dirs && dirs.length > 0 ? dirs[0].key : ''}
|
||||||
|
>
|
||||||
|
<Select placeholder="请选择父目录">
|
||||||
|
{dirs.map((x) => (
|
||||||
|
<Option value={x.key}>{x.key || '根'}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
+118
-23
@@ -31,20 +31,37 @@ 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;
|
||||||
|
|
||||||
function getFilterData(keyword: string, data: any) {
|
function getFilterData(keyword: string, data: any) {
|
||||||
|
const expandedKeys: string[] = [];
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
const tree: any = [];
|
const tree: any = [];
|
||||||
data.forEach((item: any) => {
|
data.forEach((item: any) => {
|
||||||
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
||||||
tree.push(item);
|
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 };
|
return { tree, expandedKeys };
|
||||||
}
|
}
|
||||||
return { tree: data };
|
return { tree: data, expandedKeys };
|
||||||
}
|
}
|
||||||
|
|
||||||
const LangMap: any = {
|
const LangMap: any = {
|
||||||
@@ -54,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>();
|
||||||
@@ -70,6 +87,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
const [isAddFileModalVisible, setIsAddFileModalVisible] = useState(false);
|
const [isAddFileModalVisible, setIsAddFileModalVisible] = useState(false);
|
||||||
const [currentNode, setCurrentNode] = useState<any>();
|
const [currentNode, setCurrentNode] = useState<any>();
|
||||||
|
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
const getScripts = () => {
|
const getScripts = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -78,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));
|
||||||
};
|
};
|
||||||
@@ -90,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;
|
||||||
@@ -97,12 +132,16 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
setValue('加载中...');
|
setValue('加载中...');
|
||||||
const newMode = value ? LangMap[value.slice(-3)] : '';
|
const newMode = value ? LangMap[value.slice(-3)] : '';
|
||||||
setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode);
|
setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode);
|
||||||
setSelect(value);
|
setSelect(node.key);
|
||||||
setTitle(node.parent || node.value);
|
setTitle(node.parent || node.value);
|
||||||
setCurrentNode(node);
|
setCurrentNode(node);
|
||||||
getDetail(node);
|
getDetail(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onExpand = (expKeys: any) => {
|
||||||
|
setExpandedKeys(expKeys);
|
||||||
|
};
|
||||||
|
|
||||||
const onTreeSelect = useCallback(
|
const onTreeSelect = useCallback(
|
||||||
(keys: Key[], e: any) => {
|
(keys: Key[], e: any) => {
|
||||||
const content = editorRef.current
|
const content = editorRef.current
|
||||||
@@ -131,13 +170,24 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
setSearchValue(keyword);
|
debounceSearch(keyword);
|
||||||
const { tree } = getFilterData(keyword.toLocaleLowerCase(), data);
|
|
||||||
setFilterData(tree);
|
|
||||||
},
|
},
|
||||||
[data, setFilterData],
|
[data, setFilterData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const debounceSearch = useCallback(
|
||||||
|
debounce((keyword) => {
|
||||||
|
setSearchValue(keyword);
|
||||||
|
const { tree, expandedKeys } = getFilterData(
|
||||||
|
keyword.toLocaleLowerCase(),
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
setExpandedKeys(expandedKeys);
|
||||||
|
setFilterData(tree);
|
||||||
|
}, 300),
|
||||||
|
[data, setFilterData],
|
||||||
|
);
|
||||||
|
|
||||||
const editFile = () => {
|
const editFile = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
@@ -147,7 +197,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const cancelEdit = () => {
|
const cancelEdit = () => {
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
setValue('加载中...');
|
setValue('加载中...');
|
||||||
getDetail({ value: select });
|
getDetail(currentNode);
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveFile = () => {
|
const saveFile = () => {
|
||||||
@@ -157,7 +207,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<>
|
<>
|
||||||
确认保存文件
|
确认保存文件
|
||||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
{select}
|
{currentNode.value}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
,保存后不可恢复
|
,保存后不可恢复
|
||||||
</>
|
</>
|
||||||
@@ -170,7 +220,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}scripts`, {
|
.put(`${config.apiPrefix}scripts`, {
|
||||||
data: {
|
data: {
|
||||||
filename: select,
|
filename: currentNode.value,
|
||||||
path: currentNode.parent || '',
|
path: currentNode.parent || '',
|
||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
@@ -210,15 +260,30 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}scripts`, {
|
.delete(`${config.apiPrefix}scripts`, {
|
||||||
data: {
|
data: {
|
||||||
filename: select,
|
filename: currentNode.value,
|
||||||
|
path: currentNode.parent || '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then((_data: any) => {
|
||||||
if (_data.code === 200) {
|
if (_data.code === 200) {
|
||||||
message.success(`删除成功`);
|
message.success(`删除成功`);
|
||||||
let newData = [...data];
|
let newData = [...data];
|
||||||
const index = newData.findIndex((x) => x.value === select);
|
if (currentNode.parent) {
|
||||||
newData.splice(index, 1);
|
const parentNodeIndex = newData.findIndex(
|
||||||
|
(x) => x.key === currentNode.parent,
|
||||||
|
);
|
||||||
|
const parentNode = newData[parentNodeIndex];
|
||||||
|
const index = parentNode.children.findIndex(
|
||||||
|
(y) => y.key === currentNode.key,
|
||||||
|
);
|
||||||
|
parentNode.children.splice(index, 1);
|
||||||
|
newData.splice(parentNodeIndex, 1, { ...parentNode });
|
||||||
|
} else {
|
||||||
|
const index = newData.findIndex(
|
||||||
|
(x) => x.key === currentNode.key,
|
||||||
|
);
|
||||||
|
newData.splice(index, 1);
|
||||||
|
}
|
||||||
setData(newData);
|
setData(newData);
|
||||||
} else {
|
} else {
|
||||||
message.error(_data);
|
message.error(_data);
|
||||||
@@ -236,12 +301,27 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addFileModalClose = (
|
const addFileModalClose = (
|
||||||
{ filename }: { filename: string } = { filename: '' },
|
{ filename, path, key }: { filename: string; path: string; key: string } = {
|
||||||
|
filename: '',
|
||||||
|
path: '',
|
||||||
|
key: '',
|
||||||
|
},
|
||||||
) => {
|
) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const newData = [...data];
|
const newData = [...data];
|
||||||
const _file = { title: filename, key: filename, value: filename };
|
const _file = { title: filename, key, value: filename, parent: path };
|
||||||
newData.unshift(_file);
|
if (path) {
|
||||||
|
const parentNodeIndex = newData.findIndex((x) => x.key === path);
|
||||||
|
const parentNode = newData[parentNodeIndex];
|
||||||
|
if (parentNode.children && parentNode.children.length > 0) {
|
||||||
|
parentNode.children.unshift(_file);
|
||||||
|
} else {
|
||||||
|
parentNode.children = [_file];
|
||||||
|
}
|
||||||
|
newData.splice(parentNodeIndex, 1, { ...parentNode });
|
||||||
|
} else {
|
||||||
|
newData.unshift(_file);
|
||||||
|
}
|
||||||
setData(newData);
|
setData(newData);
|
||||||
onSelect(_file.value, _file);
|
onSelect(_file.value, _file);
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
@@ -253,7 +333,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}scripts/download`, {
|
.post(`${config.apiPrefix}scripts/download`, {
|
||||||
data: {
|
data: {
|
||||||
filename: select,
|
filename: currentNode.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then((_data: any) => {
|
||||||
@@ -261,7 +341,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = select;
|
a.download = currentNode.value;
|
||||||
document.documentElement.appendChild(a);
|
document.documentElement.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.documentElement.removeChild(a);
|
document.documentElement.removeChild(a);
|
||||||
@@ -297,12 +377,22 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
) : (
|
) : (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="add" icon={<PlusOutlined />} onClick={addFile}>
|
<Menu.Item key="add" icon={<PlusOutlined />} onClick={addFile}>
|
||||||
添加
|
新建
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="edit" icon={<EditOutlined />} onClick={editFile}>
|
<Menu.Item
|
||||||
|
key="edit"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={editFile}
|
||||||
|
disabled={!select}
|
||||||
|
>
|
||||||
编辑
|
编辑
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="delete" icon={<DeleteOutlined />} onClick={deleteFile}>
|
<Menu.Item
|
||||||
|
key="delete"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={deleteFile}
|
||||||
|
disabled={!select}
|
||||||
|
>
|
||||||
删除
|
删除
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
@@ -323,7 +413,6 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
treeData={data}
|
treeData={data}
|
||||||
placeholder="请选择脚本文件"
|
placeholder="请选择脚本文件"
|
||||||
showSearch
|
showSearch
|
||||||
key="value"
|
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
@@ -349,6 +438,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
</Tooltip>,
|
</Tooltip>,
|
||||||
<Tooltip title="编辑">
|
<Tooltip title="编辑">
|
||||||
<Button
|
<Button
|
||||||
|
disabled={!select}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={editFile}
|
onClick={editFile}
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
@@ -357,6 +447,7 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<Tooltip title="删除">
|
<Tooltip title="删除">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
disabled={!select}
|
||||||
onClick={deleteFile}
|
onClick={deleteFile}
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
/>
|
/>
|
||||||
@@ -390,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>
|
||||||
@@ -432,14 +525,16 @@ const Script = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<EditModal
|
<EditModal
|
||||||
visible={isLogModalVisible}
|
visible={isLogModalVisible}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
currentFile={select}
|
currentNode={currentNode}
|
||||||
content={value}
|
content={value}
|
||||||
|
socketMessage={socketMessage}
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
setIsLogModalVisible(false);
|
setIsLogModalVisible(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<EditScriptNameModal
|
<EditScriptNameModal
|
||||||
visible={isAddFileModalVisible}
|
visible={isAddFileModalVisible}
|
||||||
|
treeData={data}
|
||||||
handleCancel={addFileModalClose}
|
handleCancel={addFileModalClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const AppModal = ({
|
|||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(app ? '更新应用成功' : '添加应用成功');
|
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
if (data.hasNewVersion) {
|
if (data.hasNewVersion) {
|
||||||
showConfirmUpdateModal(data);
|
showConfirmUpdateModal(data);
|
||||||
} else {
|
} else {
|
||||||
message.success('已经是最新版了!');
|
showForceUpdateModal();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
@@ -39,6 +39,32 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showForceUpdateModal = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
width: 500,
|
||||||
|
title: '更新',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<div>已经是最新版了!</div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
|
||||||
|
青龙 {version} 是目前检测到的最新可用版本了。
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
okText: '确认',
|
||||||
|
cancelText: '强制更新',
|
||||||
|
onCancel() {
|
||||||
|
showUpdatingModal();
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}system/update`)
|
||||||
|
.then((_data: any) => {})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const showConfirmUpdateModal = (data: any) => {
|
const showConfirmUpdateModal = (data: any) => {
|
||||||
const { lastVersion, lastLog } = data;
|
const { lastVersion, lastLog } = data;
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ const Setting = ({
|
|||||||
tabActiveKey === 'app'
|
tabActiveKey === 'app'
|
||||||
? [
|
? [
|
||||||
<Button key="2" type="primary" onClick={() => addApp()}>
|
<Button key="2" type="primary" onClick={() => addApp()}>
|
||||||
添加应用
|
新建应用
|
||||||
</Button>,
|
</Button>,
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ export default {
|
|||||||
name: '任务日志',
|
name: '任务日志',
|
||||||
value: 'logs',
|
value: 'logs',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '依赖管理',
|
||||||
|
value: 'dependencies',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '系统信息',
|
||||||
|
value: 'system',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
scopesMap: {
|
scopesMap: {
|
||||||
crons: '定时任务',
|
crons: '定时任务',
|
||||||
@@ -64,6 +72,7 @@ export default {
|
|||||||
logs: '任务日志',
|
logs: '任务日志',
|
||||||
},
|
},
|
||||||
notificationModes: [
|
notificationModes: [
|
||||||
|
{ value: 'gotify', label: 'Gotify' },
|
||||||
{ value: 'goCqHttpBot', label: 'GoCqHttpBot' },
|
{ value: 'goCqHttpBot', label: 'GoCqHttpBot' },
|
||||||
{ value: 'serverChan', label: 'Server酱' },
|
{ value: 'serverChan', label: 'Server酱' },
|
||||||
{ value: 'bark', label: 'Bark' },
|
{ value: 'bark', label: 'Bark' },
|
||||||
@@ -77,6 +86,15 @@ export default {
|
|||||||
{ value: 'closed', label: '已关闭' },
|
{ value: 'closed', label: '已关闭' },
|
||||||
],
|
],
|
||||||
notificationModeMap: {
|
notificationModeMap: {
|
||||||
|
gotify: [
|
||||||
|
{
|
||||||
|
label: 'gotifyUrl',
|
||||||
|
tip: 'gotify的url地址,例如 https://push.example.de:8080',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{ label: 'gotifyToken', tip: 'gotify的消息应用token码', required: true },
|
||||||
|
{ label: 'gotifyPriority', tip: '推送消息的优先级' },
|
||||||
|
],
|
||||||
goCqHttpBot: [
|
goCqHttpBot: [
|
||||||
{
|
{
|
||||||
label: 'goCqHttpBotUrl',
|
label: 'goCqHttpBotUrl',
|
||||||
|
|||||||
+3
-3
@@ -34,12 +34,12 @@ const errorHandler = function (error: any) {
|
|||||||
|
|
||||||
const _request = extend({ timeout: 60000, params: { t: time }, errorHandler });
|
const _request = extend({ timeout: 60000, params: { t: time }, errorHandler });
|
||||||
const apiWhiteList = [
|
const apiWhiteList = [
|
||||||
'/api/login',
|
'/api/user/login',
|
||||||
'/open/auth/token',
|
'/open/auth/token',
|
||||||
'/api/user/two-factor/login',
|
'/api/user/two-factor/login',
|
||||||
'/api/system',
|
'/api/system',
|
||||||
'/api/init/user',
|
'/api/user/init',
|
||||||
'/api/init/notification',
|
'/api/user/notification/init',
|
||||||
];
|
];
|
||||||
|
|
||||||
_request.interceptors.request.use((url, options) => {
|
_request.interceptors.request.use((url, options) => {
|
||||||
|
|||||||
+8
-8
@@ -1,9 +1,9 @@
|
|||||||
export const version = '2.10.6';
|
export const version = '2.10.13';
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/225';
|
export const changeLogLink = 'https://t.me/jiao_long/233';
|
||||||
export const changeLog = `2.10.6 版本说明
|
export const changeLog = `2.10.13 版本说明
|
||||||
1. 增加各版本自动打包workflow,感谢 https://github.com/fzls PR
|
1. task 命令执行脚本支持任意参数,比如 task xxx.js a b c d
|
||||||
2. 环境变量添加更新时间,感谢 https://github.com/miniers PR
|
2. 应用权限增加依赖管理、系统信息
|
||||||
3. 修复脚本管理结构
|
3. 调试脚本增加停止操作,优化交互
|
||||||
4. 修复定时匹配规则
|
4. 定时任务任务名修改展示效果,感谢https://github.com/tinet-jutt
|
||||||
5. 修复检查更新
|
5. 其他bug修复
|
||||||
`;
|
`;
|
||||||
|
|||||||
+1
-7
@@ -27,13 +27,7 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noEmit": false
|
"noEmit": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*", ".umirc.ts", "typings.d.ts", "back/**/*"],
|
||||||
"src/**/*",
|
|
||||||
"config/**/*",
|
|
||||||
".umirc.ts",
|
|
||||||
"typings.d.ts",
|
|
||||||
"back/**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"lib",
|
"lib",
|
||||||
|
|||||||
@@ -2889,7 +2889,7 @@ chokidar@3.5.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.1"
|
fsevents "~2.3.1"
|
||||||
|
|
||||||
chokidar@^3.2.2:
|
chokidar@^3.2.2, chokidar@^3.5.2:
|
||||||
version "3.5.2"
|
version "3.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
|
||||||
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
|
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
|
||||||
@@ -2995,6 +2995,15 @@ cliui@^6.0.0:
|
|||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
wrap-ansi "^6.2.0"
|
wrap-ansi "^6.2.0"
|
||||||
|
|
||||||
|
cliui@^7.0.2:
|
||||||
|
version "7.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
|
||||||
|
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
|
||||||
|
dependencies:
|
||||||
|
string-width "^4.2.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
wrap-ansi "^7.0.0"
|
||||||
|
|
||||||
clone-response@^1.0.2:
|
clone-response@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||||
@@ -4446,7 +4455,7 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
|
|||||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
||||||
|
|
||||||
get-caller-file@^2.0.1:
|
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||||
@@ -10819,6 +10828,11 @@ y18n@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||||
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||||
|
|
||||||
|
y18n@^5.0.5:
|
||||||
|
version "5.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||||
|
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||||
|
|
||||||
yallist@^2.1.2:
|
yallist@^2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||||
@@ -10842,6 +10856,11 @@ yargs-parser@^18.1.2:
|
|||||||
camelcase "^5.0.0"
|
camelcase "^5.0.0"
|
||||||
decamelize "^1.2.0"
|
decamelize "^1.2.0"
|
||||||
|
|
||||||
|
yargs-parser@^20.2.2:
|
||||||
|
version "20.2.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||||
|
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
||||||
|
|
||||||
yargs@^15.4.1:
|
yargs@^15.4.1:
|
||||||
version "15.4.1"
|
version "15.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||||
@@ -10859,6 +10878,19 @@ yargs@^15.4.1:
|
|||||||
y18n "^4.0.0"
|
y18n "^4.0.0"
|
||||||
yargs-parser "^18.1.2"
|
yargs-parser "^18.1.2"
|
||||||
|
|
||||||
|
yargs@^17.2.1:
|
||||||
|
version "17.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
|
||||||
|
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
|
||||||
|
dependencies:
|
||||||
|
cliui "^7.0.2"
|
||||||
|
escalade "^3.1.1"
|
||||||
|
get-caller-file "^2.0.5"
|
||||||
|
require-directory "^2.1.1"
|
||||||
|
string-width "^4.2.0"
|
||||||
|
y18n "^5.0.5"
|
||||||
|
yargs-parser "^20.2.2"
|
||||||
|
|
||||||
yn@3.1.1:
|
yn@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
|
|||||||
Reference in New Issue
Block a user