mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 11:22:58 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55945e4cd1 | ||
|
|
b39036f8f8 | ||
|
|
f07093d29f | ||
|
|
11c789c71c | ||
|
|
81898f9dd7 | ||
|
|
6dba8ae72d | ||
|
|
c47896e787 | ||
|
|
14cb1f7788 | ||
|
|
892d91edc4 | ||
|
|
74916670eb | ||
|
|
b31eab02d7 | ||
|
|
9c258ab113 | ||
|
|
3bcba11b33 | ||
|
|
15140fd87d | ||
|
|
e83058c3bc | ||
|
|
29f0c2c9ac | ||
|
|
f25fe27473 | ||
|
|
0ae1f284ec | ||
|
|
49c441117a | ||
|
|
7bc5ff01a6 | ||
|
|
8128fb8a9f | ||
|
|
315ae49360 | ||
|
|
35c6475534 | ||
|
|
7f3e60423b | ||
|
|
0a723318a1 | ||
|
|
2c1e7535b8 | ||
|
|
48d1fba699 | ||
|
|
06f861baaf | ||
|
|
bbbd353567 | ||
|
|
8283f62db2 | ||
|
|
1a94a660a5 | ||
|
|
5c393fef36 | ||
|
|
53346a3962 | ||
|
|
4e66a3aff5 | ||
|
|
b79d10b452 | ||
|
|
3dc3ece993 | ||
|
|
792cf9ad20 | ||
|
|
201f41e15b | ||
|
|
71e7ba9cb6 | ||
|
|
a7562259d1 | ||
|
|
18671264bf | ||
|
|
37bcb77be0 | ||
|
|
563457cf1b | ||
|
|
d2cf482d11 | ||
|
|
2713942a68 | ||
|
|
62168979e3 | ||
|
|
f8aba4b1fb | ||
|
|
52a89a21f6 | ||
|
|
ca8790bbec | ||
|
|
be1d68c0d1 | ||
|
|
95afcbc9dd | ||
|
|
20249119a0 | ||
|
|
6e0ee6f627 | ||
|
|
13e7dac333 | ||
|
|
b89dc6dfc6 | ||
|
|
897b710527 | ||
|
|
5afec03548 | ||
|
|
c6a25e5359 | ||
|
|
3777a4e7b4 | ||
|
|
9b7d4cb8fe | ||
|
|
59f7b72bb1 | ||
|
|
4d97f0f44d | ||
|
|
de9e9ce627 | ||
|
|
f056b611a4 | ||
|
|
659484511f | ||
|
|
9ed107980c | ||
|
|
4a3ac7dc4b | ||
|
|
c3a0f58003 | ||
|
|
23781d8b34 | ||
|
|
c1d6a0a40b | ||
|
|
82c9978514 | ||
|
|
9f6d4943ea | ||
|
|
c4a44a48c5 | ||
|
|
d2fef75e01 | ||
|
|
085cb789b5 | ||
|
|
20f615eadf | ||
|
|
66a2769e7c | ||
|
|
899a30eacd | ||
|
|
8760ac2964 | ||
|
|
62a29867a3 | ||
|
|
4938635ef4 |
@@ -2,53 +2,51 @@ name: Build And Push Docker Image
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "*.md"
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- "master"
|
||||||
- 'develop'
|
- "develop"
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "v*"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '00 20 * * *'
|
- cron: "00 20 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
to_gitlab:
|
to_gitlab:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: pixta-dev/repository-mirroring-action@v1
|
- uses: pixta-dev/repository-mirroring-action@v1
|
||||||
with:
|
with:
|
||||||
target_repo_url:
|
target_repo_url: git@gitlab.com:whyour/qinglong.git
|
||||||
git@gitlab.com:whyour/qinglong.git
|
ssh_private_key: ${{ secrets.GITLAB_SSH_PK }}
|
||||||
ssh_private_key:
|
|
||||||
${{ secrets.GITLAB_SSH_PK }}
|
|
||||||
|
|
||||||
to_gitee:
|
to_gitee:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: pixta-dev/repository-mirroring-action@v1
|
- uses: pixta-dev/repository-mirroring-action@v1
|
||||||
with:
|
with:
|
||||||
target_repo_url:
|
target_repo_url: git@gitee.com:whyour/qinglong.git
|
||||||
git@gitee.com:whyour/qinglong.git
|
ssh_private_key: ${{ secrets.GITLAB_SSH_PK }}
|
||||||
ssh_private_key:
|
|
||||||
${{ secrets.GITLAB_SSH_PK }}
|
|
||||||
|
|
||||||
build-static:
|
build-static:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: '8.3.1'
|
version: "8.3.1"
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
cache: 'pnpm'
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: build front and back
|
- name: build front and back
|
||||||
run: |
|
run: |
|
||||||
@@ -80,6 +78,7 @@ jobs:
|
|||||||
export GIT_SSH_COMMAND="ssh -v -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no -l git"
|
export GIT_SSH_COMMAND="ssh -v -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no -l git"
|
||||||
git remote add gitee "${REPO_GITEE}"
|
git remote add gitee "${REPO_GITEE}"
|
||||||
git remote add gitlab "${REPO_GITLAB}"
|
git remote add gitlab "${REPO_GITLAB}"
|
||||||
|
git gc
|
||||||
git push --force --quiet gitee ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
git push --force --quiet gitee ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
||||||
git push --force --quiet gitlab ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
git push --force --quiet gitlab ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
||||||
|
|
||||||
@@ -88,6 +87,7 @@ jobs:
|
|||||||
|
|
||||||
# 由于 ubuntu-latest 中使用 linux6.x 内核 linux/s390x npm 无法使用
|
# 由于 ubuntu-latest 中使用 linux6.x 内核 linux/s390x npm 无法使用
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
# runs-on: self-hosted
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
@@ -95,17 +95,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: '8.3.1'
|
version: "8.3.1"
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
cache: 'pnpm'
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Setup timezone
|
- name: Setup timezone
|
||||||
uses: zcong1993/setup-timezone@master
|
uses: szenius/set-timezone@v1.2
|
||||||
with:
|
with:
|
||||||
timezone: Asia/Shanghai
|
timezoneLinux: Asia/Shanghai
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
@@ -157,6 +157,7 @@ jobs:
|
|||||||
SOURCE_COMMIT=${{ github.sha }}
|
SOURCE_COMMIT=${{ github.sha }}
|
||||||
network: host
|
network: host
|
||||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x,linux/386
|
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x,linux/386
|
||||||
|
# platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386
|
||||||
context: .
|
context: .
|
||||||
file: ./docker/Dockerfile
|
file: ./docker/Dockerfile
|
||||||
push: true
|
push: true
|
||||||
@@ -164,6 +165,71 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=registry,ref=whyour/qinglong:cache
|
cache-from: type=registry,ref=whyour/qinglong:cache
|
||||||
cache-to: type=registry,ref=whyour/qinglong:cache,mode=max
|
cache-to: type=registry,ref=whyour/qinglong:cache,mode=max
|
||||||
|
|
||||||
- name: Image digest
|
- name: Image digest
|
||||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
run: |
|
||||||
|
echo ${{ steps.docker_build.outputs.digest }}
|
||||||
|
|
||||||
|
build310:
|
||||||
|
if: ${{ github.ref_name == 'master' }}
|
||||||
|
needs: build-static
|
||||||
|
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: "8.3.1"
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Setup timezone
|
||||||
|
uses: szenius/set-timezone@v1.2
|
||||||
|
with:
|
||||||
|
timezoneLinux: Asia/Shanghai
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push python3.10
|
||||||
|
id: docker_build_310
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
build-args: |
|
||||||
|
MAINTAINER=${{ github.repository_owner }}
|
||||||
|
QL_BRANCH=${{ github.ref_name }}
|
||||||
|
SOURCE_COMMIT=${{ github.sha }}
|
||||||
|
network: host
|
||||||
|
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x,linux/386
|
||||||
|
context: .
|
||||||
|
file: ./docker/310.Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: whyour/qinglong:python3.10
|
||||||
|
cache-from: type=registry,ref=whyour/qinglong:cache-python3.10
|
||||||
|
cache-to: type=registry,ref=whyour/qinglong:cache-python3.10,mode=max
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: |
|
||||||
|
echo ${{ steps.docker_build_310.outputs.digest }}
|
||||||
|
|||||||
@@ -22,11 +22,4 @@
|
|||||||
.env
|
.env
|
||||||
.history
|
.history
|
||||||
.version.ts
|
.version.ts
|
||||||
|
|
||||||
/config
|
|
||||||
/log
|
|
||||||
/db
|
|
||||||
/manual_log
|
|
||||||
/scripts
|
|
||||||
/bak
|
|
||||||
/.tmp
|
/.tmp
|
||||||
@@ -1,2 +1 @@
|
|||||||
sentrycli_cdnurl=https://npmmirror.com/mirrors/sentry-cli/
|
|
||||||
strict-peer-dependencies=false
|
strict-peer-dependencies=false
|
||||||
+1
-8
@@ -21,12 +21,5 @@ src/.umi-production
|
|||||||
src/.umi-test
|
src/.umi-test
|
||||||
.env.local
|
.env.local
|
||||||
.env
|
.env
|
||||||
history
|
|
||||||
version.ts
|
version.ts
|
||||||
config
|
/.tmp
|
||||||
log
|
|
||||||
db
|
|
||||||
manual_log
|
|
||||||
scripts
|
|
||||||
bak
|
|
||||||
.tmp
|
|
||||||
@@ -47,4 +47,10 @@ export default defineConfig({
|
|||||||
'https://gw.alipayobjects.com/os/lib/react/18.2.0/umd/react.production.min.js',
|
'https://gw.alipayobjects.com/os/lib/react/18.2.0/umd/react.production.min.js',
|
||||||
'https://gw.alipayobjects.com/os/lib/react-dom/18.2.0/umd/react-dom.production.min.js',
|
'https://gw.alipayobjects.com/os/lib/react-dom/18.2.0/umd/react-dom.production.min.js',
|
||||||
],
|
],
|
||||||
|
copy: [
|
||||||
|
{
|
||||||
|
from: 'node_modules/monaco-editor/min/vs',
|
||||||
|
to: 'static/dist/monaco-editor/min/vs',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-18
@@ -56,7 +56,7 @@ npm i @whyour/qinglong
|
|||||||
|
|
||||||
## Built-in commands
|
## Built-in commands
|
||||||
|
|
||||||
1. task
|
- task
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Execute in sequence, if a random delay is set, it will be randomly delayed by a certain number of seconds
|
# Execute in sequence, if a random delay is set, it will be randomly delayed by a certain number of seconds
|
||||||
@@ -71,13 +71,11 @@ task <file_path> conc <env_name> <account_number>(Optional)
|
|||||||
task <file_path> desi <env_name> <account_number>
|
task <file_path> desi <env_name> <account_number>
|
||||||
# Set task timeout
|
# Set task timeout
|
||||||
task -m <max_time> <file_path>
|
task -m <max_time> <file_path>
|
||||||
# Print task log in real time, no need to carry this parameter when creating timed tasks
|
|
||||||
task -l <file_path>
|
|
||||||
# Use -- to split, -- followed by a parameter that is passed to the script, as in the following example, the script receives the parameter -u whyour -p password
|
# Use -- to split, -- followed by a parameter that is passed to the script, as in the following example, the script receives the parameter -u whyour -p password
|
||||||
task <file_path> -- -u whyour -p password
|
task <file_path> -- -u whyour -p password
|
||||||
```
|
```
|
||||||
|
|
||||||
1. ql
|
- ql
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Update and restart Green Dragon
|
# Update and restart Green Dragon
|
||||||
@@ -100,19 +98,17 @@ ql resetlet
|
|||||||
ql resettfa
|
ql resettfa
|
||||||
```
|
```
|
||||||
|
|
||||||
1. parameter description
|
| **Parameter** | **Description** |
|
||||||
|
|---|---|
|
||||||
- file_url: Script address
|
| file_url | Script address |
|
||||||
- repo_url: Repository address
|
| repo_url | Repository address |
|
||||||
- whitelist: The whitelist when pulling the repository, i.e., the string contained in the path of the script to be pulled
|
| whitelist | The whitelist when pulling the repository, i.e., the string contained in the path of the script to be pulled |
|
||||||
- blacklist: Blacklisting when pulling repositories, i.e. strings that are not included in the path of the script to be pulled
|
| blacklist | Blacklisting when pulling repositories, i.e. strings that are not included in the path of the script to be pulled |
|
||||||
- dependence: Pulling the dependencies needed for the repository will be copied directly from the repository to the repository directory under scripts, regardless of the blacklist
|
| dependence | Pulling the dependencies needed for the repository will be copied directly from the repository to the repository directory under scripts, regardless of the blacklist |
|
||||||
- branch: Pull the branch of the repository
|
| extensions | Pull the branch of the repository |
|
||||||
- days: Number of days of logs to be kept
|
| branch | Number of days of logs to be kept |
|
||||||
- file_path: File path for task execution
|
| days | File path for task execution |
|
||||||
- env_name: The name of the environment variable that needs to be concurrent or specified at the time of task execution
|
| file_path | The name of the environment variable that needs to be concurrent or specified at the time of task execution |
|
||||||
- account_number: Specify the account number of an environment variable to be executed when the task is executed
|
|
||||||
- max_time: Timeout, suffix "s" for seconds (default), "m" for minutes, "h" for hours, "d" for days
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
@@ -170,6 +166,14 @@ podman run -dit \
|
|||||||
It is recommended to use a pure system installation to avoid losing the original system data, you need to install node/npm/python3/pip3 yourself
|
It is recommended to use a pure system installation to avoid losing the original system data, you need to install node/npm/python3/pip3 yourself
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Debian/Ubuntu
|
||||||
|
curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||||
|
# Centos
|
||||||
|
curl --silent --location https://rpm.nodesource.com/setup_20.x | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g node-pre-gyp pnpm@8.3.1
|
||||||
npm install -g @whyour/qinglong
|
npm install -g @whyour/qinglong
|
||||||
qinglong
|
qinglong
|
||||||
# Add the environment variables QL_DIR and QL_DATA_DIR when prompted
|
# Add the environment variables QL_DIR and QL_DATA_DIR when prompted
|
||||||
@@ -182,7 +186,7 @@ qinglong
|
|||||||
## Development
|
## Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git clone git@github.com:whyour/qinglong.git
|
$ git clone https://github.com/whyour/qinglong.git
|
||||||
$ cd qinglong
|
$ cd qinglong
|
||||||
$ cp .env.example .env
|
$ cp .env.example .env
|
||||||
# Recommended use pnpm https://pnpm.io/zh/installation
|
# Recommended use pnpm https://pnpm.io/zh/installation
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ npm i @whyour/qinglong
|
|||||||
|
|
||||||
## 内置命令
|
## 内置命令
|
||||||
|
|
||||||
1. task
|
- task
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 依次执行,如果设置了随机延迟,将随机延迟一定秒数
|
# 依次执行,如果设置了随机延迟,将随机延迟一定秒数
|
||||||
@@ -71,13 +71,11 @@ task <file_path> conc <env_name> <account_number>(可选的)
|
|||||||
task <file_path> desi <env_name> <account_number>
|
task <file_path> desi <env_name> <account_number>
|
||||||
# 设置任务超时时间
|
# 设置任务超时时间
|
||||||
task -m <max_time> <file_path>
|
task -m <max_time> <file_path>
|
||||||
# 实时打印任务日志,创建定时任务时,不用携带此参数
|
|
||||||
task -l <file_path>
|
|
||||||
# 使用 -- 分割,-- 后面的参数会传给脚本,下面的例子,脚本就可接收到参数 -u whyour -p password
|
# 使用 -- 分割,-- 后面的参数会传给脚本,下面的例子,脚本就可接收到参数 -u whyour -p password
|
||||||
task <file_path> -- -u whyour -p password
|
task <file_path> -- -u whyour -p password
|
||||||
```
|
```
|
||||||
|
|
||||||
1. ql
|
- ql
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 更新并重启青龙
|
# 更新并重启青龙
|
||||||
@@ -100,20 +98,17 @@ ql resetlet
|
|||||||
ql resettfa
|
ql resettfa
|
||||||
```
|
```
|
||||||
|
|
||||||
1. 参数说明
|
| **参数** | **说明** |
|
||||||
|
|------------|---------------------------------------------------------------------------------------------|
|
||||||
- file_url: 脚本地址
|
| file_url | 脚本地址 |
|
||||||
- repo_url: 仓库地址
|
| repo_url | 仓库地址 |
|
||||||
- whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串,多个竖线分割
|
| whitelist | 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串,多个竖线分割 |
|
||||||
- blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串,多个竖线分割
|
| blacklist | 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串,多个竖线分割 |
|
||||||
- dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响,多个竖线分割
|
| dependence | 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响,多个竖线分割 |
|
||||||
- extensions: 拉取仓库的文件后缀,多个竖线分割
|
| extensions | 拉取仓库的文件后缀,多个竖线分割 |
|
||||||
- branch: 拉取仓库的分支
|
| branch | 拉取仓库的分支 |
|
||||||
- days: 需要保留的日志的天数
|
| days | 需要保留的日志的天数 |
|
||||||
- file_path: 任务执行时的文件路径
|
| file_path | 任务执行时的文件路径 |
|
||||||
- env_name: 任务执行时需要并发或者指定时的环境变量名称
|
|
||||||
- account_number: 任务执行时指定某个环境变量需要执行的账号序号
|
|
||||||
- max_time: 超时时间,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
@@ -171,6 +166,14 @@ podman run -dit \
|
|||||||
建议使用纯净系统安装,避免系统原有数据丢失,需要自己安装 node/npm/python3/pip3
|
建议使用纯净系统安装,避免系统原有数据丢失,需要自己安装 node/npm/python3/pip3
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Debian/Ubuntu
|
||||||
|
curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||||
|
# Centos
|
||||||
|
curl --silent --location https://rpm.nodesource.com/setup_20.x | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g node-pre-gyp pnpm@8.3.1
|
||||||
npm install -g @whyour/qinglong
|
npm install -g @whyour/qinglong
|
||||||
qinglong
|
qinglong
|
||||||
# 根据提示增加环境变量 QL_DIR 和 QL_DATA_DIR
|
# 根据提示增加环境变量 QL_DIR 和 QL_DATA_DIR
|
||||||
@@ -183,7 +186,7 @@ qinglong
|
|||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git clone git@github.com:whyour/qinglong.git
|
$ git clone https://github.com/whyour/qinglong.git
|
||||||
$ cd qinglong
|
$ cd qinglong
|
||||||
$ cp .env.example .env
|
$ cp .env.example .env
|
||||||
# 推荐使用 pnpm https://pnpm.io/zh/installation
|
# 推荐使用 pnpm https://pnpm.io/zh/installation
|
||||||
@@ -192,7 +195,7 @@ $ pnpm install
|
|||||||
$ pnpm start
|
$ pnpm start
|
||||||
```
|
```
|
||||||
|
|
||||||
打开你的浏览器,访问 http://127.0.0.1:5700
|
打开你的浏览器,访问 <http://127.0.0.1:5700>
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
|
|||||||
+38
-20
@@ -3,20 +3,36 @@ import { Router, Request, Response, NextFunction } from 'express';
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs/promises';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { SAMPLE_FILES } from '../config/const';
|
||||||
|
import ConfigService from '../services/config';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/configs', route);
|
app.use('/configs', route);
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/sample',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
res.send({
|
||||||
|
code: 200,
|
||||||
|
data: SAMPLE_FILES,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/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 {
|
||||||
const fileList = fs.readdirSync(config.configPath, 'utf-8');
|
const fileList = await fs.readdir(config.configPath, 'utf-8');
|
||||||
res.send({
|
res.send({
|
||||||
code: 200,
|
code: 200,
|
||||||
data: fileList
|
data: fileList
|
||||||
@@ -32,24 +48,11 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/:file',
|
'/detail',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
try {
|
||||||
let content = '';
|
const configService = Container.get(ConfigService);
|
||||||
if (config.blackFileList.includes(req.params.file)) {
|
await configService.getFile(req.query.path as string, res);
|
||||||
res.send({ code: 403, message: '文件无法访问' });
|
|
||||||
}
|
|
||||||
if (req.params.file.includes('sample')) {
|
|
||||||
content = getFileContentByName(
|
|
||||||
join(config.samplePath, req.params.file),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
content = getFileContentByName(
|
|
||||||
join(config.configPath, req.params.file),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
res.send({ code: 200, data: content });
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
@@ -71,12 +74,27 @@ export default (app: Router) => {
|
|||||||
if (config.blackFileList.includes(name)) {
|
if (config.blackFileList.includes(name)) {
|
||||||
res.send({ code: 403, message: '文件无法访问' });
|
res.send({ code: 403, message: '文件无法访问' });
|
||||||
}
|
}
|
||||||
const path = join(config.configPath, name);
|
let path = join(config.configPath, name);
|
||||||
fs.writeFileSync(path, content);
|
if (name.startsWith('data/scripts/')) {
|
||||||
|
path = join(config.rootPath, name);
|
||||||
|
}
|
||||||
|
await fs.writeFile(path, content);
|
||||||
res.send({ code: 200, message: '保存成功' });
|
res.send({ code: 200, message: '保存成功' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/:file',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const configService = Container.get(ConfigService);
|
||||||
|
await configService.getFile(req.params.file, res);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -134,4 +134,20 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/cancel',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.number().required()),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
await dependenceService.cancel(req.body);
|
||||||
|
return res.send({ code: 200 });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+24
-9
@@ -3,7 +3,7 @@ import { Container } from 'typedi';
|
|||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { emptyDir, getFileContentByName, readDirs } from '../config/util';
|
import { getFileContentByName, readDirs, rmPath } from '../config/util';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
@@ -15,7 +15,7 @@ export default (app: Router) => {
|
|||||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const result = readDirs(config.logPath, config.logPath, blacklist);
|
const result = await readDirs(config.logPath, config.logPath, blacklist);
|
||||||
res.send({
|
res.send({
|
||||||
code: 200,
|
code: 200,
|
||||||
data: result,
|
data: result,
|
||||||
@@ -26,10 +26,29 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/detail',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (blacklist.includes(req.path)) {
|
||||||
|
return res.send({ code: 403, message: '暂无权限' });
|
||||||
|
}
|
||||||
|
const filePath = join(
|
||||||
|
config.logPath,
|
||||||
|
(req.query.path || '') as string,
|
||||||
|
req.query.file as string,
|
||||||
|
);
|
||||||
|
const content = await getFileContentByName(filePath);
|
||||||
|
res.send({ code: 200, data: content });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
try {
|
||||||
if (blacklist.includes(req.path)) {
|
if (blacklist.includes(req.path)) {
|
||||||
return res.send({ code: 403, message: '暂无权限' });
|
return res.send({ code: 403, message: '暂无权限' });
|
||||||
@@ -39,7 +58,7 @@ export default (app: Router) => {
|
|||||||
(req.query.path || '') as string,
|
(req.query.path || '') as string,
|
||||||
req.params.file,
|
req.params.file,
|
||||||
);
|
);
|
||||||
const content = getFileContentByName(filePath);
|
const content = await getFileContentByName(filePath);
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -64,11 +83,7 @@ export default (app: Router) => {
|
|||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
const filePath = join(config.logPath, path, filename);
|
const filePath = join(config.logPath, path, filename);
|
||||||
if (type === 'directory') {
|
await rmPath(filePath);
|
||||||
await emptyDir(filePath);
|
|
||||||
} else {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
}
|
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
|
|||||||
+53
-24
@@ -4,13 +4,13 @@ import {
|
|||||||
readDirs,
|
readDirs,
|
||||||
getLastModifyFilePath,
|
getLastModifyFilePath,
|
||||||
readDir,
|
readDir,
|
||||||
emptyDir,
|
rmPath,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs/promises';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import path, { join, parse } from 'path';
|
import path, { join, parse } from 'path';
|
||||||
import ScriptService from '../services/script';
|
import ScriptService from '../services/script';
|
||||||
@@ -34,15 +34,33 @@ export default (app: Router) => {
|
|||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
let result = [];
|
let result = [];
|
||||||
const blacklist = ['node_modules', '.git', '.pnpm'];
|
const blacklist = [
|
||||||
|
'node_modules',
|
||||||
|
'.git',
|
||||||
|
'.pnpm',
|
||||||
|
'pnpm-lock.yaml',
|
||||||
|
'yarn.lock',
|
||||||
|
'package-lock.json',
|
||||||
|
];
|
||||||
if (req.query.path) {
|
if (req.query.path) {
|
||||||
const targetPath = path.join(
|
const targetPath = path.join(
|
||||||
config.scriptPath,
|
config.scriptPath,
|
||||||
req.query.path as string,
|
req.query.path as string,
|
||||||
);
|
);
|
||||||
result = readDir(targetPath, config.scriptPath, blacklist);
|
result = await readDir(targetPath, config.scriptPath, blacklist);
|
||||||
} else {
|
} else {
|
||||||
result = readDirs(config.scriptPath, config.scriptPath, blacklist);
|
result = await readDirs(
|
||||||
|
config.scriptPath,
|
||||||
|
config.scriptPath,
|
||||||
|
blacklist,
|
||||||
|
(a, b) => {
|
||||||
|
if (a.type === b.type) {
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
} else {
|
||||||
|
return a.type === 'directory' ? -1 : 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
res.send({
|
res.send({
|
||||||
code: 200,
|
code: 200,
|
||||||
@@ -54,17 +72,31 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/detail',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const scriptService = Container.get(ScriptService);
|
||||||
|
const content = await scriptService.getFile(
|
||||||
|
req.query.path as string,
|
||||||
|
req.query.file as string,
|
||||||
|
);
|
||||||
|
res.send({ code: 200, data: content });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/:file',
|
'/:file',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
try {
|
||||||
const filePath = join(
|
const scriptService = Container.get(ScriptService);
|
||||||
config.scriptPath,
|
const content = await scriptService.getFile(
|
||||||
req.query.path as string,
|
req.query.path as string,
|
||||||
req.params.file,
|
req.params.file,
|
||||||
);
|
);
|
||||||
const content = getFileContentByName(filePath);
|
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -104,12 +136,12 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.file) {
|
if (req.file) {
|
||||||
fs.renameSync(req.file.path, join(path, req.file.filename));
|
await fs.rename(req.file.path, join(path, req.file.filename));
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (directory) {
|
if (directory) {
|
||||||
fs.mkdirSync(join(path, directory), { recursive: true });
|
await fs.mkdir(join(path, directory), { recursive: true });
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,16 +153,17 @@ export default (app: Router) => {
|
|||||||
`${originFilename.replace(/\//g, '')}`,
|
`${originFilename.replace(/\//g, '')}`,
|
||||||
);
|
);
|
||||||
const filePath = join(path, `${filename.replace(/\//g, '')}`);
|
const filePath = join(path, `${filename.replace(/\//g, '')}`);
|
||||||
if (fs.existsSync(originFilePath)) {
|
const fileExists = await fileExist(filePath);
|
||||||
fs.copyFileSync(
|
if (fileExists) {
|
||||||
|
await fs.copyFile(
|
||||||
originFilePath,
|
originFilePath,
|
||||||
join(config.bakPath, originFilename.replace(/\//g, '')),
|
join(config.bakPath, originFilename.replace(/\//g, '')),
|
||||||
);
|
);
|
||||||
if (filename !== originFilename) {
|
if (filename !== originFilename) {
|
||||||
fs.unlinkSync(originFilePath);
|
await rmPath(originFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs.writeFileSync(filePath, content);
|
await fs.writeFile(filePath, content);
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -156,7 +189,7 @@ export default (app: Router) => {
|
|||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
const filePath = join(config.scriptPath, path, filename);
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
fs.writeFileSync(filePath, content);
|
await fs.writeFile(filePath, content);
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -182,11 +215,7 @@ export default (app: Router) => {
|
|||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
const filePath = join(config.scriptPath, path, filename);
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
if (type === 'directory') {
|
await rmPath(filePath);
|
||||||
await emptyDir(filePath);
|
|
||||||
} else {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
}
|
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -239,7 +268,7 @@ export default (app: Router) => {
|
|||||||
let { filename, content, path } = req.body;
|
let { filename, content, path } = req.body;
|
||||||
const { name, ext } = parse(filename);
|
const { name, ext } = parse(filename);
|
||||||
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
|
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
|
||||||
fs.writeFileSync(filePath, content || '', { encoding: 'utf8' });
|
await fs.writeFile(filePath, content || '', { encoding: 'utf8' });
|
||||||
|
|
||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const result = await scriptService.runScript(filePath);
|
const result = await scriptService.runScript(filePath);
|
||||||
@@ -269,7 +298,7 @@ export default (app: Router) => {
|
|||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const result = await scriptService.stopScript(filePath, pid);
|
const result = await scriptService.stopScript(filePath, pid);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
emptyDir(logPath);
|
rmPath(logPath);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -297,7 +326,7 @@ export default (app: Router) => {
|
|||||||
};
|
};
|
||||||
const filePath = join(config.scriptPath, path, filename);
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
const newPath = join(config.scriptPath, path, newFilename);
|
const newPath = join(config.scriptPath, path, newFilename);
|
||||||
fs.renameSync(filePath, newPath);
|
await fs.rename(filePath, newPath);
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
|
|||||||
+106
-23
@@ -1,12 +1,11 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs/promises';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import SystemService from '../services/system';
|
import SystemService from '../services/system';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import UserService from '../services/user';
|
import UserService from '../services/user';
|
||||||
import { EnvModel } from '../data/env';
|
|
||||||
import {
|
import {
|
||||||
getUniqPath,
|
getUniqPath,
|
||||||
handleLogPath,
|
handleLogPath,
|
||||||
@@ -78,18 +77,16 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/config',
|
'/config/log-remove-frequency',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
logRemoveFrequency: Joi.number().optional().allow(null),
|
logRemoveFrequency: Joi.number().allow(null),
|
||||||
cronConcurrency: Joi.number().optional().allow(null),
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
try {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const result = await systemService.updateSystemConfig(req.body);
|
const result = await systemService.updateLogRemoveFrequency(req.body);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -97,6 +94,96 @@ export default (app: Router) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/config/cron-concurrency',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
cronConcurrency: Joi.number().allow(null),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.updateCronConcurrency(req.body);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/config/dependence-proxy',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
dependenceProxy: Joi.string().allow('').allow(null),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.updateDependenceProxy(req.body);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/config/node-mirror',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
nodeMirror: Joi.string().allow('').allow(null),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
res.setHeader('Content-type', 'application/octet-stream');
|
||||||
|
await systemService.updateNodeMirror(req.body, res);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/config/python-mirror',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
pythonMirror: Joi.string().allow('').allow(null),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.updatePythonMirror(req.body);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/config/linux-mirror',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
linuxMirror: Joi.string().allow('').allow(null),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
res.setHeader('Content-type', 'application/octet-stream');
|
||||||
|
await systemService.updateLinuxMirror(req.body, res);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/update-check',
|
'/update-check',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -129,7 +216,7 @@ export default (app: Router) => {
|
|||||||
'/reload',
|
'/reload',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
type: Joi.string().required(),
|
type: Joi.string().optional().allow('').allow(null),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -174,7 +261,7 @@ export default (app: Router) => {
|
|||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const command = req.body.command
|
const command = req.body.command;
|
||||||
const idStr = `cat ${config.crontabFile} | grep -E "${command}" | perl -pe "s|.*ID=(.*) ${command}.*|\\1|" | head -1 | awk -F " " '{print $1}' | xargs echo -n`;
|
const idStr = `cat ${config.crontabFile} | grep -E "${command}" | perl -pe "s|.*ID=(.*) ${command}.*|\\1|" | head -1 | awk -F " " '{print $1}' | xargs echo -n`;
|
||||||
let id = await promiseExec(idStr);
|
let id = await promiseExec(idStr);
|
||||||
const uniqPath = await getUniqPath(command, id);
|
const uniqPath = await getUniqPath(command, id);
|
||||||
@@ -193,12 +280,12 @@ export default (app: Router) => {
|
|||||||
onError: async (message: string) => {
|
onError: async (message: string) => {
|
||||||
res.write(`\n${message}`);
|
res.write(`\n${message}`);
|
||||||
const absolutePath = await handleLogPath(logPath);
|
const absolutePath = await handleLogPath(logPath);
|
||||||
fs.appendFileSync(absolutePath, `\n${message}`);
|
await fs.appendFile(absolutePath, `\n${message}`);
|
||||||
},
|
},
|
||||||
onLog: async (message: string) => {
|
onLog: async (message: string) => {
|
||||||
res.write(`\n${message}`);
|
res.write(`\n${message}`);
|
||||||
const absolutePath = await handleLogPath(logPath);
|
const absolutePath = await handleLogPath(logPath);
|
||||||
fs.appendFileSync(absolutePath, `\n${message}`);
|
await fs.appendFile(absolutePath, `\n${message}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -253,16 +340,12 @@ export default (app: Router) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
route.get(
|
route.get('/log', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
'/log',
|
try {
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
const systemService = Container.get(SystemService);
|
||||||
try {
|
await systemService.getSystemLog(res);
|
||||||
const systemService = Container.get(SystemService);
|
} catch (e) {
|
||||||
await systemService.getSystemLog(res);
|
return next(e);
|
||||||
} catch (e) {
|
}
|
||||||
return next(e);
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ export default (app: Router) => {
|
|||||||
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 {
|
||||||
|
if (process.env.DeployEnv === 'demo') {
|
||||||
|
return res.send({ code: 450, message: '未知错误' });
|
||||||
|
}
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
await userService.updateUsernameAndPassword(req.body);
|
await userService.updateUsernameAndPassword(req.body);
|
||||||
res.send({ code: 200, message: '更新成功' });
|
res.send({ code: 200, message: '更新成功' });
|
||||||
|
|||||||
@@ -5,3 +5,21 @@ export const QL_COMMAND = 'ql';
|
|||||||
|
|
||||||
export const TASK_PREFIX = `${TASK_COMMAND} `;
|
export const TASK_PREFIX = `${TASK_COMMAND} `;
|
||||||
export const QL_PREFIX = `${QL_COMMAND} `;
|
export const QL_PREFIX = `${QL_COMMAND} `;
|
||||||
|
|
||||||
|
export const SAMPLE_FILES = [
|
||||||
|
{
|
||||||
|
title: 'config.sample.sh',
|
||||||
|
value: 'sample/config.sample.sh',
|
||||||
|
target: 'config.sh',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'notify.js',
|
||||||
|
value: 'sample/notify.js',
|
||||||
|
target: 'data/scripts/sendNotify.js',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'notify.py',
|
||||||
|
value: 'sample/notify.py',
|
||||||
|
target: 'data/scripts/notify.py',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const configString = 'config sample crontab shareCode diy';
|
|||||||
const versionFile = path.join(rootPath, 'version.yaml');
|
const versionFile = path.join(rootPath, 'version.yaml');
|
||||||
const dataTgzFile = path.join(tmpPath, 'data.tgz');
|
const dataTgzFile = path.join(tmpPath, 'data.tgz');
|
||||||
const shareShellFile = path.join(shellPath, 'share.sh');
|
const shareShellFile = path.join(shellPath, 'share.sh');
|
||||||
|
const dependenceProxyFile = path.join(configPath, 'dependence-proxy.sh');
|
||||||
|
|
||||||
if (envFound.error) {
|
if (envFound.error) {
|
||||||
throw new Error("⚠️ Couldn't find .env file ⚠️");
|
throw new Error("⚠️ Couldn't find .env file ⚠️");
|
||||||
@@ -68,6 +69,7 @@ export default {
|
|||||||
dataPath,
|
dataPath,
|
||||||
dataTgzFile,
|
dataTgzFile,
|
||||||
shareShellFile,
|
shareShellFile,
|
||||||
|
dependenceProxyFile,
|
||||||
configString,
|
configString,
|
||||||
loginFaild,
|
loginFaild,
|
||||||
authError,
|
authError,
|
||||||
@@ -89,6 +91,7 @@ export default {
|
|||||||
'config.sh.sample',
|
'config.sh.sample',
|
||||||
'cookie.sh',
|
'cookie.sh',
|
||||||
'crontab.list',
|
'crontab.list',
|
||||||
|
'dependence-proxy.sh',
|
||||||
'env.sh',
|
'env.sh',
|
||||||
'token.json',
|
'token.json',
|
||||||
],
|
],
|
||||||
|
|||||||
+141
-123
@@ -1,4 +1,4 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import iconv from 'iconv-lite';
|
import iconv from 'iconv-lite';
|
||||||
@@ -13,22 +13,24 @@ import Logger from '../loaders/logger';
|
|||||||
|
|
||||||
export * from './share';
|
export * from './share';
|
||||||
|
|
||||||
export function getFileContentByName(fileName: string) {
|
export async function getFileContentByName(fileName: string) {
|
||||||
if (fs.existsSync(fileName)) {
|
const _exsit = await fileExist(fileName);
|
||||||
return fs.readFileSync(fileName, 'utf8');
|
if (_exsit) {
|
||||||
|
return await fs.readFile(fileName, 'utf8');
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLastModifyFilePath(dir: string) {
|
export async function getLastModifyFilePath(dir: string) {
|
||||||
let filePath = '';
|
let filePath = '';
|
||||||
|
|
||||||
if (fs.existsSync(dir)) {
|
const _exsit = await fileExist(dir);
|
||||||
const arr = fs.readdirSync(dir);
|
if (_exsit) {
|
||||||
|
const arr = await fs.readdir(dir);
|
||||||
|
|
||||||
arr.forEach((item) => {
|
arr.forEach(async (item) => {
|
||||||
const fullpath = path.join(dir, item);
|
const fullpath = path.join(dir, item);
|
||||||
const stats = fs.statSync(fullpath);
|
const stats = await fs.stat(fullpath);
|
||||||
if (stats.isFile()) {
|
if (stats.isFile()) {
|
||||||
if (stats.mtimeMs >= 0) {
|
if (stats.mtimeMs >= 0) {
|
||||||
filePath = fullpath;
|
filePath = fullpath;
|
||||||
@@ -59,7 +61,8 @@ export async function getNetIp(req: any) {
|
|||||||
...req.ips,
|
...req.ips,
|
||||||
req.socket.remoteAddress,
|
req.socket.remoteAddress,
|
||||||
]),
|
]),
|
||||||
];
|
].filter(Boolean);
|
||||||
|
|
||||||
let ip = ipArray[0];
|
let ip = ipArray[0];
|
||||||
|
|
||||||
if (ipArray.length > 1) {
|
if (ipArray.length > 1) {
|
||||||
@@ -80,6 +83,7 @@ export async function getNetIp(req: any) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = ip.substr(ip.lastIndexOf(':') + 1, ip.length);
|
ip = ip.substr(ip.lastIndexOf(':') + 1, ip.length);
|
||||||
if (ip.includes('127.0') || ip.includes('192.168') || ip.includes('10.7')) {
|
if (ip.includes('127.0') || ip.includes('192.168') || ip.includes('10.7')) {
|
||||||
ip = '';
|
ip = '';
|
||||||
@@ -90,29 +94,31 @@ export async function getNetIp(req: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const baiduApi = got
|
const csdnApi = got
|
||||||
.get(`https://www.cip.cc/${ip}`, { timeout: 10000, retry: 0 })
|
.get(`https://searchplugin.csdn.net/api/v1/ip/get?ip=${ip}`, {
|
||||||
|
timeout: 10000,
|
||||||
|
retry: 0,
|
||||||
|
})
|
||||||
.text();
|
.text();
|
||||||
const ipApi = got
|
const pconlineApi = got
|
||||||
.get(`https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`, {
|
.get(`https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`, {
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
retry: 0,
|
retry: 0,
|
||||||
})
|
})
|
||||||
.buffer();
|
.buffer();
|
||||||
const [data, ipApiBody] = await await Promise.all<any>([baiduApi, ipApi]);
|
const [csdnBody, pconlineBody] = await await Promise.all<any>([
|
||||||
|
csdnApi,
|
||||||
const ipRegx = /.*IP :(.*)\n/;
|
pconlineApi,
|
||||||
const addrRegx = /.*数据二 :(.*)\n/;
|
]);
|
||||||
if (data && ipRegx.test(data) && addrRegx.test(data)) {
|
const csdnRes = JSON.parse(csdnBody);
|
||||||
const ip = data.match(ipRegx)[1];
|
const pconlineRes = JSON.parse(iconv.decode(pconlineBody, 'GBK'));
|
||||||
const addr = data.match(addrRegx)[1];
|
let address = '';
|
||||||
return { address: addr, ip };
|
if (csdnBody && csdnRes.code == 200) {
|
||||||
} else if (ipApiBody) {
|
address = csdnRes.data.address;
|
||||||
const { addr, ip } = JSON.parse(iconv.decode(ipApiBody, 'GBK'));
|
} else if (pconlineRes && pconlineRes.addr) {
|
||||||
return { address: `${addr}`, ip };
|
address = pconlineRes.addr;
|
||||||
} else {
|
|
||||||
return { address: `获取失败`, ip };
|
|
||||||
}
|
}
|
||||||
|
return { address, ip };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { address: `获取失败`, ip };
|
return { address: `获取失败`, ip };
|
||||||
}
|
}
|
||||||
@@ -152,22 +158,17 @@ export function getPlatform(userAgent: string): 'mobile' | 'desktop' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fileExist(file: any) {
|
export async function fileExist(file: any) {
|
||||||
return new Promise((resolve) => {
|
try {
|
||||||
try {
|
await fs.access(file);
|
||||||
fs.accessSync(file);
|
return true;
|
||||||
resolve(true);
|
} catch (error) {
|
||||||
} catch (error) {
|
return false;
|
||||||
resolve(false);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createFile(file: string, data: string = '') {
|
export async function createFile(file: string, data: string = '') {
|
||||||
return new Promise((resolve) => {
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
||||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
await fs.writeFile(file, data);
|
||||||
fs.writeFileSync(file, data);
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleLogPath(
|
export async function handleLogPath(
|
||||||
@@ -235,63 +236,71 @@ interface IFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function dirSort(a: IFile, b: IFile): number {
|
export function dirSort(a: IFile, b: IFile): number {
|
||||||
if (a.type !== b.type) {
|
if (a.type === 'file' && b.type === 'file') {
|
||||||
return FileType[a.type] < FileType[b.type] ? -1 : 1
|
return b.mtime - a.mtime;
|
||||||
} else if (a.mtime !== b.mtime) {
|
} else if (a.type === 'directory' && b.type === 'directory') {
|
||||||
return a.mtime > b.mtime ? -1 : 1
|
return a.title.localeCompare(b.title);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return a.type === 'directory' ? -1 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDirs(
|
export async function readDirs(
|
||||||
dir: string,
|
dir: string,
|
||||||
baseDir: string = '',
|
baseDir: string = '',
|
||||||
blacklist: string[] = [],
|
blacklist: string[] = [],
|
||||||
): IFile[] {
|
sort: (a: IFile, b: IFile) => number = dirSort,
|
||||||
|
): Promise<IFile[]> {
|
||||||
const relativePath = path.relative(baseDir, dir);
|
const relativePath = path.relative(baseDir, dir);
|
||||||
const files = fs.readdirSync(dir);
|
const files = await fs.readdir(dir);
|
||||||
const result: IFile[] = files
|
const result: IFile[] = [];
|
||||||
.filter((x) => !blacklist.includes(x))
|
|
||||||
.map((file: string) => {
|
for (const file of files) {
|
||||||
const subPath = path.join(dir, file);
|
const subPath = path.join(dir, file);
|
||||||
const stats = fs.statSync(subPath);
|
const stats = await fs.stat(subPath);
|
||||||
const key = path.join(relativePath, file);
|
const key = path.join(relativePath, file);
|
||||||
if (stats.isDirectory()) {
|
|
||||||
return {
|
if (blacklist.includes(file) || stats.isSymbolicLink()) {
|
||||||
title: file,
|
continue;
|
||||||
key,
|
}
|
||||||
type: 'directory',
|
|
||||||
parent: relativePath,
|
if (stats.isDirectory()) {
|
||||||
mtime: stats.mtime.getTime(),
|
const children = await readDirs(subPath, baseDir, blacklist, sort);
|
||||||
children: readDirs(subPath, baseDir).sort(dirSort),
|
result.push({
|
||||||
};
|
title: file,
|
||||||
}
|
key,
|
||||||
return {
|
type: 'directory',
|
||||||
|
parent: relativePath,
|
||||||
|
mtime: stats.mtime.getTime(),
|
||||||
|
children: children.sort(sort),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
title: file,
|
title: file,
|
||||||
type: 'file',
|
type: 'file',
|
||||||
isLeaf: true,
|
|
||||||
key,
|
key,
|
||||||
parent: relativePath,
|
parent: relativePath,
|
||||||
size: stats.size,
|
size: stats.size,
|
||||||
mtime: stats.mtime.getTime(),
|
mtime: stats.mtime.getTime(),
|
||||||
};
|
});
|
||||||
});
|
}
|
||||||
return result.sort(dirSort);
|
}
|
||||||
|
|
||||||
|
return result.sort(sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDir(
|
export async function readDir(
|
||||||
dir: string,
|
dir: string,
|
||||||
baseDir: string = '',
|
baseDir: string = '',
|
||||||
blacklist: string[] = [],
|
blacklist: string[] = [],
|
||||||
) {
|
) {
|
||||||
const relativePath = path.relative(baseDir, dir);
|
const relativePath = path.relative(baseDir, dir);
|
||||||
const files = fs.readdirSync(dir);
|
const files = await fs.readdir(dir);
|
||||||
const result: any = files
|
const result: any = files
|
||||||
.filter((x) => !blacklist.includes(x))
|
.filter((x) => !blacklist.includes(x))
|
||||||
.map((file: string) => {
|
.map(async (file: string) => {
|
||||||
const subPath = path.join(dir, file);
|
const subPath = path.join(dir, file);
|
||||||
const stats = fs.statSync(subPath);
|
const stats = await fs.stat(subPath);
|
||||||
const key = path.join(relativePath, file);
|
const key = path.join(relativePath, file);
|
||||||
return {
|
return {
|
||||||
title: file,
|
title: file,
|
||||||
@@ -303,27 +312,12 @@ export function readDir(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function emptyDir(path: string) {
|
|
||||||
const pathExist = await fileExist(path);
|
|
||||||
if (!pathExist) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const files = fs.readdirSync(path);
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = `${path}/${file}`;
|
|
||||||
const stats = fs.statSync(filePath);
|
|
||||||
if (stats.isDirectory()) {
|
|
||||||
await emptyDir(filePath);
|
|
||||||
} else {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.rmdirSync(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function promiseExec(command: string): Promise<string> {
|
export async function promiseExec(command: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const { stderr, stdout } = await promisify(exec)(command, { maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' });
|
const { stderr, stdout } = await promisify(exec)(command, {
|
||||||
|
maxBuffer: 200 * 1024 * 1024,
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
return stdout || stderr;
|
return stdout || stderr;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return JSON.stringify(error);
|
return JSON.stringify(error);
|
||||||
@@ -332,7 +326,10 @@ export async function promiseExec(command: string): Promise<string> {
|
|||||||
|
|
||||||
export async function promiseExecSuccess(command: string): Promise<string> {
|
export async function promiseExecSuccess(command: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await promisify(exec)(command, { maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' });
|
const { stdout } = await promisify(exec)(command, {
|
||||||
|
maxBuffer: 200 * 1024 * 1024,
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
return stdout || '';
|
return stdout || '';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '';
|
return '';
|
||||||
@@ -363,37 +360,44 @@ export function parseHeaders(headers: string) {
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseString(input: string): Record<string, string> {
|
||||||
|
const regex = /(\w+):\s*((?:(?!\n\w+:).)*)/g;
|
||||||
|
const matches: Record<string, string> = {};
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(input)) !== null) {
|
||||||
|
const [, key, value] = match;
|
||||||
|
const _key = key.trim();
|
||||||
|
if (!_key || matches[_key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _value = value.trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonValue = JSON.parse(_value);
|
||||||
|
matches[_key] = jsonValue;
|
||||||
|
} catch (error) {
|
||||||
|
matches[_key] = _value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseBody(
|
export function parseBody(
|
||||||
body: string,
|
body: string,
|
||||||
contentType:
|
contentType:
|
||||||
| 'application/json'
|
| 'application/json'
|
||||||
| 'multipart/form-data'
|
| 'multipart/form-data'
|
||||||
| 'application/x-www-form-urlencoded',
|
| 'application/x-www-form-urlencoded'
|
||||||
|
| 'text/plain',
|
||||||
) {
|
) {
|
||||||
if (!body) return '';
|
if (contentType === 'text/plain' || !body) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
const parsed: any = {};
|
const parsed = parseString(body);
|
||||||
let key;
|
|
||||||
let val;
|
|
||||||
let i;
|
|
||||||
|
|
||||||
body &&
|
|
||||||
body.split('\n').forEach(function parser(line) {
|
|
||||||
i = line.indexOf(':');
|
|
||||||
key = line.substring(0, i).trim().toLowerCase();
|
|
||||||
val = line.substring(i + 1).trim();
|
|
||||||
|
|
||||||
if (!key || parsed[key]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const jsonValue = JSON.parse(val);
|
|
||||||
parsed[key] = jsonValue;
|
|
||||||
} catch (error) {
|
|
||||||
parsed[key] = val;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case 'multipart/form-data':
|
case 'multipart/form-data':
|
||||||
@@ -429,14 +433,14 @@ export async function killTask(pid: number) {
|
|||||||
[pid, ...pids].reverse().forEach((x) => {
|
[pid, ...pids].reverse().forEach((x) => {
|
||||||
process.kill(x, 15);
|
process.kill(x, 15);
|
||||||
});
|
});
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
} else {
|
} else {
|
||||||
process.kill(pid, 2);
|
process.kill(pid, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPid(name: string) {
|
export async function getPid(cmd: string) {
|
||||||
const taskCommand = `ps -eo pid,command | grep "${name}" | grep -v grep | awk '{print $1}' | head -1 | xargs echo -n`;
|
const taskCommand = `ps -eo pid,command | grep "${cmd}" | grep -v grep | awk '{print $1}' | head -1 | xargs echo -n`;
|
||||||
const pid = await promiseExec(taskCommand);
|
const pid = await promiseExec(taskCommand);
|
||||||
return pid ? Number(pid) : undefined;
|
return pid ? Number(pid) : undefined;
|
||||||
}
|
}
|
||||||
@@ -449,14 +453,17 @@ interface IVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function parseVersion(path: string): Promise<IVersion> {
|
export async function parseVersion(path: string): Promise<IVersion> {
|
||||||
return load(await promisify(fs.readFile)(path, 'utf8')) as IVersion;
|
return load(await fs.readFile(path, 'utf8')) as IVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseContentVersion(content: string): Promise<IVersion> {
|
export async function parseContentVersion(content: string): Promise<IVersion> {
|
||||||
return load(content) as IVersion;
|
return load(content) as IVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUniqPath(command: string, id: string): Promise<string> {
|
export async function getUniqPath(
|
||||||
|
command: string,
|
||||||
|
id: string,
|
||||||
|
): Promise<string> {
|
||||||
if (/^\d+$/.test(id)) {
|
if (/^\d+$/.test(id)) {
|
||||||
id = `_${id}`;
|
id = `_${id}`;
|
||||||
} else {
|
} else {
|
||||||
@@ -498,7 +505,18 @@ export function safeJSONParse(value?: string) {
|
|||||||
try {
|
try {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error('[JSON.parse失败]', error)
|
Logger.error('[JSON.parse失败]', error);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function rmPath(path: string) {
|
||||||
|
try {
|
||||||
|
const _exsit = await fileExist(path);
|
||||||
|
if (_exsit) {
|
||||||
|
await fs.rm(path, { force: true, recursive: true, maxRetries: 5 });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error('[rmPath失败]', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export enum DependenceStatus {
|
|||||||
'removed',
|
'removed',
|
||||||
'removeFailed',
|
'removeFailed',
|
||||||
'queued',
|
'queued',
|
||||||
|
'cancelled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DependenceTypes {
|
export enum DependenceTypes {
|
||||||
|
|||||||
+2
-2
@@ -66,7 +66,7 @@ export class TelegramBotNotification extends NotificationBaseInfo {
|
|||||||
public telegramBotProxyHost = '';
|
public telegramBotProxyHost = '';
|
||||||
public telegramBotProxyPort = '';
|
public telegramBotProxyPort = '';
|
||||||
public telegramBotProxyAuth = '';
|
public telegramBotProxyAuth = '';
|
||||||
public telegramBotApiHost = 'api.telegram.org';
|
public telegramBotApiHost = 'https://api.telegram.org';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DingtalkBotNotification extends NotificationBaseInfo {
|
export class DingtalkBotNotification extends NotificationBaseInfo {
|
||||||
@@ -112,7 +112,7 @@ export class PushMeNotification extends NotificationBaseInfo {
|
|||||||
export class ChronocatNotification extends NotificationBaseInfo {
|
export class ChronocatNotification extends NotificationBaseInfo {
|
||||||
public chronocatURL: string = '';
|
public chronocatURL: string = '';
|
||||||
public chronocatQQ: string = '';
|
public chronocatQQ: string = '';
|
||||||
public chronocatToekn: string = '';
|
public chronocatToken: string = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebhookNotification extends NotificationBaseInfo {
|
export class WebhookNotification extends NotificationBaseInfo {
|
||||||
|
|||||||
+3
-1
@@ -17,4 +17,6 @@ export type SockMessageType =
|
|||||||
| 'updateSystemVersion'
|
| 'updateSystemVersion'
|
||||||
| 'manuallyRunScript'
|
| 'manuallyRunScript'
|
||||||
| 'runSubscriptionEnd'
|
| 'runSubscriptionEnd'
|
||||||
| 'reloadSystem';
|
| 'reloadSystem'
|
||||||
|
| 'updateNodeMirror'
|
||||||
|
| 'updateLinuxMirror';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { NotificationInfo } from './notify';
|
|||||||
export class AuthInfo {
|
export class AuthInfo {
|
||||||
ip?: string;
|
ip?: string;
|
||||||
type: AuthDataType;
|
type: AuthDataType;
|
||||||
info?: AuthModelInfo;
|
info?: SystemModelInfo;
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
constructor(options: AuthInfo) {
|
constructor(options: AuthInfo) {
|
||||||
@@ -32,6 +32,10 @@ export enum AuthDataType {
|
|||||||
export interface SystemConfigInfo {
|
export interface SystemConfigInfo {
|
||||||
logRemoveFrequency?: number;
|
logRemoveFrequency?: number;
|
||||||
cronConcurrency?: number;
|
cronConcurrency?: number;
|
||||||
|
dependenceProxy?: string;
|
||||||
|
nodeMirror?: string;
|
||||||
|
pythonMirror?: string;
|
||||||
|
linuxMirror?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginLogInfo {
|
export interface LoginLogInfo {
|
||||||
@@ -42,12 +46,12 @@ export interface LoginLogInfo {
|
|||||||
status?: LoginStatus;
|
status?: LoginStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthModelInfo = SystemConfigInfo &
|
export type SystemModelInfo = SystemConfigInfo &
|
||||||
Partial<NotificationInfo> &
|
Partial<NotificationInfo> &
|
||||||
LoginLogInfo;
|
LoginLogInfo;
|
||||||
|
|
||||||
export interface AuthInstance extends Model<AuthInfo, AuthInfo>, AuthInfo {}
|
export interface SystemInstance extends Model<AuthInfo, AuthInfo>, AuthInfo { }
|
||||||
export const AuthModel = sequelize.define<AuthInstance>('Auth', {
|
export const SystemModel = sequelize.define<SystemInstance>('Auth', {
|
||||||
ip: DataTypes.STRING,
|
ip: DataTypes.STRING,
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
info: {
|
info: {
|
||||||
+4
-4
@@ -5,7 +5,7 @@ import { EnvModel } from '../data/env';
|
|||||||
import { CrontabModel } from '../data/cron';
|
import { CrontabModel } from '../data/cron';
|
||||||
import { DependenceModel } from '../data/dependence';
|
import { DependenceModel } from '../data/dependence';
|
||||||
import { AppModel } from '../data/open';
|
import { AppModel } from '../data/open';
|
||||||
import { AuthModel } from '../data/auth';
|
import { SystemModel } from '../data/system';
|
||||||
import { fileExist } from '../config/util';
|
import { fileExist } from '../config/util';
|
||||||
import { SubscriptionModel } from '../data/subscription';
|
import { SubscriptionModel } from '../data/subscription';
|
||||||
import { CrontabViewModel } from '../data/cronView';
|
import { CrontabViewModel } from '../data/cronView';
|
||||||
@@ -17,7 +17,7 @@ export default async () => {
|
|||||||
await CrontabModel.sync();
|
await CrontabModel.sync();
|
||||||
await DependenceModel.sync();
|
await DependenceModel.sync();
|
||||||
await AppModel.sync();
|
await AppModel.sync();
|
||||||
await AuthModel.sync();
|
await SystemModel.sync();
|
||||||
await EnvModel.sync();
|
await EnvModel.sync();
|
||||||
await SubscriptionModel.sync();
|
await SubscriptionModel.sync();
|
||||||
await CrontabViewModel.sync();
|
await CrontabViewModel.sync();
|
||||||
@@ -75,7 +75,7 @@ export default async () => {
|
|||||||
const dependenceCount = await DependenceModel.count();
|
const dependenceCount = await DependenceModel.count();
|
||||||
const envCount = await EnvModel.count();
|
const envCount = await EnvModel.count();
|
||||||
const appCount = await AppModel.count();
|
const appCount = await AppModel.count();
|
||||||
const authCount = await AuthModel.count();
|
const authCount = await SystemModel.count();
|
||||||
if (crondbExist && cronCount === 0) {
|
if (crondbExist && cronCount === 0) {
|
||||||
const cronDb = new DataStore({
|
const cronDb = new DataStore({
|
||||||
filename: cronDbFile,
|
filename: cronDbFile,
|
||||||
@@ -127,7 +127,7 @@ export default async () => {
|
|||||||
});
|
});
|
||||||
authDb.persistence.compactDatafile();
|
authDb.persistence.compactDatafile();
|
||||||
authDb.find({}).exec(async (err, docs) => {
|
authDb.find({}).exec(async (err, docs) => {
|
||||||
await AuthModel.bulkCreate(docs, { ignoreDuplicates: true });
|
await SystemModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-14
@@ -1,20 +1,19 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs/promises';
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
import config from '../config/index';
|
import config from '../config/index';
|
||||||
import { promiseExec } from '../config/util';
|
import { fileExist, promiseExec, rmPath } from '../config/util';
|
||||||
|
|
||||||
function linkToNodeModule(src: string, dst?: string) {
|
async function linkToNodeModule(src: string, dst?: string) {
|
||||||
const target = path.join(config.rootPath, 'node_modules', dst || src);
|
const target = path.join(config.rootPath, 'node_modules', dst || src);
|
||||||
const source = path.join(config.rootPath, src);
|
const source = path.join(config.rootPath, src);
|
||||||
|
|
||||||
fs.lstat(target, (err, stat) => {
|
try {
|
||||||
if (!stat) {
|
const stats = await fs.lstat(target);
|
||||||
fs.symlink(source, target, 'dir', (err) => {
|
if (!stats) {
|
||||||
if (err) throw err;
|
await fs.symlink(source, target, 'dir');
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function linkCommand() {
|
async function linkCommand() {
|
||||||
@@ -34,16 +33,14 @@ async function linkCommand() {
|
|||||||
for (const link of linkShell) {
|
for (const link of linkShell) {
|
||||||
const source = path.join(config.rootPath, 'shell', link.src);
|
const source = path.join(config.rootPath, 'shell', link.src);
|
||||||
const target = path.join(commandDir, link.dest);
|
const target = path.join(commandDir, link.dest);
|
||||||
if (fs.existsSync(target)) {
|
await rmPath(target);
|
||||||
fs.unlinkSync(target);
|
await fs.symlink(source, target);
|
||||||
}
|
|
||||||
fs.symlink(source, target, (err) => { });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (src: string = 'deps') => {
|
export default async (src: string = 'deps') => {
|
||||||
await linkCommand();
|
await linkCommand();
|
||||||
linkToNodeModule(src);
|
await linkToNodeModule(src);
|
||||||
|
|
||||||
const source = path.join(config.rootPath, src);
|
const source = path.join(config.rootPath, src);
|
||||||
const watcher = chokidar.watch(source, {
|
const watcher = chokidar.watch(source, {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import cors from 'cors';
|
|||||||
import routes from '../api';
|
import routes from '../api';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import jwt, { UnauthorizedError } from 'express-jwt';
|
import jwt, { UnauthorizedError } from 'express-jwt';
|
||||||
import fs from 'fs';
|
import fs from 'fs/promises';
|
||||||
import { getPlatform, getToken, safeJSONParse } from '../config/util';
|
import { getPlatform, getToken, safeJSONParse } from '../config/util';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
import OpenService from '../services/open';
|
import OpenService from '../services/open';
|
||||||
@@ -18,7 +18,7 @@ import { serveEnv } from '../config/serverEnv';
|
|||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
|
|
||||||
export default ({ app }: { app: Application }) => {
|
export default ({ app }: { app: Application }) => {
|
||||||
app.enable('trust proxy');
|
app.set('trust proxy', 'loopback');
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.get(`${config.api.prefix}/env.js`, serveEnv);
|
app.get(`${config.api.prefix}/env.js`, serveEnv);
|
||||||
app.use(`${config.api.prefix}/static`, express.static(config.uploadPath));
|
app.use(`${config.api.prefix}/static`, express.static(config.uploadPath));
|
||||||
@@ -29,7 +29,7 @@ export default ({ app }: { app: Application }) => {
|
|||||||
target: `http://0.0.0.0:${config.publicPort}/api`,
|
target: `http://0.0.0.0:${config.publicPort}/api`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
pathRewrite: { '/api/public': '' },
|
pathRewrite: { '/api/public': '' },
|
||||||
logProvider: () => Logger
|
logProvider: () => Logger,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export default ({ app }: { app: Application }) => {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
const data = await fs.readFile(config.authConfigFile, 'utf8');
|
||||||
if (data && headerToken) {
|
if (data && headerToken) {
|
||||||
const { token = '', tokens = {} } = safeJSONParse(data);
|
const { token = '', tokens = {} } = safeJSONParse(data);
|
||||||
if (headerToken === token || tokens[req.platform] === headerToken) {
|
if (headerToken === token || tokens[req.platform] === headerToken) {
|
||||||
@@ -105,7 +105,6 @@ export default ({ app }: { app: Application }) => {
|
|||||||
}
|
}
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
const authInfo = await userService.getUserInfo();
|
const authInfo = await userService.getUserInfo();
|
||||||
const envCount = await EnvModel.count();
|
|
||||||
|
|
||||||
let isInitialized = true;
|
let isInitialized = true;
|
||||||
if (
|
if (
|
||||||
|
|||||||
+50
-16
@@ -9,11 +9,58 @@ import { Op } from 'sequelize';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { CrontabViewModel, CronViewType } from '../data/cronView';
|
import { CrontabViewModel, CronViewType } from '../data/cronView';
|
||||||
import { initPosition } from '../data/env';
|
import { initPosition } from '../data/env';
|
||||||
|
import { AuthDataType, SystemModel } from '../data/system';
|
||||||
|
import SystemService from '../services/system';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const cronService = Container.get(CronService);
|
const cronService = Container.get(CronService);
|
||||||
const envService = Container.get(EnvService);
|
const envService = Container.get(EnvService);
|
||||||
const dependenceService = Container.get(DependenceService);
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
|
||||||
|
const installDependencies = () => {
|
||||||
|
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
||||||
|
DependenceModel.findAll({
|
||||||
|
where: {},
|
||||||
|
order: [
|
||||||
|
['type', 'DESC'],
|
||||||
|
['createdAt', 'DESC'],
|
||||||
|
],
|
||||||
|
raw: true,
|
||||||
|
}).then(async (docs) => {
|
||||||
|
await DependenceModel.update(
|
||||||
|
{ status: DependenceStatus.queued, log: [] },
|
||||||
|
{ where: { id: docs.map((x) => x.id!) } },
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
dependenceService.installDependenceOneByOne(docs);
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化更新 linux/python/nodejs 镜像源配置
|
||||||
|
const systemConfig = await systemService.getSystemConfig();
|
||||||
|
if (systemConfig.info?.pythonMirror) {
|
||||||
|
systemService.updatePythonMirror({
|
||||||
|
pythonMirror: systemConfig.info?.pythonMirror,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (systemConfig.info?.linuxMirror) {
|
||||||
|
systemService.updateLinuxMirror(
|
||||||
|
{
|
||||||
|
linuxMirror: systemConfig.info?.linuxMirror,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
() => installDependencies(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
installDependencies();
|
||||||
|
}
|
||||||
|
if (systemConfig.info?.nodeMirror) {
|
||||||
|
systemService.updateNodeMirror({
|
||||||
|
nodeMirror: systemConfig.info?.nodeMirror,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化新增默认全部任务视图
|
// 初始化新增默认全部任务视图
|
||||||
CrontabViewModel.findAll({
|
CrontabViewModel.findAll({
|
||||||
@@ -32,22 +79,6 @@ export default async () => {
|
|||||||
// 初始化更新所有任务状态为空闲
|
// 初始化更新所有任务状态为空闲
|
||||||
await CrontabModel.update({ status: CrontabStatus.idle }, { where: {} });
|
await CrontabModel.update({ status: CrontabStatus.idle }, { where: {} });
|
||||||
|
|
||||||
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
|
||||||
DependenceModel.findAll({
|
|
||||||
where: {},
|
|
||||||
order: [
|
|
||||||
['type', 'DESC'],
|
|
||||||
['createdAt', 'DESC'],
|
|
||||||
],
|
|
||||||
raw: true,
|
|
||||||
}).then(async (docs) => {
|
|
||||||
await DependenceModel.update(
|
|
||||||
{ status: DependenceStatus.queued, log: [] },
|
|
||||||
{ where: { id: docs.map((x) => x.id!) } },
|
|
||||||
);
|
|
||||||
dependenceService.installDependenceOneByOne(docs);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化时执行一次所有的 ql repo 任务
|
// 初始化时执行一次所有的 ql repo 任务
|
||||||
CrontabModel.findAll({
|
CrontabModel.findAll({
|
||||||
where: {
|
where: {
|
||||||
@@ -127,4 +158,7 @@ export default async () => {
|
|||||||
// 初始化保存一次ck和定时任务数据
|
// 初始化保存一次ck和定时任务数据
|
||||||
await cronService.autosave_crontab();
|
await cronService.autosave_crontab();
|
||||||
await envService.set_envs();
|
await envService.set_envs();
|
||||||
|
|
||||||
|
// 初始化增加系统配置
|
||||||
|
await SystemModel.upsert({ type: AuthDataType.systemConfig });
|
||||||
};
|
};
|
||||||
|
|||||||
+22
-19
@@ -1,7 +1,6 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import dotenv from 'dotenv';
|
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import { fileExist } from '../config/util';
|
import { fileExist } from '../config/util';
|
||||||
|
|
||||||
@@ -48,68 +47,72 @@ export default async () => {
|
|||||||
const TaskAfterFileExist = await fileExist(TaskAfterFile);
|
const TaskAfterFileExist = await fileExist(TaskAfterFile);
|
||||||
|
|
||||||
if (!configDirExist) {
|
if (!configDirExist) {
|
||||||
fs.mkdirSync(configPath);
|
await fs.mkdir(configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scriptDirExist) {
|
if (!scriptDirExist) {
|
||||||
fs.mkdirSync(scriptPath);
|
await fs.mkdir(scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!logDirExist) {
|
if (!logDirExist) {
|
||||||
fs.mkdirSync(logPath);
|
await fs.mkdir(logPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tmpDirExist) {
|
if (!tmpDirExist) {
|
||||||
fs.mkdirSync(tmpPath);
|
await fs.mkdir(tmpPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!uploadDirExist) {
|
if (!uploadDirExist) {
|
||||||
fs.mkdirSync(uploadPath);
|
await fs.mkdir(uploadPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sshDirExist) {
|
if (!sshDirExist) {
|
||||||
fs.mkdirSync(sshPath);
|
await fs.mkdir(sshPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bakDirExist) {
|
if (!bakDirExist) {
|
||||||
fs.mkdirSync(bakPath);
|
await fs.mkdir(bakPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sshdDirExist) {
|
if (!sshdDirExist) {
|
||||||
fs.mkdirSync(sshdPath);
|
await fs.mkdir(sshdPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!systemLogDirExist) {
|
if (!systemLogDirExist) {
|
||||||
fs.mkdirSync(systemLogPath);
|
await fs.mkdir(systemLogPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化文件
|
// 初始化文件
|
||||||
if (!authFileExist) {
|
if (!authFileExist) {
|
||||||
fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile));
|
await fs.writeFile(authConfigFile, await fs.readFile(sampleAuthFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!confFileExist) {
|
if (!confFileExist) {
|
||||||
fs.writeFileSync(confFile, fs.readFileSync(sampleConfigFile));
|
await fs.writeFile(confFile, await fs.readFile(sampleConfigFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scriptNotifyJsFileExist) {
|
if (!scriptNotifyJsFileExist) {
|
||||||
fs.writeFileSync(scriptNotifyJsFile, fs.readFileSync(sampleNotifyJsFile));
|
await fs.writeFile(
|
||||||
|
scriptNotifyJsFile,
|
||||||
|
await fs.readFile(sampleNotifyJsFile),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scriptNotifyPyFileExist) {
|
if (!scriptNotifyPyFileExist) {
|
||||||
fs.writeFileSync(scriptNotifyPyFile, fs.readFileSync(sampleNotifyPyFile));
|
await fs.writeFile(
|
||||||
|
scriptNotifyPyFile,
|
||||||
|
await fs.readFile(sampleNotifyPyFile),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TaskBeforeFileExist) {
|
if (!TaskBeforeFileExist) {
|
||||||
fs.writeFileSync(TaskBeforeFile, fs.readFileSync(sampleTaskShellFile));
|
await fs.writeFile(TaskBeforeFile, await fs.readFile(sampleTaskShellFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TaskAfterFileExist) {
|
if (!TaskAfterFileExist) {
|
||||||
fs.writeFileSync(TaskAfterFile, fs.readFileSync(sampleTaskShellFile));
|
await fs.writeFile(TaskAfterFile, await fs.readFile(sampleTaskShellFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
dotenv.config({ path: confFile });
|
|
||||||
|
|
||||||
Logger.info('✌️ Init file down');
|
Logger.info('✌️ Init file down');
|
||||||
console.log('✌️ Init file down');
|
console.log('✌️ Init file down');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Application } from 'express';
|
import { Application } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import * as Tracing from '@sentry/tracing';
|
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
@@ -16,10 +15,10 @@ export default async ({ expressApp }: { expressApp: Application }) => {
|
|||||||
/UnauthorizedError/i,
|
/UnauthorizedError/i,
|
||||||
/celebrate request validation failed/i,
|
/celebrate request validation failed/i,
|
||||||
],
|
],
|
||||||
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
dsn: 'https://8b5c84cfef3e22541bc84de0ed00497b@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 Sentry.Integrations.Express({ app: expressApp }),
|
||||||
],
|
],
|
||||||
tracesSampleRate: 0.8,
|
tracesSampleRate: 0.8,
|
||||||
release: version,
|
release: version,
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import sockJs from 'sockjs';
|
import sockJs from 'sockjs';
|
||||||
import { Server } from 'http';
|
import { Server } from 'http';
|
||||||
import Logger from './logger';
|
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import SockService from '../services/sock';
|
import SockService from '../services/sock';
|
||||||
import config from '../config/index';
|
import config from '../config/index';
|
||||||
import fs from 'fs';
|
import fs from 'fs/promises';
|
||||||
import { getPlatform, safeJSONParse } from '../config/util';
|
import { getPlatform, safeJSONParse } from '../config/util';
|
||||||
|
|
||||||
export default async ({ server }: { server: Server }) => {
|
export default async ({ server }: { server: Server }) => {
|
||||||
const echo = sockJs.createServer({ prefix: '/api/ws', log: () => {} });
|
const echo = sockJs.createServer({ prefix: '/api/ws', log: () => {} });
|
||||||
const sockService = Container.get(SockService);
|
const sockService = Container.get(SockService);
|
||||||
|
|
||||||
echo.on('connection', (conn) => {
|
echo.on('connection', async (conn) => {
|
||||||
if (!conn.headers || !conn.url || !conn.pathname) {
|
if (!conn.headers || !conn.url || !conn.pathname) {
|
||||||
conn.close('404');
|
conn.close('404');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
const data = await fs.readFile(config.authConfigFile, 'utf8');
|
||||||
const platform = getPlatform(conn.headers['user-agent'] || '') || 'desktop';
|
const platform = getPlatform(conn.headers['user-agent'] || '') || 'desktop';
|
||||||
const headerToken = conn.url.replace(`${conn.pathname}?token=`, '');
|
const headerToken = conn.url.replace(`${conn.pathname}?token=`, '');
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const app = express();
|
|||||||
const client = new HealthClient(
|
const client = new HealthClient(
|
||||||
`0.0.0.0:${config.cronPort}`,
|
`0.0.0.0:${config.cronPort}`,
|
||||||
credentials.createInsecure(),
|
credentials.createInsecure(),
|
||||||
|
{ 'grpc.enable_http_proxy': 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class Client {
|
|||||||
private client = new CronClient(
|
private client = new CronClient(
|
||||||
`0.0.0.0:${config.cronPort}`,
|
`0.0.0.0:${config.cronPort}`,
|
||||||
credentials.createInsecure(),
|
credentials.createInsecure(),
|
||||||
|
{ 'grpc.enable_http_proxy': 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
addCron(request: AddCronRequest['crons']): Promise<AddCronResponse> {
|
addCron(request: AddCronRequest['crons']): Promise<AddCronResponse> {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const check = async (
|
|||||||
switch (call.request.service) {
|
switch (call.request.service) {
|
||||||
case 'cron':
|
case 'cron':
|
||||||
const res = await promiseExec(
|
const res = await promiseExec(
|
||||||
`curl -s http://0.0.0.0:${config.port}/api/system`,
|
`curl -s --noproxy '*' http://0.0.0.0:${config.port}/api/system`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.includes('200')) {
|
if (res.includes('200')) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { check } from './health';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import Logger from '../loaders/logger';
|
import Logger from '../loaders/logger';
|
||||||
|
|
||||||
const server = new Server();
|
const server = new Server({ 'grpc.enable_http_proxy': 0 });
|
||||||
server.addService(HealthService, { check });
|
server.addService(HealthService, { check });
|
||||||
server.addService(CronService, { addCron, delCron });
|
server.addService(CronService, { addCron, delCron });
|
||||||
server.bindAsync(
|
server.bindAsync(
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import path, { join } from 'path';
|
||||||
|
import config from '../config';
|
||||||
|
import { getFileContentByName } from '../config/util';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import got from 'got';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ConfigService {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public async getFile(filePath: string, res: Response) {
|
||||||
|
let content = '';
|
||||||
|
if (config.blackFileList.includes(filePath) || !filePath) {
|
||||||
|
res.send({ code: 403, message: '文件无法访问' });
|
||||||
|
}
|
||||||
|
if (filePath.startsWith('sample/')) {
|
||||||
|
const res = await got.get(
|
||||||
|
`https://gitlab.com/whyour/qinglong/-/raw/master/${filePath}`,
|
||||||
|
);
|
||||||
|
content = res.body;
|
||||||
|
} else if (filePath.startsWith('data/scripts/')) {
|
||||||
|
content = await getFileContentByName(join(config.rootPath, filePath));
|
||||||
|
} else {
|
||||||
|
content = await getFileContentByName(join(config.configPath, filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({ code: 200, data: content });
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
-41
@@ -3,10 +3,15 @@ import winston from 'winston';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
|
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
|
||||||
import { exec, execSync } from 'child_process';
|
import { exec, execSync } from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs/promises';
|
||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
import { getFileContentByName, fileExist, killTask, getUniqPath, safeJSONParse } from '../config/util';
|
import {
|
||||||
import { promises, existsSync } from 'fs';
|
getFileContentByName,
|
||||||
|
fileExist,
|
||||||
|
killTask,
|
||||||
|
getUniqPath,
|
||||||
|
safeJSONParse,
|
||||||
|
} from '../config/util';
|
||||||
import { Op, where, col as colFn, FindOptions, fn } from 'sequelize';
|
import { Op, where, col as colFn, FindOptions, fn } from 'sequelize';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { TASK_PREFIX, QL_PREFIX } from '../config/const';
|
import { TASK_PREFIX, QL_PREFIX } from '../config/const';
|
||||||
@@ -19,11 +24,11 @@ import omit from 'lodash/omit';
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronService {
|
export default class CronService {
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) { }
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
private isSixCron(cron: Crontab) {
|
private isNodeCron(cron: Crontab) {
|
||||||
const { schedule } = cron;
|
const { schedule, extra_schedules } = cron;
|
||||||
if (Number(schedule?.split(/ +/).length) > 5) {
|
if (Number(schedule?.split(/ +/).length) > 5 || extra_schedules?.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -33,9 +38,15 @@ export default class CronService {
|
|||||||
const tab = new Crontab(payload);
|
const tab = new Crontab(payload);
|
||||||
tab.saved = false;
|
tab.saved = false;
|
||||||
const doc = await this.insert(tab);
|
const doc = await this.insert(tab);
|
||||||
if (this.isSixCron(doc) || doc.extra_schedules?.length) {
|
if (this.isNodeCron(doc)) {
|
||||||
await cronClient.addCron([
|
await cronClient.addCron([
|
||||||
{ name: doc.name || '', id: String(doc.id), schedule: doc.schedule!, command: this.makeCommand(doc), extraSchedules: doc.extra_schedules || [] },
|
{
|
||||||
|
name: doc.name || '',
|
||||||
|
id: String(doc.id),
|
||||||
|
schedule: doc.schedule!,
|
||||||
|
command: this.makeCommand(doc),
|
||||||
|
extraSchedules: doc.extra_schedules || [],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await this.set_crontab();
|
await this.set_crontab();
|
||||||
@@ -54,17 +65,17 @@ export default class CronService {
|
|||||||
if (doc.isDisabled === 1) {
|
if (doc.isDisabled === 1) {
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
if (this.isSixCron(doc) || doc.extra_schedules?.length) {
|
if (this.isNodeCron(doc)) {
|
||||||
await cronClient.delCron([String(doc.id)]);
|
await cronClient.delCron([String(doc.id)]);
|
||||||
}
|
}
|
||||||
if (this.isSixCron(newDoc) || newDoc.extra_schedules?.length) {
|
if (this.isNodeCron(newDoc)) {
|
||||||
await cronClient.addCron([
|
await cronClient.addCron([
|
||||||
{
|
{
|
||||||
name: doc.name || '',
|
name: doc.name || '',
|
||||||
id: String(newDoc.id),
|
id: String(newDoc.id),
|
||||||
schedule: newDoc.schedule!,
|
schedule: newDoc.schedule!,
|
||||||
command: this.makeCommand(newDoc),
|
command: this.makeCommand(newDoc),
|
||||||
extraSchedules: newDoc.extra_schedules || []
|
extraSchedules: newDoc.extra_schedules || [],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -107,7 +118,10 @@ export default class CronService {
|
|||||||
if (status === CrontabStatus.idle && log_path !== cron.log_path) {
|
if (status === CrontabStatus.idle && log_path !== cron.log_path) {
|
||||||
options = omit(options, ['status', 'log_path', 'pid']);
|
options = omit(options, ['status', 'log_path', 'pid']);
|
||||||
}
|
}
|
||||||
await CrontabModel.update({ ...pickBy(options, (v) => v === 0 || !!v) }, { where: { id } });
|
await CrontabModel.update(
|
||||||
|
{ ...pickBy(options, (v) => v === 0 || !!v) },
|
||||||
|
{ where: { id } },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,35 +410,45 @@ export default class CronService {
|
|||||||
return taskLimit.runWithCronLimit(() => {
|
return taskLimit.runWithCronLimit(() => {
|
||||||
return new Promise(async (resolve: any) => {
|
return new Promise(async (resolve: any) => {
|
||||||
const cron = await this.getDb({ id: cronId });
|
const cron = await this.getDb({ id: cronId });
|
||||||
const params = { name: cron.name, command: cron.command, schedule: cron.schedule, extraSchedules: cron.extra_schedules };
|
const params = {
|
||||||
|
name: cron.name,
|
||||||
|
command: cron.command,
|
||||||
|
schedule: cron.schedule,
|
||||||
|
extraSchedules: cron.extra_schedules,
|
||||||
|
};
|
||||||
if (cron.status !== CrontabStatus.queued) {
|
if (cron.status !== CrontabStatus.queued) {
|
||||||
resolve(params);
|
resolve(params);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(`[panel][开始执行任务] 参数 ${JSON.stringify(params)}`);
|
this.logger.info(
|
||||||
|
`[panel][开始执行任务] 参数 ${JSON.stringify(params)}`,
|
||||||
|
);
|
||||||
|
|
||||||
let { id, command, log_path } = cron;
|
let { id, command, log_path } = cron;
|
||||||
const uniqPath = await getUniqPath(command, `${id}`);
|
const uniqPath = await getUniqPath(command, `${id}`);
|
||||||
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
|
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
|
||||||
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
|
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
|
||||||
if (log_path?.split('/')?.every(x => x !== uniqPath)) {
|
if (log_path?.split('/')?.every((x) => x !== uniqPath)) {
|
||||||
fs.mkdirSync(logDirPath, { recursive: true });
|
await fs.mkdir(logDirPath, { recursive: true });
|
||||||
}
|
}
|
||||||
const logPath = `${uniqPath}/${logTime}.log`;
|
const logPath = `${uniqPath}/${logTime}.log`;
|
||||||
const absolutePath = path.resolve(config.logPath, `${logPath}`);
|
const absolutePath = path.resolve(config.logPath, `${logPath}`);
|
||||||
|
|
||||||
const cp = spawn(`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron)}`, { shell: '/bin/bash' });
|
const cp = spawn(
|
||||||
|
`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron)}`,
|
||||||
|
{ shell: '/bin/bash' },
|
||||||
|
);
|
||||||
|
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
{ status: CrontabStatus.running, pid: cp.pid, log_path: logPath },
|
{ status: CrontabStatus.running, pid: cp.pid, log_path: logPath },
|
||||||
{ where: { id } },
|
{ where: { id } },
|
||||||
);
|
);
|
||||||
cp.stderr.on('data', (data) => {
|
cp.stderr.on('data', async (data) => {
|
||||||
fs.appendFileSync(`${absolutePath}`, `${data.toString()}`);
|
await fs.appendFile(`${absolutePath}`, `${data.toString()}`);
|
||||||
});
|
});
|
||||||
cp.on('error', (err) => {
|
cp.on('error', async (err) => {
|
||||||
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
|
await fs.appendFile(`${absolutePath}`, `${JSON.stringify(err)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('exit', async (code) => {
|
cp.on('exit', async (code) => {
|
||||||
@@ -448,13 +472,13 @@ export default class CronService {
|
|||||||
await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
||||||
const docs = await CrontabModel.findAll({ where: { id: ids } });
|
const docs = await CrontabModel.findAll({ where: { id: ids } });
|
||||||
const sixCron = docs
|
const sixCron = docs
|
||||||
.filter((x) => this.isSixCron(x))
|
.filter((x) => this.isNodeCron(x))
|
||||||
.map((doc) => ({
|
.map((doc) => ({
|
||||||
name: doc.name || '',
|
name: doc.name || '',
|
||||||
id: String(doc.id),
|
id: String(doc.id),
|
||||||
schedule: doc.schedule!,
|
schedule: doc.schedule!,
|
||||||
command: this.makeCommand(doc),
|
command: this.makeCommand(doc),
|
||||||
extraSchedules: doc.extra_schedules || []
|
extraSchedules: doc.extra_schedules || [],
|
||||||
}));
|
}));
|
||||||
await cronClient.addCron(sixCron);
|
await cronClient.addCron(sixCron);
|
||||||
await this.set_crontab();
|
await this.set_crontab();
|
||||||
@@ -469,7 +493,7 @@ export default class CronService {
|
|||||||
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
|
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
|
||||||
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
||||||
if (logFileExist) {
|
if (logFileExist) {
|
||||||
return getFileContentByName(`${absolutePath}`);
|
return await getFileContentByName(`${absolutePath}`);
|
||||||
} else {
|
} else {
|
||||||
return '任务未运行';
|
return '任务未运行';
|
||||||
}
|
}
|
||||||
@@ -483,15 +507,18 @@ export default class CronService {
|
|||||||
|
|
||||||
const relativeDir = path.dirname(`${doc.log_path}`);
|
const relativeDir = path.dirname(`${doc.log_path}`);
|
||||||
const dir = path.resolve(config.logPath, relativeDir);
|
const dir = path.resolve(config.logPath, relativeDir);
|
||||||
if (existsSync(dir)) {
|
const dirExist = await fileExist(dir);
|
||||||
let files = await promises.readdir(dir);
|
if (dirExist) {
|
||||||
return files
|
let files = await fs.readdir(dir);
|
||||||
.map((x) => ({
|
return (
|
||||||
filename: x,
|
await Promise.all(
|
||||||
directory: relativeDir.replace(config.logPath, ''),
|
files.map(async (x) => ({
|
||||||
time: fs.statSync(`${dir}/${x}`).mtime.getTime(),
|
filename: x,
|
||||||
}))
|
directory: relativeDir.replace(config.logPath, ''),
|
||||||
.sort((a, b) => b.time - a.time);
|
time: (await fs.stat(`${dir}/${x}`)).mtime.getTime(),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
).sort((a, b) => b.time - a.time);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -502,13 +529,15 @@ export default class CronService {
|
|||||||
if (!command.startsWith(TASK_PREFIX) && !command.startsWith(QL_PREFIX)) {
|
if (!command.startsWith(TASK_PREFIX) && !command.startsWith(QL_PREFIX)) {
|
||||||
command = `${TASK_PREFIX}${tab.command}`;
|
command = `${TASK_PREFIX}${tab.command}`;
|
||||||
}
|
}
|
||||||
let commandVariable = `no_tee=true ID=${tab.id} `
|
let commandVariable = `no_tee=true ID=${tab.id} `;
|
||||||
if (tab.task_before) {
|
if (tab.task_before) {
|
||||||
commandVariable += `task_before='${tab.task_before.replace(/'/g, "'\\''")
|
commandVariable += `task_before='${tab.task_before
|
||||||
|
.replace(/'/g, "'\\''")
|
||||||
.trim()}' `;
|
.trim()}' `;
|
||||||
}
|
}
|
||||||
if (tab.task_after) {
|
if (tab.task_after) {
|
||||||
commandVariable += `task_after='${tab.task_after.replace(/'/g, "'\\''")
|
commandVariable += `task_after='${tab.task_after
|
||||||
|
.replace(/'/g, "'\\''")
|
||||||
.trim()}' `;
|
.trim()}' `;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +550,11 @@ export default class CronService {
|
|||||||
var crontab_string = '';
|
var crontab_string = '';
|
||||||
tabs.data.forEach((tab) => {
|
tabs.data.forEach((tab) => {
|
||||||
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
||||||
if (tab.isDisabled === 1 || _schedule!.length !== 5 || tab.extra_schedules?.length) {
|
if (
|
||||||
|
tab.isDisabled === 1 ||
|
||||||
|
_schedule!.length !== 5 ||
|
||||||
|
tab.extra_schedules?.length
|
||||||
|
) {
|
||||||
crontab_string += '# ';
|
crontab_string += '# ';
|
||||||
crontab_string += tab.schedule;
|
crontab_string += tab.schedule;
|
||||||
crontab_string += ' ';
|
crontab_string += ' ';
|
||||||
@@ -535,7 +568,7 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(config.crontabFile, crontab_string);
|
await fs.writeFile(config.crontabFile, crontab_string);
|
||||||
|
|
||||||
execSync(`crontab ${config.crontabFile}`);
|
execSync(`crontab ${config.crontabFile}`);
|
||||||
await CrontabModel.update({ saved: true }, { where: {} });
|
await CrontabModel.update({ saved: true }, { where: {} });
|
||||||
@@ -580,13 +613,13 @@ export default class CronService {
|
|||||||
this.set_crontab(tabs);
|
this.set_crontab(tabs);
|
||||||
|
|
||||||
const sixCron = tabs.data
|
const sixCron = tabs.data
|
||||||
.filter((x) => this.isSixCron(x) && x.isDisabled !== 1)
|
.filter((x) => this.isNodeCron(x) && x.isDisabled !== 1)
|
||||||
.map((doc) => ({
|
.map((doc) => ({
|
||||||
name: doc.name || '',
|
name: doc.name || '',
|
||||||
id: String(doc.id),
|
id: String(doc.id),
|
||||||
schedule: doc.schedule!,
|
schedule: doc.schedule!,
|
||||||
command: this.makeCommand(doc),
|
command: this.makeCommand(doc),
|
||||||
extraSchedules: doc.extra_schedules || []
|
extraSchedules: doc.extra_schedules || [],
|
||||||
}));
|
}));
|
||||||
await cronClient.addCron(sixCron);
|
await cronClient.addCron(sixCron);
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-18
@@ -14,7 +14,12 @@ import {
|
|||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'cross-spawn';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import { FindOptions, Op } from 'sequelize';
|
import { FindOptions, Op } from 'sequelize';
|
||||||
import { promiseExecSuccess } from '../config/util';
|
import {
|
||||||
|
fileExist,
|
||||||
|
getPid,
|
||||||
|
killTask,
|
||||||
|
promiseExecSuccess,
|
||||||
|
} from '../config/util';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import taskLimit from '../shared/pLimit';
|
import taskLimit from '../shared/pLimit';
|
||||||
|
|
||||||
@@ -61,12 +66,23 @@ export default class DependenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async remove(ids: number[], force = false): Promise<Dependence[]> {
|
public async remove(ids: number[], force = false): Promise<Dependence[]> {
|
||||||
await DependenceModel.update(
|
|
||||||
{ status: DependenceStatus.queued, log: [] },
|
|
||||||
{ where: { id: ids } },
|
|
||||||
);
|
|
||||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||||
this.installDependenceOneByOne(docs, false, force);
|
const unInstalledDeps = docs.filter(
|
||||||
|
(x) => x.status !== DependenceStatus.installed,
|
||||||
|
);
|
||||||
|
const installedDeps = docs.filter(
|
||||||
|
(x) => x.status === DependenceStatus.installed,
|
||||||
|
);
|
||||||
|
await this.removeDb(unInstalledDeps.map((x) => x.id!));
|
||||||
|
|
||||||
|
if (installedDeps.length) {
|
||||||
|
await DependenceModel.update(
|
||||||
|
{ status: DependenceStatus.queued, log: [] },
|
||||||
|
{ where: { id: ids } },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.installDependenceOneByOne(docs, false, force);
|
||||||
|
}
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +91,21 @@ export default class DependenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async dependencies(
|
public async dependencies(
|
||||||
{ searchValue, type }: { searchValue: string; type: string },
|
{
|
||||||
sort: any = { position: -1 },
|
searchValue,
|
||||||
|
type,
|
||||||
|
status,
|
||||||
|
}: { searchValue: string; type: string; status: string },
|
||||||
|
sort: any = [],
|
||||||
query: any = {},
|
query: any = {},
|
||||||
): Promise<Dependence[]> {
|
): Promise<Dependence[]> {
|
||||||
let condition = { ...query, type: DependenceTypes[type as any] };
|
let condition = {
|
||||||
|
...query,
|
||||||
|
type: DependenceTypes[type as any],
|
||||||
|
};
|
||||||
|
if (status) {
|
||||||
|
condition.status = status.split(',').map(Number);
|
||||||
|
}
|
||||||
if (searchValue) {
|
if (searchValue) {
|
||||||
const encodeText = encodeURI(searchValue);
|
const encodeText = encodeURI(searchValue);
|
||||||
const reg = {
|
const reg = {
|
||||||
@@ -95,7 +121,7 @@ export default class DependenceService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await this.find(condition);
|
const result = await this.find(condition, sort);
|
||||||
return result as any;
|
return result as any;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -119,10 +145,32 @@ export default class DependenceService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||||
this.installDependenceOneByOne(docs);
|
this.installDependenceOneByOne(docs, true, true);
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async cancel(ids: number[]) {
|
||||||
|
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||||
|
for (const doc of docs) {
|
||||||
|
taskLimit.removeQueuedDependency(doc);
|
||||||
|
const depInstallCommand = InstallDependenceCommandTypes[doc.type];
|
||||||
|
const depUnInstallCommand = unInstallDependenceCommandTypes[doc.type];
|
||||||
|
const installCmd = `${depInstallCommand} ${doc.name.trim()}`;
|
||||||
|
const unInstallCmd = `${depUnInstallCommand} ${doc.name.trim()}`;
|
||||||
|
const pids = await Promise.all([
|
||||||
|
getPid(installCmd),
|
||||||
|
getPid(unInstallCmd),
|
||||||
|
]);
|
||||||
|
for (const pid of pids) {
|
||||||
|
pid && (await killTask(pid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await DependenceModel.update(
|
||||||
|
{ status: DependenceStatus.cancelled },
|
||||||
|
{ where: { id: ids } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async find(query: any, sort: any = []): Promise<Dependence[]> {
|
private async find(query: any, sort: any = []): Promise<Dependence[]> {
|
||||||
const docs = await DependenceModel.findAll({
|
const docs = await DependenceModel.findAll({
|
||||||
where: { ...query },
|
where: { ...query },
|
||||||
@@ -157,8 +205,14 @@ export default class DependenceService {
|
|||||||
isInstall: boolean = true,
|
isInstall: boolean = true,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
) {
|
) {
|
||||||
return taskLimit.runOneByOne(() => {
|
return taskLimit.runDependeny(dependency, () => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
|
if (taskLimit.firstDependencyId !== dependency.id) {
|
||||||
|
return resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
taskLimit.removeQueuedDependency(dependency);
|
||||||
|
|
||||||
const depIds = [dependency.id!];
|
const depIds = [dependency.id!];
|
||||||
const status = isInstall
|
const status = isInstall
|
||||||
? DependenceStatus.installing
|
? DependenceStatus.installing
|
||||||
@@ -188,7 +242,7 @@ export default class DependenceService {
|
|||||||
this.updateLog(depIds, message);
|
this.updateLog(depIds, message);
|
||||||
|
|
||||||
// 判断是否已经安装过依赖
|
// 判断是否已经安装过依赖
|
||||||
if (isInstall) {
|
if (isInstall && !force) {
|
||||||
const getCommandPrefix = GetDependenceCommandTypes[dependency.type];
|
const getCommandPrefix = GetDependenceCommandTypes[dependency.type];
|
||||||
const depVersionStr = versionDependenceCommandTypes[dependency.type];
|
const depVersionStr = versionDependenceCommandTypes[dependency.type];
|
||||||
let depVersion = '';
|
let depVersion = '';
|
||||||
@@ -241,10 +295,18 @@ export default class DependenceService {
|
|||||||
return resolve(null);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const dependenceProxyFileExist = await fileExist(
|
||||||
const cp = spawn(`${depRunCommand} ${dependency.name.trim()}`, {
|
config.dependenceProxyFile,
|
||||||
shell: '/bin/bash',
|
);
|
||||||
});
|
const proxyStr = dependenceProxyFileExist
|
||||||
|
? `source ${config.dependenceProxyFile} &&`
|
||||||
|
: '';
|
||||||
|
const cp = spawn(
|
||||||
|
`${proxyStr} ${depRunCommand} ${dependency.name.trim()}`,
|
||||||
|
{
|
||||||
|
shell: '/bin/bash',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
cp.stdout.on('data', async (data) => {
|
cp.stdout.on('data', async (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
@@ -298,7 +360,17 @@ export default class DependenceService {
|
|||||||
? DependenceStatus.installFailed
|
? DependenceStatus.installFailed
|
||||||
: DependenceStatus.removeFailed;
|
: DependenceStatus.removeFailed;
|
||||||
}
|
}
|
||||||
await DependenceModel.update({ status }, { where: { id: depIds } });
|
const docs = await DependenceModel.findAll({ where: { id: depIds } });
|
||||||
|
const _docIds = docs
|
||||||
|
.filter((x) => x.status !== DependenceStatus.cancelled)
|
||||||
|
.map((x) => x.id!);
|
||||||
|
|
||||||
|
if (_docIds.length > 0) {
|
||||||
|
await DependenceModel.update(
|
||||||
|
{ status },
|
||||||
|
{ where: { id: _docIds } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 如果删除依赖成功或者强制删除
|
// 如果删除依赖成功或者强制删除
|
||||||
if ((isSucceed || force) && !isInstall) {
|
if ((isSucceed || force) && !isInstall) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs/promises';
|
||||||
import {
|
import {
|
||||||
Env,
|
Env,
|
||||||
EnvModel,
|
EnvModel,
|
||||||
@@ -208,6 +208,6 @@ export default class EnvService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs.writeFileSync(config.envFile, env_string);
|
await fs.writeFile(config.envFile, env_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-8
@@ -57,7 +57,7 @@ export default class NotificationService {
|
|||||||
try {
|
try {
|
||||||
return await notificationModeAction?.call(this);
|
return await notificationModeAction?.call(this);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return false;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -234,8 +234,8 @@ export default class NotificationService {
|
|||||||
telegramBotUserId,
|
telegramBotUserId,
|
||||||
} = this.params;
|
} = this.params;
|
||||||
const authStr = telegramBotProxyAuth ? `${telegramBotProxyAuth}@` : '';
|
const authStr = telegramBotProxyAuth ? `${telegramBotProxyAuth}@` : '';
|
||||||
const url = `https://${
|
const url = `${
|
||||||
telegramBotApiHost ? telegramBotApiHost : 'api.telegram.org'
|
telegramBotApiHost ? telegramBotApiHost : 'https://api.telegram.org'
|
||||||
}/bot${telegramBotToken}/sendMessage`;
|
}/bot${telegramBotToken}/sendMessage`;
|
||||||
let agent;
|
let agent;
|
||||||
if (telegramBotProxyHost && telegramBotProxyPort) {
|
if (telegramBotProxyHost && telegramBotProxyPort) {
|
||||||
@@ -562,7 +562,7 @@ export default class NotificationService {
|
|||||||
throw new Error(JSON.stringify(info));
|
throw new Error(JSON.stringify(info));
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error(error.response ? error.response.body : error);
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +591,7 @@ export default class NotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async chronocat() {
|
private async chronocat() {
|
||||||
const { chronocatURL, chronocatQQ, chronocatToekn } = this.params;
|
const { chronocatURL, chronocatQQ, chronocatToken } = this.params;
|
||||||
try {
|
try {
|
||||||
const user_ids = chronocatQQ
|
const user_ids = chronocatQQ
|
||||||
.match(/user_id=(\d+)/g)
|
.match(/user_id=(\d+)/g)
|
||||||
@@ -603,7 +603,7 @@ export default class NotificationService {
|
|||||||
const url = `${chronocatURL}/api/message/send`;
|
const url = `${chronocatURL}/api/message/send`;
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${chronocatToekn}`,
|
Authorization: `Bearer ${chronocatToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [chat_type, ids] of [
|
for (const [chat_type, ids] of [
|
||||||
@@ -634,7 +634,7 @@ export default class NotificationService {
|
|||||||
json: data,
|
json: data,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
if (res.body === 'success') {
|
if (res.statusCode === 200) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.body);
|
throw new Error(res.body);
|
||||||
@@ -660,9 +660,11 @@ export default class NotificationService {
|
|||||||
webhookUrl,
|
webhookUrl,
|
||||||
webhookBody,
|
webhookBody,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!formatUrl && !formatBody) {
|
if (!formatUrl && !formatBody) {
|
||||||
return false;
|
throw new Error('Url 或者 Body 中必须包含 $title');
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = parseHeaders(webhookHeaders);
|
const headers = parseHeaders(webhookHeaders);
|
||||||
const body = parseBody(formatBody, webhookContentType);
|
const body = parseBody(formatBody, webhookContentType);
|
||||||
const bodyParam = this.formatBody(webhookContentType, body);
|
const bodyParam = this.formatBody(webhookContentType, body);
|
||||||
@@ -693,6 +695,7 @@ export default class NotificationService {
|
|||||||
case 'multipart/form-data':
|
case 'multipart/form-data':
|
||||||
return { form: body };
|
return { form: body };
|
||||||
case 'application/x-www-form-urlencoded':
|
case 'application/x-www-form-urlencoded':
|
||||||
|
case 'text/plain':
|
||||||
return { body };
|
return { body };
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
+13
-10
@@ -1,13 +1,12 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import fs from 'fs';
|
import path, { join } from 'path';
|
||||||
import path from 'path';
|
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import CronService from './cron';
|
import CronService from './cron';
|
||||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { TASK_COMMAND } from '../config/const';
|
import { TASK_COMMAND } from '../config/const';
|
||||||
import { getPid, killTask } from '../config/util';
|
import { getFileContentByName, getPid, killTask, rmPath } from '../config/util';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ScriptService {
|
export default class ScriptService {
|
||||||
@@ -16,14 +15,12 @@ export default class ScriptService {
|
|||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
private cronService: CronService,
|
private cronService: CronService,
|
||||||
private scheduleService: ScheduleService,
|
private scheduleService: ScheduleService,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
private taskCallbacks(filePath: string): TaskCallbacks {
|
private taskCallbacks(filePath: string): TaskCallbacks {
|
||||||
return {
|
return {
|
||||||
onEnd: async (cp, endTime, diff) => {
|
onEnd: async (cp, endTime, diff) => {
|
||||||
try {
|
await rmPath(filePath);
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
} catch (error) { }
|
|
||||||
},
|
},
|
||||||
onError: async (message: string) => {
|
onError: async (message: string) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
@@ -42,7 +39,7 @@ export default class ScriptService {
|
|||||||
|
|
||||||
public async runScript(filePath: string) {
|
public async runScript(filePath: string) {
|
||||||
const relativePath = path.relative(config.scriptPath, filePath);
|
const relativePath = path.relative(config.scriptPath, filePath);
|
||||||
const command = `${TASK_COMMAND} -l ${relativePath} now`;
|
const command = `${TASK_COMMAND} ${relativePath} now`;
|
||||||
const pid = await this.scheduleService.runTask(
|
const pid = await this.scheduleService.runTask(
|
||||||
command,
|
command,
|
||||||
this.taskCallbacks(filePath),
|
this.taskCallbacks(filePath),
|
||||||
@@ -56,12 +53,18 @@ export default class ScriptService {
|
|||||||
public async stopScript(filePath: string, pid: number) {
|
public async stopScript(filePath: string, pid: number) {
|
||||||
if (!pid) {
|
if (!pid) {
|
||||||
const relativePath = path.relative(config.scriptPath, filePath);
|
const relativePath = path.relative(config.scriptPath, filePath);
|
||||||
pid = await getPid(`${TASK_COMMAND} -l ${relativePath} now`) as number;
|
pid = (await getPid(`${TASK_COMMAND} ${relativePath} now`)) as number;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await killTask(pid);
|
await killTask(pid);
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
|
|
||||||
return { code: 200 };
|
return { code: 200 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getFile(filePath: string, fileName: string) {
|
||||||
|
const _filePath = join(config.scriptPath, filePath, fileName);
|
||||||
|
const content = await getFileContentByName(_filePath);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-32
@@ -1,11 +1,12 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import fs, { existsSync } from 'fs';
|
import fs from 'fs/promises';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Subscription } from '../data/subscription';
|
import { Subscription } from '../data/subscription';
|
||||||
import { formatUrl } from '../config/subscription';
|
import { formatUrl } from '../config/subscription';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
import { fileExist, rmPath } from '../config/util';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SshKeyService {
|
export default class SshKeyService {
|
||||||
@@ -18,15 +19,16 @@ export default class SshKeyService {
|
|||||||
this.initSshConfigFile();
|
this.initSshConfigFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initSshConfigFile() {
|
private async initSshConfigFile() {
|
||||||
let config = '';
|
let config = '';
|
||||||
if (existsSync(this.sshConfigFilePath)) {
|
const _exist = await fileExist(this.sshConfigFilePath);
|
||||||
config = fs.readFileSync(this.sshConfigFilePath, { encoding: 'utf-8' });
|
if (_exist) {
|
||||||
|
config = await fs.readFile(this.sshConfigFilePath, { encoding: 'utf-8' });
|
||||||
} else {
|
} else {
|
||||||
fs.writeFileSync(this.sshConfigFilePath, '');
|
await fs.writeFile(this.sshConfigFilePath, '');
|
||||||
}
|
}
|
||||||
if (!config.includes(this.sshConfigHeader)) {
|
if (!config.includes(this.sshConfigHeader)) {
|
||||||
fs.writeFileSync(
|
await fs.writeFile(
|
||||||
this.sshConfigFilePath,
|
this.sshConfigFilePath,
|
||||||
`${this.sshConfigHeader}\n\n${config}`,
|
`${this.sshConfigHeader}\n\n${config}`,
|
||||||
{ encoding: 'utf-8' },
|
{ encoding: 'utf-8' },
|
||||||
@@ -34,9 +36,12 @@ export default class SshKeyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private generatePrivateKeyFile(alias: string, key: string): void {
|
private async generatePrivateKeyFile(
|
||||||
|
alias: string,
|
||||||
|
key: string,
|
||||||
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(path.join(this.sshPath, alias), `${key}${os.EOL}`, {
|
await fs.writeFile(path.join(this.sshPath, alias), `${key}${os.EOL}`, {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
mode: '400',
|
mode: '400',
|
||||||
});
|
});
|
||||||
@@ -45,18 +50,20 @@ export default class SshKeyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removePrivateKeyFile(alias: string): void {
|
private async removePrivateKeyFile(alias: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const filePath = path.join(this.sshPath, alias);
|
const filePath = path.join(this.sshPath, alias);
|
||||||
if (existsSync(filePath)) {
|
await rmPath(filePath);
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('删除私钥文件失败', error);
|
this.logger.error('删除私钥文件失败', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateSingleSshConfig(alias: string, host: string, proxy?: string) {
|
private async generateSingleSshConfig(
|
||||||
|
alias: string,
|
||||||
|
host: string,
|
||||||
|
proxy?: string,
|
||||||
|
) {
|
||||||
if (host === 'github.com') {
|
if (host === 'github.com') {
|
||||||
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa`;
|
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa`;
|
||||||
}
|
}
|
||||||
@@ -67,49 +74,51 @@ export default class SshKeyService {
|
|||||||
this.sshPath,
|
this.sshPath,
|
||||||
alias,
|
alias,
|
||||||
)}\n StrictHostKeyChecking no\n${proxyStr}`;
|
)}\n StrictHostKeyChecking no\n${proxyStr}`;
|
||||||
fs.writeFileSync(`${path.join(this.sshPath, `${alias}.config`)}`, config, {
|
await fs.writeFile(
|
||||||
encoding: 'utf8',
|
`${path.join(this.sshPath, `${alias}.config`)}`,
|
||||||
});
|
config,
|
||||||
|
{
|
||||||
|
encoding: 'utf8',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeSshConfig(alias: string) {
|
private async removeSshConfig(alias: string) {
|
||||||
try {
|
try {
|
||||||
const filePath = path.join(this.sshPath, `${alias}.config`);
|
const filePath = path.join(this.sshPath, `${alias}.config`);
|
||||||
if (existsSync(filePath)) {
|
await rmPath(filePath);
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`删除ssh配置文件${alias}失败`, error);
|
this.logger.error(`删除ssh配置文件${alias}失败`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addSSHKey(
|
public async addSSHKey(
|
||||||
key: string,
|
key: string,
|
||||||
alias: string,
|
alias: string,
|
||||||
host: string,
|
host: string,
|
||||||
proxy?: string,
|
proxy?: string,
|
||||||
): void {
|
): Promise<void> {
|
||||||
this.generatePrivateKeyFile(alias, key);
|
await this.generatePrivateKeyFile(alias, key);
|
||||||
this.generateSingleSshConfig(alias, host, proxy);
|
await this.generateSingleSshConfig(alias, host, proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeSSHKey(alias: string, host: string, proxy?: string): void {
|
public async removeSSHKey(alias: string, host: string, proxy?: string): Promise<void> {
|
||||||
this.removePrivateKeyFile(alias);
|
await this.removePrivateKeyFile(alias);
|
||||||
this.removeSshConfig(alias);
|
await this.removeSshConfig(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSshConfig(docs: Subscription[]) {
|
public async setSshConfig(docs: Subscription[]) {
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
if (doc.type === 'private-repo' && doc.pull_type === 'ssh-key') {
|
if (doc.type === 'private-repo' && doc.pull_type === 'ssh-key') {
|
||||||
const { alias, proxy } = doc;
|
const { alias, proxy } = doc;
|
||||||
const { host } = formatUrl(doc);
|
const { host } = formatUrl(doc);
|
||||||
this.removePrivateKeyFile(alias);
|
await this.removePrivateKeyFile(alias);
|
||||||
this.removeSshConfig(alias);
|
await this.removeSshConfig(alias);
|
||||||
this.generatePrivateKeyFile(
|
await this.generatePrivateKeyFile(
|
||||||
alias,
|
alias,
|
||||||
(doc.pull_option as any).private_key,
|
(doc.pull_option as any).private_key,
|
||||||
);
|
);
|
||||||
this.generateSingleSshConfig(alias, host, proxy);
|
await this.generateSingleSshConfig(alias, host, proxy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
} from '../data/subscription';
|
} from '../data/subscription';
|
||||||
import { ChildProcessWithoutNullStreams } from 'child_process';
|
import { ChildProcessWithoutNullStreams } from 'child_process';
|
||||||
import fs from 'fs';
|
|
||||||
import {
|
import {
|
||||||
getFileContentByName,
|
getFileContentByName,
|
||||||
concurrentRun,
|
concurrentRun,
|
||||||
@@ -16,9 +15,9 @@ import {
|
|||||||
killTask,
|
killTask,
|
||||||
handleLogPath,
|
handleLogPath,
|
||||||
promiseExec,
|
promiseExec,
|
||||||
emptyDir,
|
rmPath,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import { promises, existsSync } from 'fs';
|
import fs from 'fs/promises';
|
||||||
import { FindOptions, Op } from 'sequelize';
|
import { FindOptions, Op } from 'sequelize';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||||
@@ -39,7 +38,7 @@ export default class SubscriptionService {
|
|||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
private sshKeyService: SshKeyService,
|
private sshKeyService: SshKeyService,
|
||||||
private crontabService: CrontabService,
|
private crontabService: CrontabService,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
public async list(searchText?: string): Promise<Subscription[]> {
|
public async list(searchText?: string): Promise<Subscription[]> {
|
||||||
let query = {};
|
let query = {};
|
||||||
@@ -107,7 +106,7 @@ export default class SubscriptionService {
|
|||||||
|
|
||||||
public async setSshConfig() {
|
public async setSshConfig() {
|
||||||
const docs = await SubscriptionModel.findAll();
|
const docs = await SubscriptionModel.findAll();
|
||||||
this.sshKeyService.setSshConfig(docs);
|
await this.sshKeyService.setSshConfig(docs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private taskCallbacks(doc: Subscription): TaskCallbacks {
|
private taskCallbacks(doc: Subscription): TaskCallbacks {
|
||||||
@@ -131,7 +130,7 @@ export default class SubscriptionService {
|
|||||||
let beforeStr = '';
|
let beforeStr = '';
|
||||||
try {
|
try {
|
||||||
if (doc.sub_before) {
|
if (doc.sub_before) {
|
||||||
fs.appendFileSync(absolutePath, `\n## 执行before命令...\n\n`);
|
await fs.appendFile(absolutePath, `\n## 执行before命令...\n\n`);
|
||||||
beforeStr = await promiseExec(doc.sub_before);
|
beforeStr = await promiseExec(doc.sub_before);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -139,7 +138,7 @@ export default class SubscriptionService {
|
|||||||
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
||||||
}
|
}
|
||||||
if (beforeStr) {
|
if (beforeStr) {
|
||||||
fs.appendFileSync(absolutePath, `${beforeStr}\n`);
|
await fs.appendFile(absolutePath, `${beforeStr}\n`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
|
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
|
||||||
@@ -158,7 +157,7 @@ export default class SubscriptionService {
|
|||||||
let afterStr = '';
|
let afterStr = '';
|
||||||
try {
|
try {
|
||||||
if (sub.sub_after) {
|
if (sub.sub_after) {
|
||||||
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n\n`);
|
await fs.appendFile(absolutePath, `\n\n## 执行after命令...\n\n`);
|
||||||
afterStr = await promiseExec(sub.sub_after);
|
afterStr = await promiseExec(sub.sub_after);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -166,10 +165,10 @@ export default class SubscriptionService {
|
|||||||
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
||||||
}
|
}
|
||||||
if (afterStr) {
|
if (afterStr) {
|
||||||
fs.appendFileSync(absolutePath, `${afterStr}\n`);
|
await fs.appendFile(absolutePath, `${afterStr}\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.appendFileSync(
|
await fs.appendFile(
|
||||||
absolutePath,
|
absolutePath,
|
||||||
`\n## 执行结束... ${endTime.format(
|
`\n## 执行结束... ${endTime.format(
|
||||||
'YYYY-MM-DD HH:mm:ss',
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
@@ -190,12 +189,12 @@ export default class SubscriptionService {
|
|||||||
onError: async (message: string) => {
|
onError: async (message: string) => {
|
||||||
const sub = await this.getDb({ id: doc.id });
|
const sub = await this.getDb({ id: doc.id });
|
||||||
const absolutePath = await handleLogPath(sub.log_path as string);
|
const absolutePath = await handleLogPath(sub.log_path as string);
|
||||||
fs.appendFileSync(absolutePath, `\n${message}`);
|
await fs.appendFile(absolutePath, `\n${message}`);
|
||||||
},
|
},
|
||||||
onLog: async (message: string) => {
|
onLog: async (message: string) => {
|
||||||
const sub = await this.getDb({ id: doc.id });
|
const sub = await this.getDb({ id: doc.id });
|
||||||
const absolutePath = await handleLogPath(sub.log_path as string);
|
const absolutePath = await handleLogPath(sub.log_path as string);
|
||||||
fs.appendFileSync(absolutePath, `\n${message}`);
|
await fs.appendFile(absolutePath, `\n${message}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -268,11 +267,11 @@ export default class SubscriptionService {
|
|||||||
if (query?.force === true) {
|
if (query?.force === true) {
|
||||||
const crons = await CrontabModel.findAll({ where: { sub_id: ids } });
|
const crons = await CrontabModel.findAll({ where: { sub_id: ids } });
|
||||||
if (crons?.length) {
|
if (crons?.length) {
|
||||||
await this.crontabService.remove(crons.map(x => x.id!))
|
await this.crontabService.remove(crons.map((x) => x.id!));
|
||||||
}
|
}
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
const filePath = join(config.scriptPath, doc.alias);
|
const filePath = join(config.scriptPath, doc.alias);
|
||||||
emptyDir(filePath);
|
await rmPath(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,7 +322,7 @@ export default class SubscriptionService {
|
|||||||
this.scheduleService.runTask(command, this.taskCallbacks(subscription), {
|
this.scheduleService.runTask(command, this.taskCallbacks(subscription), {
|
||||||
name: subscription.name,
|
name: subscription.name,
|
||||||
schedule: subscription.schedule,
|
schedule: subscription.schedule,
|
||||||
command
|
command,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +351,7 @@ export default class SubscriptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const absolutePath = await handleLogPath(doc.log_path as string);
|
const absolutePath = await handleLogPath(doc.log_path as string);
|
||||||
return getFileContentByName(absolutePath);
|
return await getFileContentByName(absolutePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async logs(id: number) {
|
public async logs(id: number) {
|
||||||
@@ -364,15 +363,18 @@ export default class SubscriptionService {
|
|||||||
if (doc.log_path) {
|
if (doc.log_path) {
|
||||||
const relativeDir = path.dirname(`${doc.log_path}`);
|
const relativeDir = path.dirname(`${doc.log_path}`);
|
||||||
const dir = path.resolve(config.logPath, relativeDir);
|
const dir = path.resolve(config.logPath, relativeDir);
|
||||||
if (existsSync(dir)) {
|
const _exist = await fileExist(dir);
|
||||||
let files = await promises.readdir(dir);
|
if (_exist) {
|
||||||
return files
|
let files = await fs.readdir(dir);
|
||||||
.map((x) => ({
|
return (
|
||||||
filename: x,
|
await Promise.all(
|
||||||
directory: relativeDir.replace(config.logPath, ''),
|
files.map(async (x) => ({
|
||||||
time: fs.statSync(`${dir}/${x}`).mtime.getTime(),
|
filename: x,
|
||||||
}))
|
directory: relativeDir.replace(config.logPath, ''),
|
||||||
.sort((a, b) => b.time - a.time);
|
time: (await fs.stat(`${dir}/${x}`)).mtime.getTime(),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
).sort((a, b) => b.time - a.time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+170
-29
@@ -5,10 +5,10 @@ import config from '../config';
|
|||||||
import {
|
import {
|
||||||
AuthDataType,
|
AuthDataType,
|
||||||
AuthInfo,
|
AuthInfo,
|
||||||
AuthInstance,
|
SystemInstance,
|
||||||
AuthModel,
|
SystemModel,
|
||||||
AuthModelInfo,
|
SystemModelInfo,
|
||||||
} from '../data/auth';
|
} from '../data/system';
|
||||||
import { NotificationInfo } from '../data/notify';
|
import { NotificationInfo } from '../data/notify';
|
||||||
import NotificationService from './notify';
|
import NotificationService from './notify';
|
||||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||||
@@ -29,6 +29,7 @@ import tar from 'tar';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import sum from 'lodash/sum';
|
import sum from 'lodash/sum';
|
||||||
|
import { DependenceModel, DependenceStatus, DependenceTypes } from '../data/dependence';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SystemService {
|
export default class SystemService {
|
||||||
@@ -39,21 +40,21 @@ export default class SystemService {
|
|||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
private scheduleService: ScheduleService,
|
private scheduleService: ScheduleService,
|
||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
public async getSystemConfig() {
|
public async getSystemConfig() {
|
||||||
const doc = await this.getDb({ type: AuthDataType.systemConfig });
|
const doc = await this.getDb({ type: AuthDataType.systemConfig });
|
||||||
return doc || ({} as AuthInstance);
|
return doc || ({} as SystemInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateAuthDb(payload: AuthInfo): Promise<AuthInstance> {
|
private async updateAuthDb(payload: AuthInfo): Promise<SystemInstance> {
|
||||||
await AuthModel.upsert({ ...payload });
|
await SystemModel.upsert({ ...payload });
|
||||||
const doc = await this.getDb({ type: payload.type });
|
const doc = await this.getDb({ type: payload.type });
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDb(query: any): Promise<AuthInstance> {
|
public async getDb(query: any): Promise<SystemInstance> {
|
||||||
const doc: any = await AuthModel.findOne({ where: { ...query } });
|
const doc: any = await SystemModel.findOne({ where: { ...query } });
|
||||||
return doc && doc.get({ plain: true });
|
return doc && doc.get({ plain: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,32 +76,171 @@ export default class SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateSystemConfig(info: AuthModelInfo) {
|
public async updateLogRemoveFrequency(info: SystemModelInfo) {
|
||||||
const oDoc = await this.getSystemConfig();
|
const oDoc = await this.getSystemConfig();
|
||||||
const result = await this.updateAuthDb({
|
const result = await this.updateAuthDb({
|
||||||
...oDoc,
|
...oDoc,
|
||||||
type: AuthDataType.systemConfig,
|
info: { ...oDoc.info, ...info },
|
||||||
info,
|
|
||||||
});
|
});
|
||||||
if (info.logRemoveFrequency) {
|
const cron = {
|
||||||
const cron = {
|
id: result.id || NaN,
|
||||||
id: result.id || NaN,
|
name: '删除日志',
|
||||||
name: '删除日志',
|
command: `ql rmlog ${info.logRemoveFrequency}`,
|
||||||
command: `ql rmlog ${info.logRemoveFrequency}`,
|
};
|
||||||
};
|
if (oDoc.info?.logRemoveFrequency) {
|
||||||
await this.scheduleService.cancelIntervalTask(cron);
|
await this.scheduleService.cancelIntervalTask(cron);
|
||||||
if (info.logRemoveFrequency > 0) {
|
|
||||||
this.scheduleService.createIntervalTask(cron, {
|
|
||||||
days: info.logRemoveFrequency,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (info.logRemoveFrequency && info.logRemoveFrequency > 0) {
|
||||||
|
this.scheduleService.createIntervalTask(cron, {
|
||||||
|
days: info.logRemoveFrequency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { code: 200, data: info };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateCronConcurrency(info: SystemModelInfo) {
|
||||||
|
const oDoc = await this.getSystemConfig();
|
||||||
|
await this.updateAuthDb({
|
||||||
|
...oDoc,
|
||||||
|
info: { ...oDoc.info, ...info },
|
||||||
|
});
|
||||||
if (info.cronConcurrency) {
|
if (info.cronConcurrency) {
|
||||||
await taskLimit.setCustomLimit(info.cronConcurrency);
|
await taskLimit.setCustomLimit(info.cronConcurrency);
|
||||||
}
|
}
|
||||||
return { code: 200, data: info };
|
return { code: 200, data: info };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateDependenceProxy(info: SystemModelInfo) {
|
||||||
|
const oDoc = await this.getSystemConfig();
|
||||||
|
await this.updateAuthDb({
|
||||||
|
...oDoc,
|
||||||
|
info: { ...oDoc.info, ...info },
|
||||||
|
});
|
||||||
|
if (info.dependenceProxy) {
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
config.dependenceProxyFile,
|
||||||
|
`export http_proxy="${info.dependenceProxy}"\nexport https_proxy="${info.dependenceProxy}"`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await fs.promises.rm(config.dependenceProxyFile);
|
||||||
|
}
|
||||||
|
return { code: 200, data: info };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateNodeMirror(info: SystemModelInfo, res?: Response) {
|
||||||
|
const oDoc = await this.getSystemConfig();
|
||||||
|
await this.updateAuthDb({
|
||||||
|
...oDoc,
|
||||||
|
info: { ...oDoc.info, ...info },
|
||||||
|
});
|
||||||
|
let cmd = 'pnpm config delete registry';
|
||||||
|
if (info.nodeMirror) {
|
||||||
|
cmd = `pnpm config set registry ${info.nodeMirror}`;
|
||||||
|
}
|
||||||
|
let command = `cd && ${cmd}`;
|
||||||
|
const docs = await DependenceModel.findAll({
|
||||||
|
where: { type: DependenceTypes.nodejs, status: DependenceStatus.installed },
|
||||||
|
});
|
||||||
|
if (docs.length > 0) {
|
||||||
|
command += ` && pnpm i -g`;
|
||||||
|
}
|
||||||
|
this.scheduleService.runTask(
|
||||||
|
command,
|
||||||
|
{
|
||||||
|
onStart: async (cp) => {
|
||||||
|
res?.setHeader('QL-Task-Pid', `${cp.pid}`);
|
||||||
|
res?.end();
|
||||||
|
},
|
||||||
|
onEnd: async () => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateNodeMirror',
|
||||||
|
message: 'update node mirror end',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: async (message: string) => {
|
||||||
|
this.sockService.sendMessage({ type: 'updateNodeMirror', message });
|
||||||
|
},
|
||||||
|
onLog: async (message: string) => {
|
||||||
|
this.sockService.sendMessage({ type: 'updateNodeMirror', message });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updatePythonMirror(info: SystemModelInfo) {
|
||||||
|
const oDoc = await this.getSystemConfig();
|
||||||
|
await this.updateAuthDb({
|
||||||
|
...oDoc,
|
||||||
|
info: { ...oDoc.info, ...info },
|
||||||
|
});
|
||||||
|
let cmd = 'pip config unset global.index-url';
|
||||||
|
if (info.pythonMirror) {
|
||||||
|
cmd = `pip3 config set global.index-url ${info.pythonMirror}`;
|
||||||
|
}
|
||||||
|
await promiseExec(cmd);
|
||||||
|
return { code: 200, data: info };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateLinuxMirror(
|
||||||
|
info: SystemModelInfo,
|
||||||
|
res?: Response,
|
||||||
|
onEnd?: () => void,
|
||||||
|
) {
|
||||||
|
const oDoc = await this.getSystemConfig();
|
||||||
|
await this.updateAuthDb({
|
||||||
|
...oDoc,
|
||||||
|
info: { ...oDoc.info, ...info },
|
||||||
|
});
|
||||||
|
let defaultDomain = 'https://dl-cdn.alpinelinux.org';
|
||||||
|
let targetDomain = 'https://dl-cdn.alpinelinux.org';
|
||||||
|
const content = await fs.promises.readFile('/etc/apk/repositories', {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
});
|
||||||
|
const domainMatch = content.match(/(http.*)\/alpine\/.*/);
|
||||||
|
if (domainMatch) {
|
||||||
|
defaultDomain = domainMatch[1];
|
||||||
|
}
|
||||||
|
if (info.linuxMirror) {
|
||||||
|
targetDomain = info.linuxMirror;
|
||||||
|
}
|
||||||
|
const command = `sed -i 's/${defaultDomain.replace(
|
||||||
|
/\//g,
|
||||||
|
'\\/',
|
||||||
|
)}/${targetDomain.replace(
|
||||||
|
/\//g,
|
||||||
|
'\\/',
|
||||||
|
)}/g' /etc/apk/repositories && apk update -f`;
|
||||||
|
|
||||||
|
this.scheduleService.runTask(
|
||||||
|
command,
|
||||||
|
{
|
||||||
|
onStart: async (cp) => {
|
||||||
|
res?.setHeader('QL-Task-Pid', `${cp.pid}`);
|
||||||
|
res?.end();
|
||||||
|
},
|
||||||
|
onEnd: async () => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateLinuxMirror',
|
||||||
|
message: 'update linux mirror end',
|
||||||
|
});
|
||||||
|
onEnd?.();
|
||||||
|
},
|
||||||
|
onError: async (message: string) => {
|
||||||
|
this.sockService.sendMessage({ type: 'updateLinuxMirror', message });
|
||||||
|
},
|
||||||
|
onLog: async (message: string) => {
|
||||||
|
this.sockService.sendMessage({ type: 'updateLinuxMirror', message });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async checkUpdate() {
|
public async checkUpdate() {
|
||||||
try {
|
try {
|
||||||
const currentVersionContent = await parseVersion(config.versionFile);
|
const currentVersionContent = await parseVersion(config.versionFile);
|
||||||
@@ -114,7 +254,7 @@ export default class SystemService {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
lastVersionContent = await parseContentVersion(result.body);
|
lastVersionContent = await parseContentVersion(result.body);
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
|
|
||||||
if (!lastVersionContent) {
|
if (!lastVersionContent) {
|
||||||
lastVersionContent = currentVersionContent;
|
lastVersionContent = currentVersionContent;
|
||||||
@@ -160,7 +300,7 @@ export default class SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateSystem() {
|
public async updateSystem() {
|
||||||
const cp = spawn('ql -l update false', { shell: '/bin/bash' });
|
const cp = spawn('real_time=true ql update false', { shell: '/bin/bash' });
|
||||||
|
|
||||||
cp.stdout.on('data', (data) => {
|
cp.stdout.on('data', (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
@@ -187,7 +327,8 @@ export default class SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async reloadSystem(target: 'system' | 'data') {
|
public async reloadSystem(target: 'system' | 'data') {
|
||||||
const cp = spawn(`ql -l reload ${target || ''}`, { shell: '/bin/bash' });
|
const cmd = `real_time=true ql reload ${target || ''}`;
|
||||||
|
const cp = spawn(cmd, { shell: '/bin/bash' });
|
||||||
|
|
||||||
cp.stdout.on('data', (data) => {
|
cp.stdout.on('data', (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
@@ -234,7 +375,7 @@ export default class SystemService {
|
|||||||
callback,
|
callback,
|
||||||
{
|
{
|
||||||
command,
|
command,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +424,7 @@ export default class SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getSystemLog(res: Response) {
|
public async getSystemLog(res: Response) {
|
||||||
const result = readDirs(config.systemLogPath, config.systemLogPath);
|
const result = await readDirs(config.systemLogPath, config.systemLogPath);
|
||||||
const logs = result.reverse().filter((x) => x.title.endsWith('.log'));
|
const logs = result.reverse().filter((x) => x.title.endsWith('.log'));
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Length': sum(logs.map((x) => x.size)),
|
'Content-Length': sum(logs.map((x) => x.size)),
|
||||||
|
|||||||
+48
-37
@@ -8,22 +8,25 @@ import {
|
|||||||
safeJSONParse,
|
safeJSONParse,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs/promises';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { authenticator } from '@otplib/preset-default';
|
import { authenticator } from '@otplib/preset-default';
|
||||||
import {
|
import {
|
||||||
AuthDataType,
|
AuthDataType,
|
||||||
AuthInfo,
|
AuthInfo,
|
||||||
AuthModel,
|
SystemModel,
|
||||||
AuthModelInfo,
|
SystemModelInfo,
|
||||||
LoginStatus,
|
LoginStatus,
|
||||||
} from '../data/auth';
|
} from '../data/system';
|
||||||
import { NotificationInfo } from '../data/notify';
|
import { NotificationInfo } from '../data/notify';
|
||||||
import NotificationService from './notify';
|
import NotificationService from './notify';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import ScheduleService from './schedule';
|
import ScheduleService from './schedule';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import IP2Region from 'ip2region';
|
||||||
|
import requestIp from 'request-ip';
|
||||||
|
import uniq from 'lodash/uniq';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
@@ -44,12 +47,13 @@ export default class UserService {
|
|||||||
req: Request,
|
req: Request,
|
||||||
needTwoFactor = true,
|
needTwoFactor = true,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
if (!fs.existsSync(config.authConfigFile)) {
|
const _exist = await fileExist(config.authConfigFile);
|
||||||
|
if (!_exist) {
|
||||||
return this.initAuthInfo();
|
return this.initAuthInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
let { username, password } = payloads;
|
let { username, password } = payloads;
|
||||||
const content = this.getAuthInfo();
|
const content = await this.getAuthInfo();
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
if (content) {
|
if (content) {
|
||||||
let {
|
let {
|
||||||
@@ -102,7 +106,16 @@ export default class UserService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ip, address } = await getNetIp(req);
|
const ip = requestIp.getClientIp(req) || '';
|
||||||
|
const query = new IP2Region();
|
||||||
|
const ipAddress = query.search(ip);
|
||||||
|
let address = '';
|
||||||
|
if (ipAddress) {
|
||||||
|
const { country, province, city, isp } = ipAddress;
|
||||||
|
address = uniq([country, province, city, isp])
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
if (username === cUsername && password === cPassword) {
|
if (username === cUsername && password === cPassword) {
|
||||||
const data = createRandomString(50, 100);
|
const data = createRandomString(50, 100);
|
||||||
const expiration = twoFactorActivated ? 60 : 20;
|
const expiration = twoFactorActivated ? 60 : 20;
|
||||||
@@ -124,13 +137,12 @@ export default class UserService {
|
|||||||
platform: req.platform,
|
platform: req.platform,
|
||||||
isTwoFactorChecking: false,
|
isTwoFactorChecking: false,
|
||||||
});
|
});
|
||||||
await this.notificationService.notify(
|
this.notificationService.notify(
|
||||||
'登录通知',
|
'登录通知',
|
||||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||||
req.platform
|
req.platform
|
||||||
}端 登录成功,ip地址 ${ip}`,
|
}端 登录成功,ip地址 ${ip}`,
|
||||||
);
|
);
|
||||||
await this.getLoginLog();
|
|
||||||
await this.insertDb({
|
await this.insertDb({
|
||||||
type: AuthDataType.loginLog,
|
type: AuthDataType.loginLog,
|
||||||
info: {
|
info: {
|
||||||
@@ -153,13 +165,12 @@ export default class UserService {
|
|||||||
lastaddr: address,
|
lastaddr: address,
|
||||||
platform: req.platform,
|
platform: req.platform,
|
||||||
});
|
});
|
||||||
await this.notificationService.notify(
|
this.notificationService.notify(
|
||||||
'登录通知',
|
'登录通知',
|
||||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||||
req.platform
|
req.platform
|
||||||
}端 登录失败,ip地址 ${ip}`,
|
}端 登录失败,ip地址 ${ip}`,
|
||||||
);
|
);
|
||||||
await this.getLoginLog();
|
|
||||||
await this.insertDb({
|
await this.insertDb({
|
||||||
type: AuthDataType.loginLog,
|
type: AuthDataType.loginLog,
|
||||||
info: {
|
info: {
|
||||||
@@ -187,15 +198,15 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async logout(platform: string): Promise<any> {
|
public async logout(platform: string): Promise<any> {
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, {
|
this.updateAuthInfo(authInfo, {
|
||||||
token: '',
|
token: '',
|
||||||
tokens: { ...authInfo.tokens, [platform]: '' },
|
tokens: { ...authInfo.tokens, [platform]: '' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getLoginLog(): Promise<Array<AuthModelInfo | undefined>> {
|
public async getLoginLog(): Promise<Array<SystemModelInfo | undefined>> {
|
||||||
const docs = await AuthModel.findAll({
|
const docs = await SystemModel.findAll({
|
||||||
where: { type: AuthDataType.loginLog },
|
where: { type: AuthDataType.loginLog },
|
||||||
});
|
});
|
||||||
if (docs && docs.length > 0) {
|
if (docs && docs.length > 0) {
|
||||||
@@ -203,7 +214,7 @@ export default class UserService {
|
|||||||
(a, b) => b.info!.timestamp! - a.info!.timestamp!,
|
(a, b) => b.info!.timestamp! - a.info!.timestamp!,
|
||||||
);
|
);
|
||||||
if (result.length > 100) {
|
if (result.length > 100) {
|
||||||
await AuthModel.destroy({
|
await SystemModel.destroy({
|
||||||
where: { id: result[result.length - 1].id },
|
where: { id: result[result.length - 1].id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -213,12 +224,12 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async insertDb(payload: AuthInfo): Promise<AuthInfo> {
|
private async insertDb(payload: AuthInfo): Promise<AuthInfo> {
|
||||||
const doc = await AuthModel.create({ ...payload }, { returning: true });
|
const doc = await SystemModel.create({ ...payload }, { returning: true });
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initAuthInfo() {
|
private async initAuthInfo() {
|
||||||
fs.writeFileSync(
|
await fs.writeFile(
|
||||||
config.authConfigFile,
|
config.authConfigFile,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
@@ -241,13 +252,13 @@ export default class UserService {
|
|||||||
if (password === 'admin') {
|
if (password === 'admin') {
|
||||||
return { code: 400, message: '密码不能设置为admin' };
|
return { code: 400, message: '密码不能设置为admin' };
|
||||||
}
|
}
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, { username, password });
|
this.updateAuthInfo(authInfo, { username, password });
|
||||||
return { code: 200, message: '更新成功' };
|
return { code: 200, message: '更新成功' };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateAvatar(avatar: string) {
|
public async updateAvatar(avatar: string) {
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, { avatar });
|
this.updateAuthInfo(authInfo, { avatar });
|
||||||
return { code: 200, data: avatar, message: '更新成功' };
|
return { code: 200, data: avatar, message: '更新成功' };
|
||||||
}
|
}
|
||||||
@@ -255,7 +266,7 @@ export default class UserService {
|
|||||||
public async getUserInfo(): Promise<any> {
|
public async getUserInfo(): Promise<any> {
|
||||||
const authFileExist = await fileExist(config.authConfigFile);
|
const authFileExist = await fileExist(config.authConfigFile);
|
||||||
if (!authFileExist) {
|
if (!authFileExist) {
|
||||||
fs.writeFileSync(
|
await fs.writeFile(
|
||||||
config.authConfigFile,
|
config.authConfigFile,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
@@ -263,19 +274,19 @@ export default class UserService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.getAuthInfo();
|
return await this.getAuthInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public initTwoFactor() {
|
public async initTwoFactor() {
|
||||||
const secret = authenticator.generateSecret();
|
const secret = authenticator.generateSecret();
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
const otpauth = authenticator.keyuri(authInfo.username, 'qinglong', secret);
|
const otpauth = authenticator.keyuri(authInfo.username, 'qinglong', secret);
|
||||||
this.updateAuthInfo(authInfo, { twoFactorSecret: secret });
|
this.updateAuthInfo(authInfo, { twoFactorSecret: secret });
|
||||||
return { secret, url: otpauth };
|
return { secret, url: otpauth };
|
||||||
}
|
}
|
||||||
|
|
||||||
public activeTwoFactor(code: string) {
|
public async activeTwoFactor(code: string) {
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
const isValid = authenticator.verify({
|
const isValid = authenticator.verify({
|
||||||
token: code,
|
token: code,
|
||||||
secret: authInfo.twoFactorSecret,
|
secret: authInfo.twoFactorSecret,
|
||||||
@@ -294,7 +305,7 @@ export default class UserService {
|
|||||||
}: { username: string; password: string; code: string },
|
}: { username: string; password: string; code: string },
|
||||||
req: any,
|
req: any,
|
||||||
) {
|
) {
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
const { isTwoFactorChecking, twoFactorSecret } = authInfo;
|
const { isTwoFactorChecking, twoFactorSecret } = authInfo;
|
||||||
if (!isTwoFactorChecking) {
|
if (!isTwoFactorChecking) {
|
||||||
return { code: 450, message: '未知错误' };
|
return { code: 450, message: '未知错误' };
|
||||||
@@ -316,8 +327,8 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public deactiveTwoFactor() {
|
public async deactiveTwoFactor() {
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, {
|
this.updateAuthInfo(authInfo, {
|
||||||
twoFactorActivated: false,
|
twoFactorActivated: false,
|
||||||
twoFactorActived: false,
|
twoFactorActived: false,
|
||||||
@@ -326,13 +337,13 @@ export default class UserService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAuthInfo() {
|
private async getAuthInfo() {
|
||||||
const content = fs.readFileSync(config.authConfigFile, 'utf8');
|
const content = await fs.readFile(config.authConfigFile, 'utf8');
|
||||||
return safeJSONParse(content);
|
return safeJSONParse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateAuthInfo(authInfo: any, info: any) {
|
private async updateAuthInfo(authInfo: any, info: any) {
|
||||||
fs.writeFileSync(
|
await fs.writeFile(
|
||||||
config.authConfigFile,
|
config.authConfigFile,
|
||||||
JSON.stringify({ ...authInfo, ...info }),
|
JSON.stringify({ ...authInfo, ...info }),
|
||||||
);
|
);
|
||||||
@@ -344,21 +355,21 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async updateAuthDb(payload: AuthInfo): Promise<any> {
|
private async updateAuthDb(payload: AuthInfo): Promise<any> {
|
||||||
let doc = await AuthModel.findOne({ type: payload.type });
|
let doc = await SystemModel.findOne({ type: payload.type });
|
||||||
if (doc) {
|
if (doc) {
|
||||||
const updateResult = await AuthModel.update(payload, {
|
const updateResult = await SystemModel.update(payload, {
|
||||||
where: { id: doc.id },
|
where: { id: doc.id },
|
||||||
returning: true,
|
returning: true,
|
||||||
});
|
});
|
||||||
doc = updateResult[1][0];
|
doc = updateResult[1][0];
|
||||||
} else {
|
} else {
|
||||||
doc = await AuthModel.create(payload, { returning: true });
|
doc = await SystemModel.create(payload, { returning: true });
|
||||||
}
|
}
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDb(query: any): Promise<any> {
|
public async getDb(query: any): Promise<any> {
|
||||||
const doc: any = await AuthModel.findOne({ where: { ...query } });
|
const doc: any = await SystemModel.findOne({ where: { ...query } });
|
||||||
return doc && (doc.get({ plain: true }) as any);
|
return doc && (doc.get({ plain: true }) as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-23
@@ -1,12 +1,20 @@
|
|||||||
import PQueue, { QueueAddOptions } from 'p-queue-cjs';
|
import PQueue, { QueueAddOptions } from 'p-queue-cjs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { AuthDataType, AuthModel } from '../data/auth';
|
import { AuthDataType, SystemModel } from '../data/system';
|
||||||
import Logger from '../loaders/logger';
|
import Logger from '../loaders/logger';
|
||||||
|
import { Dependence } from '../data/dependence';
|
||||||
|
|
||||||
|
interface IDependencyFn<T> {
|
||||||
|
(): Promise<T>;
|
||||||
|
dependency?: Dependence;
|
||||||
|
}
|
||||||
class TaskLimit {
|
class TaskLimit {
|
||||||
private oneLimit = new PQueue({ concurrency: 1 });
|
private dependenyLimit = new PQueue({ concurrency: 1 });
|
||||||
|
private queuedDependencyIds = new Set<number>([]);
|
||||||
private updateLogLimit = new PQueue({ concurrency: 1 });
|
private updateLogLimit = new PQueue({ concurrency: 1 });
|
||||||
private cronLimit = new PQueue({ concurrency: Math.max(os.cpus().length, 4) });
|
private cronLimit = new PQueue({
|
||||||
|
concurrency: Math.max(os.cpus().length, 4),
|
||||||
|
});
|
||||||
|
|
||||||
get cronLimitActiveCount() {
|
get cronLimitActiveCount() {
|
||||||
return this.cronLimit.pending;
|
return this.cronLimit.pending;
|
||||||
@@ -16,6 +24,10 @@ class TaskLimit {
|
|||||||
return this.cronLimit.size;
|
return this.cronLimit.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get firstDependencyId() {
|
||||||
|
return [...this.queuedDependencyIds.values()][0];
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.setCustomLimit();
|
this.setCustomLimit();
|
||||||
this.handleEvents();
|
this.handleEvents();
|
||||||
@@ -26,21 +38,19 @@ class TaskLimit {
|
|||||||
Logger.info(
|
Logger.info(
|
||||||
`[schedule][任务加入队列] 运行中任务数: ${this.cronLimitActiveCount}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
`[schedule][任务加入队列] 运行中任务数: ${this.cronLimitActiveCount}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
this.cronLimit.on('active', () => {
|
this.cronLimit.on('active', () => {
|
||||||
Logger.info(
|
Logger.info(
|
||||||
`[schedule][开始处理任务] 运行中任务数: ${this.cronLimitActiveCount + 1}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
`[schedule][开始处理任务] 运行中任务数: ${
|
||||||
);
|
this.cronLimitActiveCount + 1
|
||||||
})
|
}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
||||||
this.cronLimit.on('completed', (param) => {
|
|
||||||
Logger.info(
|
|
||||||
`[schedule][任务处理成功] 参数 ${JSON.stringify(param)}`,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.cronLimit.on('error', error => {
|
this.cronLimit.on('completed', (param) => {
|
||||||
Logger.error(
|
Logger.info(`[schedule][任务处理成功] 参数 ${JSON.stringify(param)}`);
|
||||||
`[schedule][任务处理错误] 参数 ${JSON.stringify(error)}`,
|
});
|
||||||
);
|
this.cronLimit.on('error', (error) => {
|
||||||
|
Logger.error(`[schedule][任务处理错误] 参数 ${JSON.stringify(error)}`);
|
||||||
});
|
});
|
||||||
this.cronLimit.on('next', () => {
|
this.cronLimit.on('next', () => {
|
||||||
Logger.info(
|
Logger.info(
|
||||||
@@ -48,19 +58,23 @@ class TaskLimit {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.cronLimit.on('idle', () => {
|
this.cronLimit.on('idle', () => {
|
||||||
Logger.info(
|
Logger.info(`[schedule][任务队列] 空闲中...`);
|
||||||
`[schedule][任务队列] 空闲中...`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public removeQueuedDependency(dependency: Dependence) {
|
||||||
|
if (this.queuedDependencyIds.has(dependency.id!)) {
|
||||||
|
this.queuedDependencyIds.delete(dependency.id!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async setCustomLimit(limit?: number) {
|
public async setCustomLimit(limit?: number) {
|
||||||
if (limit) {
|
if (limit) {
|
||||||
this.cronLimit.concurrency = limit;
|
this.cronLimit.concurrency = limit;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await AuthModel.sync();
|
await SystemModel.sync();
|
||||||
const doc = await AuthModel.findOne({
|
const doc = await SystemModel.findOne({
|
||||||
where: { type: AuthDataType.systemConfig },
|
where: { type: AuthDataType.systemConfig },
|
||||||
});
|
});
|
||||||
if (doc?.info?.cronConcurrency) {
|
if (doc?.info?.cronConcurrency) {
|
||||||
@@ -68,15 +82,27 @@ class TaskLimit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async runWithCronLimit<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
public async runWithCronLimit<T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
options?: Partial<QueueAddOptions>,
|
||||||
|
): Promise<T | void> {
|
||||||
return this.cronLimit.add(fn, options);
|
return this.cronLimit.add(fn, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public runOneByOne<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
public runDependeny<T>(
|
||||||
return this.oneLimit.add(fn, options);
|
dependency: Dependence,
|
||||||
|
fn: IDependencyFn<T>,
|
||||||
|
options?: Partial<QueueAddOptions>,
|
||||||
|
): Promise<T | void> {
|
||||||
|
this.queuedDependencyIds.add(dependency.id!);
|
||||||
|
fn.dependency = dependency;
|
||||||
|
return this.dependenyLimit.add(fn, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateDepLog<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
public updateDepLog<T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
options?: Partial<QueueAddOptions>,
|
||||||
|
): Promise<T | void> {
|
||||||
return this.updateLogLimit.add(fn, options);
|
return this.updateLogLimit.add(fn, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
FROM python:3.10-alpine3.18 as builder
|
||||||
|
COPY package.json .npmrc pnpm-lock.yaml /tmp/build/
|
||||||
|
RUN set -x \
|
||||||
|
&& apk update \
|
||||||
|
&& apk add nodejs npm git \
|
||||||
|
&& npm i -g pnpm@8.3.1 \
|
||||||
|
&& cd /tmp/build \
|
||||||
|
&& pnpm install --prod
|
||||||
|
|
||||||
|
FROM python:3.10-alpine3.18
|
||||||
|
|
||||||
|
ARG QL_MAINTAINER="whyour"
|
||||||
|
LABEL maintainer="${QL_MAINTAINER}"
|
||||||
|
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
||||||
|
ARG QL_BRANCH=develop
|
||||||
|
|
||||||
|
ENV PNPM_HOME=/root/.local/share/pnpm \
|
||||||
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules:$PNPM_HOME \
|
||||||
|
NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules:/root/.local/share/pnpm/global/5/node_modules \
|
||||||
|
LANG=C.UTF-8 \
|
||||||
|
SHELL=/bin/bash \
|
||||||
|
PS1="\u@\h:\w \$ " \
|
||||||
|
QL_DIR=/ql \
|
||||||
|
QL_BRANCH=${QL_BRANCH}
|
||||||
|
|
||||||
|
RUN set -x \
|
||||||
|
&& apk update -f \
|
||||||
|
&& apk upgrade \
|
||||||
|
&& apk --no-cache add -f bash \
|
||||||
|
coreutils \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
tzdata \
|
||||||
|
perl \
|
||||||
|
openssl \
|
||||||
|
nginx \
|
||||||
|
nodejs \
|
||||||
|
jq \
|
||||||
|
openssh \
|
||||||
|
procps \
|
||||||
|
netcat-openbsd \
|
||||||
|
unzip \
|
||||||
|
npm \
|
||||||
|
&& rm -rf /var/cache/apk/* \
|
||||||
|
&& apk update \
|
||||||
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||||
|
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||||
|
&& git config --global user.name "qinglong" \
|
||||||
|
&& git config --global http.postBuffer 524288000 \
|
||||||
|
&& npm install -g pnpm@8.3.1 pm2 tsx \
|
||||||
|
&& rm -rf /root/.pnpm-store \
|
||||||
|
&& rm -rf /root/.local/share/pnpm/store \
|
||||||
|
&& rm -rf /root/.cache \
|
||||||
|
&& rm -rf /root/.npm \
|
||||||
|
&& ulimit -c 0
|
||||||
|
|
||||||
|
ARG SOURCE_COMMIT
|
||||||
|
RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||||
|
&& cd ${QL_DIR} \
|
||||||
|
&& cp -f .env.example .env \
|
||||||
|
&& chmod 777 ${QL_DIR}/shell/*.sh \
|
||||||
|
&& chmod 777 ${QL_DIR}/docker/*.sh \
|
||||||
|
&& git clone --depth=1 -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
|
||||||
|
&& mkdir -p ${QL_DIR}/static \
|
||||||
|
&& cp -rf /static/* ${QL_DIR}/static \
|
||||||
|
&& rm -rf /static
|
||||||
|
|
||||||
|
COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
|
||||||
|
|
||||||
|
WORKDIR ${QL_DIR}
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
|
||||||
|
CMD curl -sf --noproxy '*' http://127.0.0.1:5400/api/health || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
||||||
+12
-9
@@ -1,13 +1,13 @@
|
|||||||
FROM python:3.10-alpine as builder
|
FROM python:3.11-alpine3.18 as builder
|
||||||
COPY package.json .npmrc pnpm-lock.yaml /tmp/build/
|
COPY package.json .npmrc pnpm-lock.yaml /tmp/build/
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& apk update \
|
&& apk update \
|
||||||
&& apk add nodejs npm git \
|
&& apk add nodejs npm git \
|
||||||
&& npm i -g pnpm@8.3.1 \
|
&& npm i -g pnpm@8.3.1 \
|
||||||
&& cd /tmp/build \
|
&& cd /tmp/build \
|
||||||
&& pnpm --registry https://registry.npmmirror.com install --prod
|
&& pnpm install --prod
|
||||||
|
|
||||||
FROM python:3.10-alpine
|
FROM python:3.11-alpine3.18
|
||||||
|
|
||||||
ARG QL_MAINTAINER="whyour"
|
ARG QL_MAINTAINER="whyour"
|
||||||
LABEL maintainer="${QL_MAINTAINER}"
|
LABEL maintainer="${QL_MAINTAINER}"
|
||||||
@@ -23,8 +23,11 @@ ENV PNPM_HOME=/root/.local/share/pnpm \
|
|||||||
QL_DIR=/ql \
|
QL_DIR=/ql \
|
||||||
QL_BRANCH=${QL_BRANCH}
|
QL_BRANCH=${QL_BRANCH}
|
||||||
|
|
||||||
|
VOLUME /ql/data
|
||||||
|
|
||||||
|
EXPOSE 5700
|
||||||
|
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
|
||||||
&& apk update -f \
|
&& apk update -f \
|
||||||
&& apk upgrade \
|
&& apk upgrade \
|
||||||
&& apk --no-cache add -f bash \
|
&& apk --no-cache add -f bash \
|
||||||
@@ -41,6 +44,7 @@ RUN set -x \
|
|||||||
openssh \
|
openssh \
|
||||||
procps \
|
procps \
|
||||||
netcat-openbsd \
|
netcat-openbsd \
|
||||||
|
unzip \
|
||||||
npm \
|
npm \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& apk update \
|
&& apk update \
|
||||||
@@ -49,13 +53,12 @@ RUN set -x \
|
|||||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||||
&& git config --global user.name "qinglong" \
|
&& git config --global user.name "qinglong" \
|
||||||
&& git config --global http.postBuffer 524288000 \
|
&& git config --global http.postBuffer 524288000 \
|
||||||
&& npm install -g pnpm@8.3.1 \
|
&& npm install -g pnpm@8.3.1 pm2 tsx \
|
||||||
&& cd && pnpm config set registry https://registry.npmmirror.com \
|
|
||||||
&& pnpm add -g pm2 tsx \
|
|
||||||
&& rm -rf /root/.pnpm-store \
|
&& rm -rf /root/.pnpm-store \
|
||||||
&& rm -rf /root/.local/share/pnpm/store \
|
&& rm -rf /root/.local/share/pnpm/store \
|
||||||
&& rm -rf /root/.cache \
|
&& rm -rf /root/.cache \
|
||||||
&& rm -rf /root/.npm
|
&& rm -rf /root/.npm \
|
||||||
|
&& ulimit -c 0
|
||||||
|
|
||||||
ARG SOURCE_COMMIT
|
ARG SOURCE_COMMIT
|
||||||
RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||||
@@ -73,6 +76,6 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
|
|||||||
WORKDIR ${QL_DIR}
|
WORKDIR ${QL_DIR}
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
|
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
|
||||||
CMD curl -sf http://127.0.0.1:5400/api/health || exit 1
|
CMD curl -sf --noproxy '*' http://127.0.0.1:5400/api/health || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
dir_shell=/ql/shell
|
dir_shell=/ql/shell
|
||||||
|
. $dir_shell/env.sh
|
||||||
. $dir_shell/share.sh
|
. $dir_shell/share.sh
|
||||||
link_shell
|
link_shell
|
||||||
|
|
||||||
@@ -14,20 +15,6 @@ pm2 l &>/dev/null
|
|||||||
|
|
||||||
echo -e "======================2. 安装依赖========================\n"
|
echo -e "======================2. 安装依赖========================\n"
|
||||||
patch_version
|
patch_version
|
||||||
if [[ $PipMirror ]]; then
|
|
||||||
pip3 config set global.index-url $PipMirror
|
|
||||||
fi
|
|
||||||
current_npm_registry=$(cd && pnpm config get registry)
|
|
||||||
is_equal_registry=$(echo $current_npm_registry | grep "${NpmMirror}")
|
|
||||||
if [[ "$is_equal_registry" == "" ]]; then
|
|
||||||
cd && pnpm config set registry $NpmMirror
|
|
||||||
pnpm install -g
|
|
||||||
fi
|
|
||||||
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
|
|
||||||
cp -f $dir_sample/package.json $dir_scripts/package.json
|
|
||||||
npm_install_2 $dir_scripts
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
|
|
||||||
echo -e "======================3. 启动nginx========================\n"
|
echo -e "======================3. 启动nginx========================\n"
|
||||||
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
||||||
@@ -38,13 +25,13 @@ reload_pm2
|
|||||||
|
|
||||||
if [[ $AutoStartBot == true ]]; then
|
if [[ $AutoStartBot == true ]]; then
|
||||||
echo -e "======================5. 启动bot========================\n"
|
echo -e "======================5. 启动bot========================\n"
|
||||||
nohup ql -l bot >$dir_log/bot.log 2>&1 &
|
nohup ql bot >$dir_log/bot.log 2>&1 &
|
||||||
echo -e "bot后台启动中...\n"
|
echo -e "bot后台启动中...\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $EnableExtraShell == true ]]; then
|
if [[ $EnableExtraShell == true ]]; then
|
||||||
echo -e "====================6. 执行自定义脚本========================\n"
|
echo -e "====================6. 执行自定义脚本========================\n"
|
||||||
nohup ql -l extra >$dir_log/extra.log 2>&1 &
|
nohup ql extra >$dir_log/extra.log 2>&1 &
|
||||||
echo -e "自定义脚本后台执行中...\n"
|
echo -e "自定义脚本后台执行中...\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_pass http://publicApi/api/;
|
proxy_pass http://publicApi/api/;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
proxy_redirect default;
|
||||||
|
proxy_connect_timeout 1800;
|
||||||
|
proxy_send_timeout 1800;
|
||||||
|
proxy_read_timeout 1800;
|
||||||
}
|
}
|
||||||
|
|
||||||
location QL_BASE_URLapi/ {
|
location QL_BASE_URLapi/ {
|
||||||
@@ -30,6 +34,10 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_pass http://baseApi/api/;
|
proxy_pass http://baseApi/api/;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
proxy_redirect default;
|
||||||
|
proxy_connect_timeout 1800;
|
||||||
|
proxy_send_timeout 1800;
|
||||||
|
proxy_read_timeout 1800;
|
||||||
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
@@ -41,6 +49,10 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_pass http://baseApi/open/;
|
proxy_pass http://baseApi/open/;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
proxy_redirect default;
|
||||||
|
proxy_connect_timeout 1800;
|
||||||
|
proxy_send_timeout 1800;
|
||||||
|
proxy_read_timeout 1800;
|
||||||
}
|
}
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ module.exports = {
|
|||||||
source_map_support: true,
|
source_map_support: true,
|
||||||
time: true,
|
time: true,
|
||||||
script: 'static/build/schedule/index.js',
|
script: 'static/build/schedule/index.js',
|
||||||
|
env: {
|
||||||
|
http_proxy: '',
|
||||||
|
https_proxy: '',
|
||||||
|
HTTP_PROXY: '',
|
||||||
|
HTTPS_PROXY: '',
|
||||||
|
all_proxy: '',
|
||||||
|
ALL_PROXY: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'public',
|
name: 'public',
|
||||||
|
|||||||
+11
-6
@@ -58,7 +58,6 @@
|
|||||||
"@grpc/grpc-js": "^1.8.13",
|
"@grpc/grpc-js": "^1.8.13",
|
||||||
"@otplib/preset-default": "^12.0.1",
|
"@otplib/preset-default": "^12.0.1",
|
||||||
"@sentry/node": "^7.12.1",
|
"@sentry/node": "^7.12.1",
|
||||||
"@sentry/tracing": "^7.12.1",
|
|
||||||
"body-parser": "^1.19.2",
|
"body-parser": "^1.19.2",
|
||||||
"celebrate": "^15.0.1",
|
"celebrate": "^15.0.1",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
@@ -79,7 +78,7 @@
|
|||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multer": "^1.4.4",
|
"multer": "1.4.5-lts.1",
|
||||||
"nedb": "^1.8.0",
|
"nedb": "^1.8.0",
|
||||||
"node-schedule": "^2.1.0",
|
"node-schedule": "^2.1.0",
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
@@ -97,7 +96,10 @@
|
|||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"winston": "^3.6.0",
|
"winston": "^3.6.0",
|
||||||
"winston-daily-rotate-file": "^4.7.1",
|
"winston-daily-rotate-file": "^4.7.1",
|
||||||
"yargs": "^17.3.1"
|
"yargs": "^17.3.1",
|
||||||
|
"tough-cookie": "^4.0.0",
|
||||||
|
"request-ip": "3.3.0",
|
||||||
|
"ip2region": "2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
@@ -110,7 +112,7 @@
|
|||||||
"@types/cross-spawn": "^6.0.2",
|
"@types/cross-spawn": "^6.0.2",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/express-jwt": "^6.0.4",
|
"@types/express-jwt": "^6.0.4",
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "2.0.2",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"@types/lodash": "^4.14.185",
|
"@types/lodash": "^4.14.185",
|
||||||
@@ -128,6 +130,7 @@
|
|||||||
"@types/sockjs-client": "^1.5.1",
|
"@types/sockjs-client": "^1.5.1",
|
||||||
"@types/tar": "^6.1.5",
|
"@types/tar": "^6.1.5",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
|
"@types/request-ip": "0.0.41",
|
||||||
"@uiw/codemirror-extensions-langs": "^4.21.9",
|
"@uiw/codemirror-extensions-langs": "^4.21.9",
|
||||||
"@uiw/react-codemirror": "^4.21.9",
|
"@uiw/react-codemirror": "^4.21.9",
|
||||||
"@umijs/max": "^4.0.72",
|
"@umijs/max": "^4.0.72",
|
||||||
@@ -139,11 +142,13 @@
|
|||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"compression-webpack-plugin": "9.2.0",
|
"compression-webpack-plugin": "9.2.0",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"file-saver": "^2.0.5",
|
"react-hotkeys-hook": "^4.4.1",
|
||||||
|
"file-saver": "2.0.2",
|
||||||
"lint-staged": "^13.0.3",
|
"lint-staged": "^13.0.3",
|
||||||
"monaco-editor": "0.33.0",
|
"monaco-editor": "0.33.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^3.0.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
|
"pretty-bytes": "6.1.1",
|
||||||
"qiniu": "^7.4.0",
|
"qiniu": "^7.4.0",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
|
|||||||
Generated
+170
-77
@@ -10,9 +10,6 @@ dependencies:
|
|||||||
'@sentry/node':
|
'@sentry/node':
|
||||||
specifier: ^7.12.1
|
specifier: ^7.12.1
|
||||||
version: 7.54.0
|
version: 7.54.0
|
||||||
'@sentry/tracing':
|
|
||||||
specifier: ^7.12.1
|
|
||||||
version: 7.54.0
|
|
||||||
body-parser:
|
body-parser:
|
||||||
specifier: ^1.19.2
|
specifier: ^1.19.2
|
||||||
version: 1.20.2
|
version: 1.20.2
|
||||||
@@ -64,6 +61,9 @@ dependencies:
|
|||||||
iconv-lite:
|
iconv-lite:
|
||||||
specifier: ^0.6.3
|
specifier: ^0.6.3
|
||||||
version: 0.6.3
|
version: 0.6.3
|
||||||
|
ip2region:
|
||||||
|
specifier: 2.3.0
|
||||||
|
version: 2.3.0(@types/node@17.0.45)
|
||||||
js-yaml:
|
js-yaml:
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
@@ -74,8 +74,8 @@ dependencies:
|
|||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
multer:
|
multer:
|
||||||
specifier: ^1.4.4
|
specifier: 1.4.5-lts.1
|
||||||
version: 1.4.4
|
version: 1.4.5-lts.1
|
||||||
nedb:
|
nedb:
|
||||||
specifier: ^1.8.0
|
specifier: ^1.8.0
|
||||||
version: 1.8.0
|
version: 1.8.0
|
||||||
@@ -97,6 +97,9 @@ dependencies:
|
|||||||
reflect-metadata:
|
reflect-metadata:
|
||||||
specifier: ^0.1.13
|
specifier: ^0.1.13
|
||||||
version: 0.1.13
|
version: 0.1.13
|
||||||
|
request-ip:
|
||||||
|
specifier: 3.3.0
|
||||||
|
version: 3.3.0
|
||||||
sequelize:
|
sequelize:
|
||||||
specifier: ^6.25.5
|
specifier: ^6.25.5
|
||||||
version: 6.32.0(@whyour/sqlite3@1.0.3)
|
version: 6.32.0(@whyour/sqlite3@1.0.3)
|
||||||
@@ -115,6 +118,9 @@ dependencies:
|
|||||||
toad-scheduler:
|
toad-scheduler:
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.6.1
|
version: 1.6.1
|
||||||
|
tough-cookie:
|
||||||
|
specifier: ^4.0.0
|
||||||
|
version: 4.0.0
|
||||||
typedi:
|
typedi:
|
||||||
specifier: ^0.10.0
|
specifier: ^0.10.0
|
||||||
version: 0.10.0
|
version: 0.10.0
|
||||||
@@ -163,8 +169,8 @@ devDependencies:
|
|||||||
specifier: ^6.0.4
|
specifier: ^6.0.4
|
||||||
version: 6.0.4
|
version: 6.0.4
|
||||||
'@types/file-saver':
|
'@types/file-saver':
|
||||||
specifier: ^2.0.5
|
specifier: 2.0.2
|
||||||
version: 2.0.5
|
version: 2.0.2
|
||||||
'@types/js-yaml':
|
'@types/js-yaml':
|
||||||
specifier: ^4.0.5
|
specifier: ^4.0.5
|
||||||
version: 4.0.5
|
version: 4.0.5
|
||||||
@@ -201,6 +207,9 @@ devDependencies:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^18.0.6
|
specifier: ^18.0.6
|
||||||
version: 18.2.4
|
version: 18.2.4
|
||||||
|
'@types/request-ip':
|
||||||
|
specifier: 0.0.41
|
||||||
|
version: 0.0.41
|
||||||
'@types/serve-handler':
|
'@types/serve-handler':
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
@@ -250,8 +259,8 @@ devDependencies:
|
|||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.6.0
|
version: 7.6.0
|
||||||
file-saver:
|
file-saver:
|
||||||
specifier: ^2.0.5
|
specifier: 2.0.2
|
||||||
version: 2.0.5
|
version: 2.0.2
|
||||||
lint-staged:
|
lint-staged:
|
||||||
specifier: ^13.0.3
|
specifier: ^13.0.3
|
||||||
version: 13.2.2
|
version: 13.2.2
|
||||||
@@ -259,11 +268,14 @@ devDependencies:
|
|||||||
specifier: 0.33.0
|
specifier: 0.33.0
|
||||||
version: 0.33.0
|
version: 0.33.0
|
||||||
nodemon:
|
nodemon:
|
||||||
specifier: ^2.0.15
|
specifier: ^3.0.1
|
||||||
version: 2.0.22
|
version: 3.0.1
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^2.5.1
|
specifier: ^2.5.1
|
||||||
version: 2.8.8
|
version: 2.8.8
|
||||||
|
pretty-bytes:
|
||||||
|
specifier: 6.1.1
|
||||||
|
version: 6.1.1
|
||||||
qiniu:
|
qiniu:
|
||||||
specifier: ^7.4.0
|
specifier: ^7.4.0
|
||||||
version: 7.8.0
|
version: 7.8.0
|
||||||
@@ -297,6 +309,9 @@ devDependencies:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
react-hotkeys-hook:
|
||||||
|
specifier: ^4.4.1
|
||||||
|
version: 4.4.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
react-intl-universal:
|
react-intl-universal:
|
||||||
specifier: ^2.6.21
|
specifier: ^2.6.21
|
||||||
version: 2.6.21(react@18.2.0)
|
version: 2.6.21(react@18.2.0)
|
||||||
@@ -3694,6 +3709,7 @@ packages:
|
|||||||
|
|
||||||
/@gar/promisify@1.1.3:
|
/@gar/promisify@1.1.3:
|
||||||
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
|
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -4028,18 +4044,18 @@ packages:
|
|||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@mapbox/node-pre-gyp@1.0.10:
|
/@mapbox/node-pre-gyp@1.0.11:
|
||||||
resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==}
|
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-libc: 2.0.1
|
detect-libc: 2.0.1
|
||||||
https-proxy-agent: 5.0.1
|
https-proxy-agent: 5.0.1
|
||||||
make-dir: 3.1.0
|
make-dir: 3.1.0
|
||||||
node-fetch: 2.6.11
|
node-fetch: 2.7.0
|
||||||
nopt: 5.0.0
|
nopt: 5.0.0
|
||||||
npmlog: 5.0.1
|
npmlog: 5.0.1
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
tar: 6.1.15
|
tar: 6.1.15
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
@@ -4128,9 +4144,10 @@ packages:
|
|||||||
|
|
||||||
/@npmcli/fs@1.1.1:
|
/@npmcli/fs@1.1.1:
|
||||||
resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
|
resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@gar/promisify': 1.1.3
|
'@gar/promisify': 1.1.3
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -4138,6 +4155,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
|
resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
deprecated: This functionality has been moved to @npmcli/fs
|
deprecated: This functionality has been moved to @npmcli/fs
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
mkdirp: 1.0.4
|
mkdirp: 1.0.4
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
@@ -4474,13 +4492,6 @@ packages:
|
|||||||
'@sentry/utils': 7.54.0
|
'@sentry/utils': 7.54.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sentry/tracing@7.54.0:
|
|
||||||
resolution: {integrity: sha512-IyflAAqOiKuNvhWXrJsXN8yfl8DbHRpfgtCEPo5TBvdkpcyxUyfE8W2a2MQ8MXDzgfJT1IixXEwleO+qPIi3Gg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
dependencies:
|
|
||||||
'@sentry-internal/tracing': 7.54.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sentry/types@7.54.0:
|
/@sentry/types@7.54.0:
|
||||||
resolution: {integrity: sha512-D+i9xogBeawvQi2r0NOrM7zYcUaPuijeME4O9eOTrDF20tj71hWtJLilK+KTGLYFtpGg1h+9bPaz7OHEIyVopg==}
|
resolution: {integrity: sha512-D+i9xogBeawvQi2r0NOrM7zYcUaPuijeME4O9eOTrDF20tj71hWtJLilK+KTGLYFtpGg1h+9bPaz7OHEIyVopg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4894,8 +4905,8 @@ packages:
|
|||||||
'@types/qs': 6.9.7
|
'@types/qs': 6.9.7
|
||||||
'@types/serve-static': 1.15.1
|
'@types/serve-static': 1.15.1
|
||||||
|
|
||||||
/@types/file-saver@2.0.5:
|
/@types/file-saver@2.0.2:
|
||||||
resolution: {integrity: sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==}
|
resolution: {integrity: sha512-xbqnZmGrCEqi/KUzOkeUSe77p7APvLuyellGaAoeww3CHJ1AbjQWjPSCFtKIzZn8L7LpEax4NXnC+gfa6nM7IA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/graceful-fs@4.1.6:
|
/@types/graceful-fs@4.1.6:
|
||||||
@@ -5069,6 +5080,12 @@ packages:
|
|||||||
csstype: 3.1.2
|
csstype: 3.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/request-ip@0.0.41:
|
||||||
|
resolution: {integrity: sha512-Qzz0PM2nSZej4lsLzzNfADIORZhhxO7PED0fXpg4FjXiHuJ/lMyUg+YFF5q8x9HPZH3Gl6N+NOM8QZjItNgGKg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 17.0.45
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/responselike@1.0.0:
|
/@types/responselike@1.0.0:
|
||||||
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5172,7 +5189,7 @@ packages:
|
|||||||
ignore: 5.2.4
|
ignore: 5.2.4
|
||||||
natural-compare-lite: 1.4.0
|
natural-compare-lite: 1.4.0
|
||||||
regexpp: 3.2.0
|
regexpp: 3.2.0
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
tsutils: 3.21.0(typescript@5.2.2)
|
tsutils: 3.21.0(typescript@5.2.2)
|
||||||
typescript: 5.2.2
|
typescript: 5.2.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5263,7 +5280,7 @@ packages:
|
|||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
tsutils: 3.21.0(typescript@5.2.2)
|
tsutils: 3.21.0(typescript@5.2.2)
|
||||||
typescript: 5.2.2
|
typescript: 5.2.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5284,7 +5301,7 @@ packages:
|
|||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
tsutils: 3.21.0(typescript@5.2.2)
|
tsutils: 3.21.0(typescript@5.2.2)
|
||||||
typescript: 5.2.2
|
typescript: 5.2.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5308,7 +5325,7 @@ packages:
|
|||||||
eslint: 8.35.0
|
eslint: 8.35.0
|
||||||
eslint-scope: 5.1.1
|
eslint-scope: 5.1.1
|
||||||
eslint-utils: 3.0.0(eslint@8.35.0)
|
eslint-utils: 3.0.0(eslint@8.35.0)
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
@@ -5331,7 +5348,7 @@ packages:
|
|||||||
'@typescript-eslint/typescript-estree': 5.59.9(typescript@5.2.2)
|
'@typescript-eslint/typescript-estree': 5.59.9(typescript@5.2.2)
|
||||||
eslint: 8.35.0
|
eslint: 8.35.0
|
||||||
eslint-scope: 5.1.1
|
eslint-scope: 5.1.1
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
@@ -6439,6 +6456,7 @@ packages:
|
|||||||
|
|
||||||
/aproba@2.0.0:
|
/aproba@2.0.0:
|
||||||
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
|
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/are-we-there-yet@2.0.0:
|
/are-we-there-yet@2.0.0:
|
||||||
@@ -6452,6 +6470,7 @@ packages:
|
|||||||
/are-we-there-yet@3.0.1:
|
/are-we-there-yet@3.0.1:
|
||||||
resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
|
resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
|
||||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
delegates: 1.0.0
|
delegates: 1.0.0
|
||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
@@ -7019,12 +7038,11 @@ packages:
|
|||||||
run-applescript: 5.0.0
|
run-applescript: 5.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/busboy@0.2.14:
|
/busboy@1.6.0:
|
||||||
resolution: {integrity: sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==}
|
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=10.16.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
dicer: 0.2.5
|
streamsearch: 1.1.0
|
||||||
readable-stream: 1.1.14
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/bytes@3.0.0:
|
/bytes@3.0.0:
|
||||||
@@ -7039,6 +7057,7 @@ packages:
|
|||||||
/cacache@15.3.0:
|
/cacache@15.3.0:
|
||||||
resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==}
|
resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@npmcli/fs': 1.1.1
|
'@npmcli/fs': 1.1.1
|
||||||
'@npmcli/move-file': 1.1.2
|
'@npmcli/move-file': 1.1.2
|
||||||
@@ -7326,6 +7345,7 @@ packages:
|
|||||||
/color-support@1.1.3:
|
/color-support@1.1.3:
|
||||||
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/color@3.2.1:
|
/color@3.2.1:
|
||||||
@@ -7441,6 +7461,7 @@ packages:
|
|||||||
|
|
||||||
/console-control-strings@1.1.0:
|
/console-control-strings@1.1.0:
|
||||||
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/constants-browserify@1.0.0:
|
/constants-browserify@1.0.0:
|
||||||
@@ -7705,7 +7726,7 @@ packages:
|
|||||||
postcss-modules-scope: 3.0.0(postcss@8.4.24)
|
postcss-modules-scope: 3.0.0(postcss@8.4.24)
|
||||||
postcss-modules-values: 4.0.0(postcss@8.4.24)
|
postcss-modules-values: 4.0.0(postcss@8.4.24)
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
webpack: 5.85.1
|
webpack: 5.85.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -7952,6 +7973,7 @@ packages:
|
|||||||
|
|
||||||
/delegates@1.0.0:
|
/delegates@1.0.0:
|
||||||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/depd@2.0.0:
|
/depd@2.0.0:
|
||||||
@@ -7994,14 +8016,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
|
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/dicer@0.2.5:
|
|
||||||
resolution: {integrity: sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==}
|
|
||||||
engines: {node: '>=0.8.0'}
|
|
||||||
dependencies:
|
|
||||||
readable-stream: 1.1.14
|
|
||||||
streamsearch: 0.1.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/diff@4.0.2:
|
/diff@4.0.2:
|
||||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||||
engines: {node: '>=0.3.1'}
|
engines: {node: '>=0.3.1'}
|
||||||
@@ -8266,11 +8280,13 @@ packages:
|
|||||||
/env-paths@2.2.1:
|
/env-paths@2.2.1:
|
||||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/err-code@2.0.3:
|
/err-code@2.0.3:
|
||||||
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -8911,8 +8927,8 @@ packages:
|
|||||||
flat-cache: 3.0.4
|
flat-cache: 3.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/file-saver@2.0.5:
|
/file-saver@2.0.2:
|
||||||
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
|
resolution: {integrity: sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/file-stream-rotator@0.6.1:
|
/file-stream-rotator@0.6.1:
|
||||||
@@ -9039,7 +9055,7 @@ packages:
|
|||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
node-abort-controller: 3.1.1
|
node-abort-controller: 3.1.1
|
||||||
schema-utils: 3.1.2
|
schema-utils: 3.1.2
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
tapable: 2.2.1
|
tapable: 2.2.1
|
||||||
typescript: 5.2.2
|
typescript: 5.2.2
|
||||||
webpack: 5.85.1
|
webpack: 5.85.1
|
||||||
@@ -9117,6 +9133,7 @@ packages:
|
|||||||
|
|
||||||
/fs.realpath@1.0.0:
|
/fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
requiresBuild: true
|
||||||
|
|
||||||
/fsevents@2.3.2:
|
/fsevents@2.3.2:
|
||||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
@@ -9168,6 +9185,7 @@ packages:
|
|||||||
/gauge@4.0.4:
|
/gauge@4.0.4:
|
||||||
resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
|
resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
|
||||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
aproba: 2.0.0
|
aproba: 2.0.0
|
||||||
color-support: 1.1.3
|
color-support: 1.1.3
|
||||||
@@ -9432,6 +9450,7 @@ packages:
|
|||||||
|
|
||||||
/has-unicode@2.0.1:
|
/has-unicode@2.0.1:
|
||||||
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
|
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has@1.0.3:
|
/has@1.0.3:
|
||||||
@@ -9745,6 +9764,7 @@ packages:
|
|||||||
|
|
||||||
/infer-owner@1.0.4:
|
/infer-owner@1.0.4:
|
||||||
resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}
|
resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -9755,6 +9775,7 @@ packages:
|
|||||||
|
|
||||||
/inflight@1.0.6:
|
/inflight@1.0.6:
|
||||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
@@ -9815,6 +9836,14 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/ip2region@2.3.0(@types/node@17.0.45):
|
||||||
|
resolution: {integrity: sha512-zV5Xsadzrx9Ej6heoyhbXMsfGWWQ3C6bAIYStrHhw9kzLpGpVNlnAyRBxxPgxA1GNqr1Ti7oUxcWsMWNN3jZBg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '*'
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 17.0.45
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ip@1.1.8:
|
/ip@1.1.8:
|
||||||
resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==}
|
resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -9996,6 +10025,7 @@ packages:
|
|||||||
|
|
||||||
/is-lambda@1.0.1:
|
/is-lambda@1.0.1:
|
||||||
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
|
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10792,6 +10822,7 @@ packages:
|
|||||||
/make-fetch-happen@9.1.0:
|
/make-fetch-happen@9.1.0:
|
||||||
resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==}
|
resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
agentkeepalive: 4.3.0
|
agentkeepalive: 4.3.0
|
||||||
cacache: 15.3.0
|
cacache: 15.3.0
|
||||||
@@ -11006,6 +11037,7 @@ packages:
|
|||||||
/minipass-collect@1.0.2:
|
/minipass-collect@1.0.2:
|
||||||
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
|
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11014,6 +11046,7 @@ packages:
|
|||||||
/minipass-fetch@1.4.1:
|
/minipass-fetch@1.4.1:
|
||||||
resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==}
|
resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
minipass-sized: 1.0.3
|
minipass-sized: 1.0.3
|
||||||
@@ -11026,6 +11059,7 @@ packages:
|
|||||||
/minipass-flush@1.0.5:
|
/minipass-flush@1.0.5:
|
||||||
resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}
|
resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11034,6 +11068,7 @@ packages:
|
|||||||
/minipass-pipeline@1.2.4:
|
/minipass-pipeline@1.2.4:
|
||||||
resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
|
resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11042,6 +11077,7 @@ packages:
|
|||||||
/minipass-sized@1.0.3:
|
/minipass-sized@1.0.3:
|
||||||
resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
|
resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11110,17 +11146,15 @@ packages:
|
|||||||
/ms@2.1.3:
|
/ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
/multer@1.4.4:
|
/multer@1.4.5-lts.1:
|
||||||
resolution: {integrity: sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==}
|
resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
deprecated: Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
append-field: 1.0.0
|
append-field: 1.0.0
|
||||||
busboy: 0.2.14
|
busboy: 1.6.0
|
||||||
concat-stream: 1.6.2
|
concat-stream: 1.6.2
|
||||||
mkdirp: 0.5.6
|
mkdirp: 0.5.6
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
on-finished: 2.4.1
|
|
||||||
type-is: 1.6.18
|
type-is: 1.6.18
|
||||||
xtend: 4.0.2
|
xtend: 4.0.2
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11209,8 +11243,8 @@ packages:
|
|||||||
engines: {node: '>=10.5.0'}
|
engines: {node: '>=10.5.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/node-fetch@2.6.11:
|
/node-fetch@2.7.0:
|
||||||
resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==}
|
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||||
engines: {node: 4.x || >=6.0.0}
|
engines: {node: 4.x || >=6.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
encoding: ^0.1.0
|
encoding: ^0.1.0
|
||||||
@@ -11243,7 +11277,7 @@ packages:
|
|||||||
nopt: 5.0.0
|
nopt: 5.0.0
|
||||||
npmlog: 6.0.2
|
npmlog: 6.0.2
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
tar: 6.1.15
|
tar: 6.1.15
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -11302,9 +11336,9 @@ packages:
|
|||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/nodemon@2.0.22:
|
/nodemon@3.0.1:
|
||||||
resolution: {integrity: sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==}
|
resolution: {integrity: sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
@@ -11312,8 +11346,8 @@ packages:
|
|||||||
ignore-by-default: 1.0.1
|
ignore-by-default: 1.0.1
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
pstree.remy: 1.1.8
|
pstree.remy: 1.1.8
|
||||||
semver: 5.7.1
|
semver: 7.5.4
|
||||||
simple-update-notifier: 1.1.0
|
simple-update-notifier: 2.0.0
|
||||||
supports-color: 5.5.0
|
supports-color: 5.5.0
|
||||||
touch: 3.1.0
|
touch: 3.1.0
|
||||||
undefsafe: 2.0.5
|
undefsafe: 2.0.5
|
||||||
@@ -11330,6 +11364,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
abbrev: 1.1.1
|
abbrev: 1.1.1
|
||||||
dev: false
|
dev: false
|
||||||
@@ -11349,7 +11384,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
hosted-git-info: 4.1.0
|
hosted-git-info: 4.1.0
|
||||||
is-core-module: 2.12.1
|
is-core-module: 2.12.1
|
||||||
semver: 7.5.1
|
semver: 7.5.4
|
||||||
validate-npm-package-license: 3.0.4
|
validate-npm-package-license: 3.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -11413,6 +11448,7 @@ packages:
|
|||||||
/npmlog@6.0.2:
|
/npmlog@6.0.2:
|
||||||
resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
|
resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
|
||||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
are-we-there-yet: 3.0.1
|
are-we-there-yet: 3.0.1
|
||||||
console-control-strings: 1.1.0
|
console-control-strings: 1.1.0
|
||||||
@@ -11766,6 +11802,7 @@ packages:
|
|||||||
/path-is-absolute@1.0.1:
|
/path-is-absolute@1.0.1:
|
||||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
requiresBuild: true
|
||||||
|
|
||||||
/path-is-inside@1.0.2:
|
/path-is-inside@1.0.2:
|
||||||
resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
|
resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
|
||||||
@@ -12496,6 +12533,11 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/pretty-bytes@6.1.1:
|
||||||
|
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
|
||||||
|
engines: {node: ^14.13.1 || >=16.0.0}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/pretty-error@4.0.0:
|
/pretty-error@4.0.0:
|
||||||
resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==}
|
resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12517,6 +12559,7 @@ packages:
|
|||||||
|
|
||||||
/promise-inflight@1.0.1:
|
/promise-inflight@1.0.1:
|
||||||
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
|
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
|
||||||
|
requiresBuild: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
bluebird: '*'
|
bluebird: '*'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -12528,6 +12571,7 @@ packages:
|
|||||||
/promise-retry@2.0.1:
|
/promise-retry@2.0.1:
|
||||||
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
|
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
err-code: 2.0.3
|
err-code: 2.0.3
|
||||||
retry: 0.12.0
|
retry: 0.12.0
|
||||||
@@ -12615,6 +12659,7 @@ packages:
|
|||||||
|
|
||||||
/prr@1.0.1:
|
/prr@1.0.1:
|
||||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||||
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -12622,6 +12667,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
|
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/psl@1.9.0:
|
||||||
|
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/pstree.remy@1.1.8:
|
/pstree.remy@1.1.8:
|
||||||
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
||||||
|
|
||||||
@@ -12652,7 +12701,6 @@ packages:
|
|||||||
/punycode@2.3.0:
|
/punycode@2.3.0:
|
||||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/qiankun@2.10.8:
|
/qiankun@2.10.8:
|
||||||
resolution: {integrity: sha512-5w6QeM1INFavJdIWfSoDIVv2InV8MEojauvY5tY9Biu8hhE5Rml/HM2t/Y60kZcuOpgy/HGX+tcc6NkQuzDoCg==}
|
resolution: {integrity: sha512-5w6QeM1INFavJdIWfSoDIVv2InV8MEojauvY5tY9Biu8hhE5Rml/HM2t/Y60kZcuOpgy/HGX+tcc6NkQuzDoCg==}
|
||||||
@@ -13747,6 +13795,21 @@ packages:
|
|||||||
shallowequal: 1.1.0
|
shallowequal: 1.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/react-hotkeys-hook@4.4.1(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-sClBMBioFEgFGYLTWWRKvhxcCx1DRznd+wkFHwQZspnRBkHTgruKIHptlK/U/2DPX8BhHoRGzpMVWUXMmdZlmw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.1 || 18'
|
||||||
|
react-dom: '>=16.8.1 || 18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: true
|
||||||
|
|
||||||
/react-intl-universal@2.6.21(react@18.2.0):
|
/react-intl-universal@2.6.21(react@18.2.0):
|
||||||
resolution: {integrity: sha512-JJqZMzdB6jd4f0NfGRc9XM/pZElxtrDCWC9pE2yIesDO70SL+jQgaFAucQmstPwYqCcjvLpCgOrjP7cIo4kJnw==}
|
resolution: {integrity: sha512-JJqZMzdB6jd4f0NfGRc9XM/pZElxtrDCWC9pE2yIesDO70SL+jQgaFAucQmstPwYqCcjvLpCgOrjP7cIo4kJnw==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@@ -14003,6 +14066,7 @@ packages:
|
|||||||
inherits: 2.0.4
|
inherits: 2.0.4
|
||||||
isarray: 0.0.1
|
isarray: 0.0.1
|
||||||
string_decoder: 0.10.31
|
string_decoder: 0.10.31
|
||||||
|
dev: true
|
||||||
|
|
||||||
/readable-stream@2.3.8:
|
/readable-stream@2.3.8:
|
||||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||||
@@ -14145,6 +14209,10 @@ packages:
|
|||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/request-ip@3.3.0:
|
||||||
|
resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/require-directory@2.1.1:
|
/require-directory@2.1.1:
|
||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -14218,6 +14286,7 @@ packages:
|
|||||||
/retry@0.12.0:
|
/retry@0.12.0:
|
||||||
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -14309,6 +14378,7 @@ packages:
|
|||||||
|
|
||||||
/sax@1.2.4:
|
/sax@1.2.4:
|
||||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||||
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -14366,17 +14436,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
/semver@7.0.0:
|
|
||||||
resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==}
|
|
||||||
hasBin: true
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/semver@7.5.1:
|
/semver@7.5.1:
|
||||||
resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==}
|
resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 6.0.0
|
lru-cache: 6.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/semver@7.5.4:
|
||||||
|
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
lru-cache: 6.0.0
|
||||||
|
|
||||||
/send@0.18.0:
|
/send@0.18.0:
|
||||||
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||||
@@ -14491,6 +14564,7 @@ packages:
|
|||||||
|
|
||||||
/set-blocking@2.0.0:
|
/set-blocking@2.0.0:
|
||||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/setimmediate@1.0.5:
|
/setimmediate@1.0.5:
|
||||||
@@ -14558,11 +14632,11 @@ packages:
|
|||||||
is-arrayish: 0.3.2
|
is-arrayish: 0.3.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/simple-update-notifier@1.1.0:
|
/simple-update-notifier@2.0.0:
|
||||||
resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==}
|
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.0.0
|
semver: 7.5.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/single-spa@5.9.4:
|
/single-spa@5.9.4:
|
||||||
@@ -14649,6 +14723,7 @@ packages:
|
|||||||
/socks-proxy-agent@6.2.1:
|
/socks-proxy-agent@6.2.1:
|
||||||
resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==}
|
resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
@@ -14792,6 +14867,7 @@ packages:
|
|||||||
/ssri@8.0.1:
|
/ssri@8.0.1:
|
||||||
resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
|
resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
dev: false
|
dev: false
|
||||||
@@ -14851,9 +14927,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==}
|
resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/streamsearch@0.1.2:
|
/streamsearch@1.1.0:
|
||||||
resolution: {integrity: sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==}
|
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/strict-uri-encode@2.0.0:
|
/strict-uri-encode@2.0.0:
|
||||||
@@ -14927,6 +15003,7 @@ packages:
|
|||||||
|
|
||||||
/string_decoder@0.10.31:
|
/string_decoder@0.10.31:
|
||||||
resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
|
resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/string_decoder@1.1.1:
|
/string_decoder@1.1.1:
|
||||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||||
@@ -15389,6 +15466,15 @@ packages:
|
|||||||
nopt: 1.0.10
|
nopt: 1.0.10
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tough-cookie@4.0.0:
|
||||||
|
resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dependencies:
|
||||||
|
psl: 1.9.0
|
||||||
|
punycode: 2.3.0
|
||||||
|
universalify: 0.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tr46@0.0.3:
|
/tr46@0.0.3:
|
||||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -15703,6 +15789,7 @@ packages:
|
|||||||
|
|
||||||
/unique-filename@1.1.1:
|
/unique-filename@1.1.1:
|
||||||
resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==}
|
resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
unique-slug: 2.0.2
|
unique-slug: 2.0.2
|
||||||
dev: false
|
dev: false
|
||||||
@@ -15710,6 +15797,7 @@ packages:
|
|||||||
|
|
||||||
/unique-slug@2.0.2:
|
/unique-slug@2.0.2:
|
||||||
resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==}
|
resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
imurmurhash: 0.1.4
|
imurmurhash: 0.1.4
|
||||||
dev: false
|
dev: false
|
||||||
@@ -15718,7 +15806,6 @@ packages:
|
|||||||
/universalify@0.1.2:
|
/universalify@0.1.2:
|
||||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/universalify@2.0.0:
|
/universalify@2.0.0:
|
||||||
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
|
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
|
||||||
@@ -16162,6 +16249,7 @@ packages:
|
|||||||
|
|
||||||
/wide-align@1.1.5:
|
/wide-align@1.1.5:
|
||||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
dev: false
|
dev: false
|
||||||
@@ -16242,6 +16330,7 @@ packages:
|
|||||||
|
|
||||||
/wrappy@1.0.2:
|
/wrappy@1.0.2:
|
||||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
requiresBuild: true
|
||||||
|
|
||||||
/write-file-atomic@4.0.2:
|
/write-file-atomic@4.0.2:
|
||||||
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
|
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
|
||||||
@@ -16335,7 +16424,7 @@ packages:
|
|||||||
node-gyp:
|
node-gyp:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mapbox/node-pre-gyp': 1.0.10
|
'@mapbox/node-pre-gyp': 1.0.11
|
||||||
node-addon-api: 4.3.0
|
node-addon-api: 4.3.0
|
||||||
tar: 6.1.15
|
tar: 6.1.15
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -16345,3 +16434,7 @@ packages:
|
|||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|||||||
+10
-20
@@ -21,20 +21,19 @@ DiskWarn=90
|
|||||||
## 设置定时任务执行的超时时间,例如1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
## 设置定时任务执行的超时时间,例如1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||||
CommandTimeoutTime=""
|
CommandTimeoutTime=""
|
||||||
|
|
||||||
## 在运行 task 命令时,随机延迟启动任务的最大延迟时间
|
## 在运行 task 命令时,随机延迟启动任务的最大延迟时间,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空
|
||||||
## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空
|
RandomDelay=""
|
||||||
RandomDelay="300"
|
|
||||||
|
|
||||||
## 需要随机延迟运行任务的文件后缀,直接写后缀名即可,多个后缀用空格分开,例如: js py ts
|
## 需要随机延迟运行任务的文件后缀,直接写后缀名即可,多个后缀用空格分开,例如: js py ts
|
||||||
## 默认仅给javascript任务加随机延迟,其它任务按定时规则准点运行。全部任务随机延迟赋值为空
|
## 默认仅给javascript任务加随机延迟,其它任务按定时规则准点运行。全部任务随机延迟赋值为空
|
||||||
RandomDelayFileExtensions="js"
|
RandomDelayFileExtensions=""
|
||||||
|
|
||||||
## 每小时的第几分钟准点运行任务,当在这些时间运行任务时将忽略 RandomDelay 配置,不会被随机延迟
|
## 每小时的第几分钟准点运行任务,当在这些时间运行任务时将忽略 RandomDelay 配置,不会被随机延迟
|
||||||
## 默认是第0分钟和第30分钟,例如21:00或21:30分的任务将会准点运行。不需要准点运行赋值为空
|
## 默认是第0分钟和第30分钟,例如21:00或21:30分的任务将会准点运行。不需要准点运行赋值为空
|
||||||
RandomDelayIgnoredMinutes="0 30"
|
RandomDelayIgnoredMinutes=""
|
||||||
|
|
||||||
## 如果你自己会写shell脚本,并且希望在每次运行 ql update 命令时,额外运行你的 shell 脚本,请赋值为 "true",默认为true
|
## 如果你自己会写shell脚本,并且希望在每次容器启动时,额外运行你的 shell 脚本,请赋值为 "true"
|
||||||
EnableExtraShell="true"
|
EnableExtraShell=""
|
||||||
|
|
||||||
## 是否自动启动bot,默认不启动,设置为true时自动启动,目前需要自行克隆bot仓库所需代码,存到ql/repo目录下,文件夹命名为dockerbot
|
## 是否自动启动bot,默认不启动,设置为true时自动启动,目前需要自行克隆bot仓库所需代码,存到ql/repo目录下,文件夹命名为dockerbot
|
||||||
AutoStartBot=""
|
AutoStartBot=""
|
||||||
@@ -42,12 +41,6 @@ AutoStartBot=""
|
|||||||
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
||||||
BotRepoUrl=""
|
BotRepoUrl=""
|
||||||
|
|
||||||
## 安装python依赖时指定pip源
|
|
||||||
PipMirror="https://pypi.doubanio.com/simple/"
|
|
||||||
|
|
||||||
## 安装node依赖时指定npm源
|
|
||||||
NpmMirror="https://registry.npmmirror.com"
|
|
||||||
|
|
||||||
## 通知环境变量
|
## 通知环境变量
|
||||||
## 1. Server酱
|
## 1. Server酱
|
||||||
## https://sct.ftqq.com
|
## https://sct.ftqq.com
|
||||||
@@ -156,11 +149,8 @@ export AIBOTK_TYPE=""
|
|||||||
export AIBOTK_NAME=""
|
export AIBOTK_NAME=""
|
||||||
|
|
||||||
## 14. SMTP
|
## 14. SMTP
|
||||||
## 暂时只支持在 Python 中调用 notify.py 以发送 SMTP 邮件通知
|
## 邮箱服务名称,比如126、163、Gmail、QQ等,支持列表 https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json
|
||||||
## smtp_server 填写 SMTP 发送邮件服务器,形如 smtp.exmail.qq.com:465
|
export SMTP_SERVICE=""
|
||||||
export SMTP_SERVER=""
|
|
||||||
## smtp_ssl 填写 SMTP 发送邮件服务器是否使用 SSL,内容应为 true 或 false
|
|
||||||
export SMTP_SSL="false"
|
|
||||||
## smtp_email 填写 SMTP 收发件邮箱,通知将会由自己发给自己
|
## smtp_email 填写 SMTP 收发件邮箱,通知将会由自己发给自己
|
||||||
export SMTP_EMAIL=""
|
export SMTP_EMAIL=""
|
||||||
## smtp_password 填写 SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定
|
## smtp_password 填写 SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定
|
||||||
@@ -178,8 +168,8 @@ export PUSHME_KEY=""
|
|||||||
## CHRONOCAT_TOKEN 填写在CHRONOCAT文件生成的访问密钥
|
## CHRONOCAT_TOKEN 填写在CHRONOCAT文件生成的访问密钥
|
||||||
## CHRONOCAT_QQ 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx
|
## CHRONOCAT_QQ 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx
|
||||||
## CHRONOCAT相关API https://chronocat.vercel.app/install/docker/official/
|
## CHRONOCAT相关API https://chronocat.vercel.app/install/docker/official/
|
||||||
export CHRONOCAT_URL=""
|
export CHRONOCAT_URL=""
|
||||||
export CHRONOCAT_QQ="" #
|
export CHRONOCAT_QQ=""
|
||||||
export CHRONOCAT_TOKEN=""
|
export CHRONOCAT_TOKEN=""
|
||||||
|
|
||||||
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
||||||
|
|||||||
+152
-158
@@ -13,81 +13,81 @@
|
|||||||
const querystring = require('querystring');
|
const querystring = require('querystring');
|
||||||
const got = require('got');
|
const got = require('got');
|
||||||
const $ = new Env();
|
const $ = new Env();
|
||||||
const timeout = 15000; //超时时间(单位毫秒)
|
const timeout = 15000; // 超时时间(单位毫秒)
|
||||||
// =======================================gotify通知设置区域==============================================
|
// =======================================gotify通知设置区域==============================================
|
||||||
//gotify_url 填写gotify地址,如https://push.example.de:8080
|
// gotify_url 填写gotify地址,如https://push.example.de:8080
|
||||||
//gotify_token 填写gotify的消息应用token
|
// gotify_token 填写gotify的消息应用token
|
||||||
//gotify_priority 填写推送消息优先级,默认为0
|
// gotify_priority 填写推送消息优先级,默认为0
|
||||||
let GOTIFY_URL = '';
|
let GOTIFY_URL = '';
|
||||||
let GOTIFY_TOKEN = '';
|
let GOTIFY_TOKEN = '';
|
||||||
let GOTIFY_PRIORITY = 0;
|
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文件设置的访问密钥
|
||||||
//gobot_qq 填写推送到个人QQ或者QQ群号
|
// gobot_qq 填写推送到个人QQ或者QQ群号
|
||||||
//go-cqhttp相关API https://docs.go-cqhttp.org/api
|
// go-cqhttp相关API https://docs.go-cqhttp.org/api
|
||||||
let GOBOT_URL = ''; // 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg
|
let GOBOT_URL = ''; // 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg
|
||||||
let GOBOT_TOKEN = ''; //访问密钥
|
let GOBOT_TOKEN = ''; // 访问密钥
|
||||||
let GOBOT_QQ = ''; // 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群
|
let GOBOT_QQ = ''; // 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群
|
||||||
|
|
||||||
// =======================================微信server酱通知设置区域===========================================
|
// =======================================微信server酱通知设置区域===========================================
|
||||||
//此处填你申请的SCKEY.
|
// 此处填你申请的SCKEY.
|
||||||
//(环境变量名 PUSH_KEY)
|
// (环境变量名 PUSH_KEY)
|
||||||
let SCKEY = '';
|
let SCKEY = '';
|
||||||
|
|
||||||
// =======================================PushDeer通知设置区域===========================================
|
// =======================================PushDeer通知设置区域===========================================
|
||||||
//此处填你申请的PushDeer KEY.
|
// 此处填你申请的PushDeer KEY.
|
||||||
//(环境变量名 DEER_KEY)
|
// (环境变量名 DEER_KEY)
|
||||||
let PUSHDEER_KEY = '';
|
let PUSHDEER_KEY = '';
|
||||||
let PUSHDEER_URL = '';
|
let PUSHDEER_URL = '';
|
||||||
|
|
||||||
// =======================================Synology Chat通知设置区域===========================================
|
// =======================================Synology Chat通知设置区域===========================================
|
||||||
//此处填你申请的CHAT_URL与CHAT_TOKEN
|
// 此处填你申请的CHAT_URL与CHAT_TOKEN
|
||||||
//(环境变量名 CHAT_URL CHAT_TOKEN)
|
// (环境变量名 CHAT_URL CHAT_TOKEN)
|
||||||
let CHAT_URL = '';
|
let CHAT_URL = '';
|
||||||
let CHAT_TOKEN = '';
|
let CHAT_TOKEN = '';
|
||||||
|
|
||||||
// =======================================Bark App通知设置区域===========================================
|
// =======================================Bark App通知设置区域===========================================
|
||||||
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
// 此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
||||||
let BARK_PUSH = '';
|
let BARK_PUSH = '';
|
||||||
//BARK app推送图标,自定义推送图标(需iOS15或以上)
|
// BARK app推送图标,自定义推送图标(需iOS15或以上)
|
||||||
let BARK_ICON = 'https://qn.whyour.cn/logo.png';
|
let BARK_ICON = 'https://qn.whyour.cn/logo.png';
|
||||||
//BARK app推送铃声,铃声列表去APP查看复制填写
|
// BARK app推送铃声,铃声列表去APP查看复制填写
|
||||||
let BARK_SOUND = '';
|
let BARK_SOUND = '';
|
||||||
//BARK app推送消息的分组, 默认为"QingLong"
|
// BARK app推送消息的分组, 默认为"QingLong"
|
||||||
let BARK_GROUP = 'QingLong';
|
let BARK_GROUP = 'QingLong';
|
||||||
//BARK app推送消息的时效性, 默认为"active"
|
// BARK app推送消息的时效性, 默认为"active"
|
||||||
let BARK_LEVEL = 'active';
|
let BARK_LEVEL = 'active';
|
||||||
//BARK app推送消息的跳转URL
|
// BARK app推送消息的跳转URL
|
||||||
let BARK_URL = '';
|
let BARK_URL = '';
|
||||||
|
|
||||||
// =======================================telegram机器人通知设置区域===========================================
|
// =======================================telegram机器人通知设置区域===========================================
|
||||||
//此处填你telegram bot 的Token,telegram机器人通知推送必填项.例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw
|
// 此处填你telegram bot 的Token,telegram机器人通知推送必填项.例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw
|
||||||
//(环境变量名 TG_BOT_TOKEN)
|
// (环境变量名 TG_BOT_TOKEN)
|
||||||
let TG_BOT_TOKEN = '';
|
let TG_BOT_TOKEN = '';
|
||||||
//此处填你接收通知消息的telegram用户的id,telegram机器人通知推送必填项.例如:129xxx206
|
// 此处填你接收通知消息的telegram用户的id,telegram机器人通知推送必填项.例如:129xxx206
|
||||||
//(环境变量名 TG_USER_ID)
|
// (环境变量名 TG_USER_ID)
|
||||||
let TG_USER_ID = '';
|
let TG_USER_ID = '';
|
||||||
//tg推送HTTP代理设置(不懂可忽略,telegram机器人通知推送功能中非必填)
|
// tg推送HTTP代理设置(不懂可忽略,telegram机器人通知推送功能中非必填)
|
||||||
let TG_PROXY_HOST = ''; //例如:127.0.0.1(环境变量名:TG_PROXY_HOST)
|
let TG_PROXY_HOST = ''; // 例如:127.0.0.1(环境变量名:TG_PROXY_HOST)
|
||||||
let TG_PROXY_PORT = ''; //例如:1080(环境变量名:TG_PROXY_PORT)
|
let TG_PROXY_PORT = ''; // 例如:1080(环境变量名:TG_PROXY_PORT)
|
||||||
let TG_PROXY_AUTH = ''; //tg代理配置认证参数
|
let TG_PROXY_AUTH = ''; // tg代理配置认证参数
|
||||||
//Telegram api自建的反向代理地址(不懂可忽略,telegram机器人通知推送功能中非必填),默认tg官方api(环境变量名:TG_API_HOST)
|
// Telegram api自建的反向代理地址(不懂可忽略,telegram机器人通知推送功能中非必填),默认tg官方api(环境变量名:TG_API_HOST)
|
||||||
let TG_API_HOST = 'api.telegram.org';
|
let TG_API_HOST = 'https://api.telegram.org';
|
||||||
// =======================================钉钉机器人通知设置区域===========================================
|
// =======================================钉钉机器人通知设置区域===========================================
|
||||||
//此处填你钉钉 bot 的webhook,例如:5a544165465465645d0f31dca676e7bd07415asdasd
|
// 此处填你钉钉 bot 的webhook,例如:5a544165465465645d0f31dca676e7bd07415asdasd
|
||||||
//(环境变量名 DD_BOT_TOKEN)
|
// (环境变量名 DD_BOT_TOKEN)
|
||||||
let DD_BOT_TOKEN = '';
|
let DD_BOT_TOKEN = '';
|
||||||
//密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串
|
// 密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串
|
||||||
let DD_BOT_SECRET = '';
|
let DD_BOT_SECRET = '';
|
||||||
|
|
||||||
// =======================================企业微信基础设置===========================================
|
// =======================================企业微信基础设置===========================================
|
||||||
// 企业微信反向代理地址
|
// 企业微信反向代理地址
|
||||||
//(环境变量名 QYWX_ORIGIN)
|
// (环境变量名 QYWX_ORIGIN)
|
||||||
let QYWX_ORIGIN = '';
|
let QYWX_ORIGIN = '';
|
||||||
// =======================================企业微信机器人通知设置区域===========================================
|
// =======================================企业微信机器人通知设置区域===========================================
|
||||||
//此处填你企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa
|
// 此处填你企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa
|
||||||
//(环境变量名 QYWX_KEY)
|
// (环境变量名 QYWX_KEY)
|
||||||
let QYWX_KEY = '';
|
let QYWX_KEY = '';
|
||||||
|
|
||||||
// =======================================企业微信应用消息通知设置区域===========================================
|
// =======================================企业微信应用消息通知设置区域===========================================
|
||||||
@@ -103,52 +103,43 @@ let QYWX_KEY = '';
|
|||||||
let QYWX_AM = '';
|
let QYWX_AM = '';
|
||||||
|
|
||||||
// =======================================iGot聚合推送通知设置区域===========================================
|
// =======================================iGot聚合推送通知设置区域===========================================
|
||||||
//此处填您iGot的信息(推送key,例如:https://push.hellyw.com/XXXXXXXX)
|
// 此处填您iGot的信息(推送key,例如:https://push.hellyw.com/XXXXXXXX)
|
||||||
let IGOT_PUSH_KEY = '';
|
let IGOT_PUSH_KEY = '';
|
||||||
|
|
||||||
// =======================================push+设置区域=======================================
|
// =======================================push+设置区域=======================================
|
||||||
//官方文档:http://www.pushplus.plus/
|
// 官方文档:http://www.pushplus.plus/
|
||||||
//PUSH_PLUS_TOKEN:微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送
|
// PUSH_PLUS_TOKEN:微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送
|
||||||
//PUSH_PLUS_USER: 一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)
|
// PUSH_PLUS_USER: 一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)
|
||||||
let PUSH_PLUS_TOKEN = '';
|
let PUSH_PLUS_TOKEN = '';
|
||||||
let PUSH_PLUS_USER = '';
|
let PUSH_PLUS_USER = '';
|
||||||
|
|
||||||
// =======================================Cool Push设置区域=======================================
|
|
||||||
//官方文档:https://cp.xuthus.cc/docs
|
|
||||||
//QQ_SKEY: Cool Push登录授权后推送消息的调用代码Skey
|
|
||||||
//QQ_MODE: 推送模式详情请登录获取QQ_SKEY后见https://cp.xuthus.cc/feat
|
|
||||||
let QQ_SKEY = '';
|
|
||||||
let QQ_MODE = '';
|
|
||||||
|
|
||||||
// =======================================智能微秘书设置区域=======================================
|
// =======================================智能微秘书设置区域=======================================
|
||||||
//官方文档:http://wechat.aibotk.com/docs/about
|
// 官方文档:http://wechat.aibotk.com/docs/about
|
||||||
//AIBOTK_KEY: 填写智能微秘书个人中心的apikey
|
// AIBOTK_KEY: 填写智能微秘书个人中心的apikey
|
||||||
//AIBOTK_TYPE:填写发送的目标 room 或 contact, 填其他的不生效
|
// AIBOTK_TYPE:填写发送的目标 room 或 contact, 填其他的不生效
|
||||||
//AIBOTK_NAME: 填写群名或用户昵称,和上面的type类型要对应
|
// AIBOTK_NAME: 填写群名或用户昵称,和上面的type类型要对应
|
||||||
let AIBOTK_KEY = '';
|
let AIBOTK_KEY = '';
|
||||||
let AIBOTK_TYPE = '';
|
let AIBOTK_TYPE = '';
|
||||||
let AIBOTK_NAME = '';
|
let AIBOTK_NAME = '';
|
||||||
|
|
||||||
// =======================================飞书机器人设置区域=======================================
|
// =======================================飞书机器人设置区域=======================================
|
||||||
//官方文档:https://www.feishu.cn/hc/zh-CN/articles/360024984973
|
// 官方文档:https://www.feishu.cn/hc/zh-CN/articles/360024984973
|
||||||
//FSKEY 飞书机器人的 FSKEY
|
// FSKEY 飞书机器人的 FSKEY
|
||||||
let FSKEY = '';
|
let FSKEY = '';
|
||||||
|
|
||||||
// =======================================SMTP 邮件设置区域=======================================
|
// =======================================SMTP 邮件设置区域=======================================
|
||||||
// SMTP_SERVER: 填写 SMTP 发送邮件服务器,形如 smtp.exmail.qq.com:465
|
// SMTP_SERVICE: 邮箱服务名称,比如126、163、Gmail、QQ等,支持列表 https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json
|
||||||
// SMTP_SSL: 填写 SMTP 发送邮件服务器是否使用 SSL,内容应为 true 或 false
|
|
||||||
// SMTP_EMAIL: 填写 SMTP 收发件邮箱,通知将会由自己发给自己
|
// SMTP_EMAIL: 填写 SMTP 收发件邮箱,通知将会由自己发给自己
|
||||||
// SMTP_PASSWORD: 填写 SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定
|
// SMTP_PASSWORD: 填写 SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定
|
||||||
// SMTP_NAME: 填写 SMTP 收发件人姓名,可随意填写
|
// SMTP_NAME: 填写 SMTP 收发件人姓名,可随意填写
|
||||||
let SMTP_SERVER = '';
|
let SMTP_SERVICE = '';
|
||||||
let SMTP_SSL = 'false';
|
|
||||||
let SMTP_EMAIL = '';
|
let SMTP_EMAIL = '';
|
||||||
let SMTP_PASSWORD = '';
|
let SMTP_PASSWORD = '';
|
||||||
let SMTP_NAME = '';
|
let SMTP_NAME = '';
|
||||||
|
|
||||||
// =======================================PushMe通知设置区域===========================================
|
// =======================================PushMe通知设置区域===========================================
|
||||||
//官方文档:https://push.i-i.me/
|
// 官方文档:https://push.i-i.me/
|
||||||
//此处填你的PushMe KEY.
|
// 此处填你的PushMe KEY.
|
||||||
let PUSHME_KEY = '';
|
let PUSHME_KEY = '';
|
||||||
|
|
||||||
// =======================================CHRONOCAT通知设置区域===========================================
|
// =======================================CHRONOCAT通知设置区域===========================================
|
||||||
@@ -157,7 +148,7 @@ let PUSHME_KEY = '';
|
|||||||
// CHRONOCAT_QQ 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群
|
// CHRONOCAT_QQ 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群
|
||||||
// CHRONOCAT相关API https://chronocat.vercel.app/install/docker/official/
|
// CHRONOCAT相关API https://chronocat.vercel.app/install/docker/official/
|
||||||
let CHRONOCAT_URL = ''; // CHRONOCAT Red协议连接地址
|
let CHRONOCAT_URL = ''; // CHRONOCAT Red协议连接地址
|
||||||
let CHRONOCAT_TOKEN = ''; //CHRONOCAT 生成的访问密钥
|
let CHRONOCAT_TOKEN = ''; // CHRONOCAT 生成的访问密钥
|
||||||
let CHRONOCAT_QQ = ''; // 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx
|
let CHRONOCAT_QQ = ''; // 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx
|
||||||
|
|
||||||
// =======================================自定义通知设置区域=======================================
|
// =======================================自定义通知设置区域=======================================
|
||||||
@@ -168,7 +159,7 @@ let WEBHOOK_HEADERS = '';
|
|||||||
let WEBHOOK_METHOD = '';
|
let WEBHOOK_METHOD = '';
|
||||||
let WEBHOOK_CONTENT_TYPE = '';
|
let WEBHOOK_CONTENT_TYPE = '';
|
||||||
|
|
||||||
//==========================云端环境变量的判断与接收=========================
|
// ==========================云端环境变量的判断与接收=========================
|
||||||
if (process.env.GOTIFY_URL) {
|
if (process.env.GOTIFY_URL) {
|
||||||
GOTIFY_URL = process.env.GOTIFY_URL;
|
GOTIFY_URL = process.env.GOTIFY_URL;
|
||||||
}
|
}
|
||||||
@@ -219,7 +210,7 @@ if (process.env.BARK_PUSH) {
|
|||||||
process.env.BARK_PUSH.indexOf('https') > -1 ||
|
process.env.BARK_PUSH.indexOf('https') > -1 ||
|
||||||
process.env.BARK_PUSH.indexOf('http') > -1
|
process.env.BARK_PUSH.indexOf('http') > -1
|
||||||
) {
|
) {
|
||||||
//兼容BARK自建用户
|
// 兼容BARK自建用户
|
||||||
BARK_PUSH = process.env.BARK_PUSH;
|
BARK_PUSH = process.env.BARK_PUSH;
|
||||||
} else {
|
} else {
|
||||||
BARK_PUSH = `https://api.day.app/${process.env.BARK_PUSH}`;
|
BARK_PUSH = `https://api.day.app/${process.env.BARK_PUSH}`;
|
||||||
@@ -245,7 +236,7 @@ if (process.env.BARK_PUSH) {
|
|||||||
BARK_PUSH.indexOf('https') === -1 &&
|
BARK_PUSH.indexOf('https') === -1 &&
|
||||||
BARK_PUSH.indexOf('http') === -1
|
BARK_PUSH.indexOf('http') === -1
|
||||||
) {
|
) {
|
||||||
//兼容BARK本地用户只填写设备码的情况
|
// 兼容BARK本地用户只填写设备码的情况
|
||||||
BARK_PUSH = `https://api.day.app/${BARK_PUSH}`;
|
BARK_PUSH = `https://api.day.app/${BARK_PUSH}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,11 +297,8 @@ if (process.env.FSKEY) {
|
|||||||
FSKEY = process.env.FSKEY;
|
FSKEY = process.env.FSKEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.SMTP_SERVER) {
|
if (process.env.SMTP_SERVICE) {
|
||||||
SMTP_SERVER = process.env.SMTP_SERVER;
|
SMTP_SERVICE = process.env.SMTP_SERVICE;
|
||||||
}
|
|
||||||
if (process.env.SMTP_SSL) {
|
|
||||||
SMTP_SSL = process.env.SMTP_SSL;
|
|
||||||
}
|
}
|
||||||
if (process.env.SMTP_EMAIL) {
|
if (process.env.SMTP_EMAIL) {
|
||||||
SMTP_EMAIL = process.env.SMTP_EMAIL;
|
SMTP_EMAIL = process.env.SMTP_EMAIL;
|
||||||
@@ -350,7 +338,7 @@ if (process.env.WEBHOOK_METHOD) {
|
|||||||
if (process.env.WEBHOOK_CONTENT_TYPE) {
|
if (process.env.WEBHOOK_CONTENT_TYPE) {
|
||||||
WEBHOOK_CONTENT_TYPE = process.env.WEBHOOK_CONTENT_TYPE;
|
WEBHOOK_CONTENT_TYPE = process.env.WEBHOOK_CONTENT_TYPE;
|
||||||
}
|
}
|
||||||
//==========================云端环境变量的判断与接收=========================
|
// ==========================云端环境变量的判断与接收=========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sendNotify 推送通知功能
|
* sendNotify 推送通知功能
|
||||||
@@ -366,8 +354,8 @@ async function sendNotify(
|
|||||||
params = {},
|
params = {},
|
||||||
author = '\n\n本通知 By:https://github.com/whyour/qinglong',
|
author = '\n\n本通知 By:https://github.com/whyour/qinglong',
|
||||||
) {
|
) {
|
||||||
//提供6种通知
|
// 提供6种通知
|
||||||
desp += author; //增加作者信息,防止被贩卖等
|
desp += author; // 增加作者信息,防止被贩卖等
|
||||||
|
|
||||||
// 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔
|
// 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔
|
||||||
let skipTitle = process.env.SKIP_PUSH_TITLE;
|
let skipTitle = process.env.SKIP_PUSH_TITLE;
|
||||||
@@ -379,28 +367,28 @@ async function sendNotify(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
serverNotify(text, desp), //微信server酱
|
serverNotify(text, desp), // 微信server酱
|
||||||
pushPlusNotify(text, desp), //pushplus(推送加)
|
pushPlusNotify(text, desp), // pushplus(推送加)
|
||||||
]);
|
]);
|
||||||
//由于上述两种微信通知需点击进去才能查看到详情,故text(标题内容)携带了账号序号以及昵称信息,方便不点击也可知道是哪个京东哪个活动
|
// 由于上述两种微信通知需点击进去才能查看到详情,故text(标题内容)携带了账号序号以及昵称信息,方便不点击也可知道是哪个京东哪个活动
|
||||||
text = text.match(/.*?(?=\s?-)/g) ? text.match(/.*?(?=\s?-)/g)[0] : text;
|
text = text.match(/.*?(?=\s?-)/g) ? text.match(/.*?(?=\s?-)/g)[0] : text;
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
BarkNotify(text, desp, params), //iOS Bark APP
|
BarkNotify(text, desp, params), // iOS Bark APP
|
||||||
tgBotNotify(text, desp), //telegram 机器人
|
tgBotNotify(text, desp), // telegram 机器人
|
||||||
ddBotNotify(text, desp), //钉钉机器人
|
ddBotNotify(text, desp), // 钉钉机器人
|
||||||
qywxBotNotify(text, desp), //企业微信机器人
|
qywxBotNotify(text, desp), // 企业微信机器人
|
||||||
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
|
gotifyNotify(text, desp), // gotify
|
||||||
ChatNotify(text, desp), //synolog chat
|
ChatNotify(text, desp), // synolog chat
|
||||||
PushDeerNotify(text, desp), //PushDeer
|
PushDeerNotify(text, desp), // PushDeer
|
||||||
aibotkNotify(text, desp), //智能微秘书
|
aibotkNotify(text, desp), // 智能微秘书
|
||||||
fsBotNotify(text, desp), //飞书机器人
|
fsBotNotify(text, desp), // 飞书机器人
|
||||||
smtpNotify(text, desp), //SMTP 邮件
|
smtpNotify(text, desp), // SMTP 邮件
|
||||||
PushMeNotify(text, desp, params), //PushMe
|
pushMeNotify(text, desp, params), // PushMe
|
||||||
ChronocatNotify(text, desp), // Chronocat
|
chronocatNotify(text, desp), // Chronocat
|
||||||
webhookNotify(text, desp), //自定义通知
|
webhookNotify(text, desp), // 自定义通知
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +470,7 @@ function gobotNotify(text, desp) {
|
|||||||
function serverNotify(text, desp) {
|
function serverNotify(text, desp) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (SCKEY) {
|
if (SCKEY) {
|
||||||
//微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换
|
// 微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换
|
||||||
desp = desp.replace(/[\n\r]/g, '\n\n');
|
desp = desp.replace(/[\n\r]/g, '\n\n');
|
||||||
const options = {
|
const options = {
|
||||||
url: SCKEY.includes('SCT')
|
url: SCKEY.includes('SCT')
|
||||||
@@ -501,7 +489,7 @@ function serverNotify(text, desp) {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
} else {
|
} else {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
//server酱和Server酱·Turbo版的返回json格式不太一样
|
// server酱和Server酱·Turbo版的返回json格式不太一样
|
||||||
if (data.errno === 0 || data.data.errno === 0) {
|
if (data.errno === 0 || data.data.errno === 0) {
|
||||||
console.log('server酱发送通知消息成功🎉\n');
|
console.log('server酱发送通知消息成功🎉\n');
|
||||||
} else if (data.errno === 1024) {
|
} else if (data.errno === 1024) {
|
||||||
@@ -645,7 +633,7 @@ function tgBotNotify(text, desp) {
|
|||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (TG_BOT_TOKEN && TG_USER_ID) {
|
if (TG_BOT_TOKEN && TG_USER_ID) {
|
||||||
const options = {
|
const options = {
|
||||||
url: `https://${TG_API_HOST}/bot${TG_BOT_TOKEN}/sendMessage`,
|
url: `${TG_API_HOST}/bot${TG_BOT_TOKEN}/sendMessage`,
|
||||||
json: {
|
json: {
|
||||||
chat_id: `${TG_USER_ID}`,
|
chat_id: `${TG_USER_ID}`,
|
||||||
text: `${text}\n\n${desp}`,
|
text: `${text}\n\n${desp}`,
|
||||||
@@ -657,15 +645,19 @@ function tgBotNotify(text, desp) {
|
|||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
if (TG_PROXY_HOST && TG_PROXY_PORT) {
|
if (TG_PROXY_HOST && TG_PROXY_PORT) {
|
||||||
const tunnel = require('tunnel');
|
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
|
||||||
|
const options = {
|
||||||
|
keepAlive: true,
|
||||||
|
keepAliveMsecs: 1000,
|
||||||
|
maxSockets: 256,
|
||||||
|
maxFreeSockets: 256,
|
||||||
|
proxy: `http://${TG_PROXY_AUTH}${TG_PROXY_HOST}:${TG_PROXY_PORT}`,
|
||||||
|
};
|
||||||
|
const httpAgent = new HttpProxyAgent(options);
|
||||||
|
const httpsAgent = new HttpsProxyAgent(options);
|
||||||
const agent = {
|
const agent = {
|
||||||
https: tunnel.httpsOverHttp({
|
http: httpAgent,
|
||||||
proxy: {
|
https: httpsAgent,
|
||||||
host: TG_PROXY_HOST,
|
|
||||||
port: TG_PROXY_PORT * 1,
|
|
||||||
proxyAuth: TG_PROXY_AUTH,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
Object.assign(options, { agent });
|
Object.assign(options, { agent });
|
||||||
}
|
}
|
||||||
@@ -824,7 +816,18 @@ function ChangeUserId(desp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function qywxamNotify(text, desp) {
|
async function qywxamNotify(text, desp) {
|
||||||
|
const MAX_LENGTH = 900;
|
||||||
|
if (desp.length > MAX_LENGTH) {
|
||||||
|
let d = desp.substr(0, MAX_LENGTH) + "\n==More==";
|
||||||
|
await do_qywxamNotify(text, d);
|
||||||
|
await qywxamNotify(text, desp.substr(MAX_LENGTH));
|
||||||
|
} else {
|
||||||
|
return await do_qywxamNotify(text,desp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_qywxamNotify(text, desp) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (QYWX_AM) {
|
if (QYWX_AM) {
|
||||||
const QYWX_AM_AY = QYWX_AM.split(',');
|
const QYWX_AM_AY = QYWX_AM.split(',');
|
||||||
@@ -885,7 +888,7 @@ function qywxamNotify(text, desp) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!QYWX_AM_AY[4]) {
|
if (!QYWX_AM_AY[4]) {
|
||||||
//如不提供第四个参数,则默认进行文本消息类型推送
|
// 如不提供第四个参数,则默认进行文本消息类型推送
|
||||||
options = {
|
options = {
|
||||||
msgtype: 'text',
|
msgtype: 'text',
|
||||||
text: {
|
text: {
|
||||||
@@ -1137,31 +1140,31 @@ function fsBotNotify(text, desp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function smtpNotify(text, desp) {
|
async function smtpNotify(text, desp) {
|
||||||
if (![SMTP_SERVER, SMTP_EMAIL, SMTP_PASSWORD].every(Boolean)) {
|
if (![SMTP_EMAIL, SMTP_PASSWORD].every(Boolean) || !SMTP_SERVICE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nodemailer = require('nodemailer');
|
const nodemailer = require('nodemailer');
|
||||||
const transporter = nodemailer.createTransport(
|
const transporter = nodemailer.createTransport({
|
||||||
`${SMTP_SSL === 'true' ? 'smtps:' : 'smtp:'}//${SMTP_SERVER}`,
|
service: SMTP_SERVICE,
|
||||||
{
|
auth: {
|
||||||
auth: {
|
user: SMTP_EMAIL,
|
||||||
user: SMTP_EMAIL,
|
pass: SMTP_PASSWORD,
|
||||||
pass: SMTP_PASSWORD,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
const addr = SMTP_NAME ? `"${SMTP_NAME}" <${SMTP_EMAIL}>` : SMTP_EMAIL;
|
const addr = SMTP_NAME ? `"${SMTP_NAME}" <${SMTP_EMAIL}>` : SMTP_EMAIL;
|
||||||
const info = await transporter.sendMail({
|
const info = await transporter.sendMail({
|
||||||
from: addr,
|
from: addr,
|
||||||
to: addr,
|
to: addr,
|
||||||
subject: text,
|
subject: text,
|
||||||
text: desp,
|
html: `${desp.replace(/\n/g, '<br/>')}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!info.messageId) {
|
transporter.close();
|
||||||
|
|
||||||
|
if (info.messageId) {
|
||||||
console.log('SMTP发送通知消息成功🎉\n');
|
console.log('SMTP发送通知消息成功🎉\n');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1172,17 +1175,7 @@ async function smtpNotify(text, desp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function smtpNotify(text, desp) {
|
function pushMeNotify(text, desp, params = {}) {
|
||||||
return new Promise((resolve) => {
|
|
||||||
if (SMTP_SERVER && SMTP_SSL && SMTP_EMAIL && SMTP_PASSWORD && SMTP_NAME) {
|
|
||||||
// todo: Node.js并没有内置的 smtp 实现,需要调用外部库,因为不清楚这个文件的模块依赖情况,所以留给有缘人实现
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function PushMeNotify(text, desp, params = {}) {
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (PUSHME_KEY) {
|
if (PUSHME_KEY) {
|
||||||
const options = {
|
const options = {
|
||||||
@@ -1217,16 +1210,13 @@ function PushMeNotify(text, desp, params = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChronocatNotify(title, desp) {
|
function chronocatNotify(title, desp) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!CHRONOCAT_TOKEN || !CHRONOCAT_QQ || !CHRONOCAT_URL) {
|
if (!CHRONOCAT_TOKEN || !CHRONOCAT_QQ || !CHRONOCAT_URL) {
|
||||||
console.log(
|
resolve();
|
||||||
'CHRONOCAT 服务的 CHRONOCAT_URL 或 CHRONOCAT_QQ 未设置!!\n取消推送',
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('CHRONOCAT 服务启动');
|
|
||||||
const user_ids = CHRONOCAT_QQ.match(/user_id=(\d+)/g)?.map(
|
const user_ids = CHRONOCAT_QQ.match(/user_id=(\d+)/g)?.map(
|
||||||
(match) => match.split('=')[1],
|
(match) => match.split('=')[1],
|
||||||
);
|
);
|
||||||
@@ -1320,11 +1310,9 @@ function webhookNotify(text, desp) {
|
|||||||
got(formatUrl, options).then((resp) => {
|
got(formatUrl, options).then((resp) => {
|
||||||
try {
|
try {
|
||||||
if (resp.statusCode !== 200) {
|
if (resp.statusCode !== 200) {
|
||||||
console.log('自定义发送通知消息失败!!\n');
|
console.log(`自定义发送通知消息失败!!\n${resp.body}`);
|
||||||
console.log(resp.body);
|
|
||||||
} else {
|
} else {
|
||||||
console.log('自定义发送通知消息成功🎉。\n');
|
console.log(`自定义发送通知消息成功🎉。\n${resp.body}`);
|
||||||
console.log(resp.body);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.logErr(e, resp);
|
$.logErr(e, resp);
|
||||||
@@ -1338,6 +1326,31 @@ function webhookNotify(text, desp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseString(input) {
|
||||||
|
const regex = /(\w+):\s*((?:(?!\n\w+:).)*)/g;
|
||||||
|
const matches = {};
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(input)) !== null) {
|
||||||
|
const [, key, value] = match;
|
||||||
|
const _key = key.trim();
|
||||||
|
if (!_key || matches[_key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _value = value.trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonValue = JSON.parse(_value);
|
||||||
|
matches[_key] = jsonValue;
|
||||||
|
} catch (error) {
|
||||||
|
matches[_key] = _value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
function parseHeaders(headers) {
|
function parseHeaders(headers) {
|
||||||
if (!headers) return {};
|
if (!headers) return {};
|
||||||
|
|
||||||
@@ -1363,30 +1376,11 @@ function parseHeaders(headers) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseBody(body, contentType) {
|
function parseBody(body, contentType) {
|
||||||
if (!body) return '';
|
if (contentType === 'text/plain' || !body) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
const parsed = {};
|
const parsed = parseString(body);
|
||||||
let key;
|
|
||||||
let val;
|
|
||||||
let i;
|
|
||||||
|
|
||||||
body &&
|
|
||||||
body.split('\n').forEach(function parser(line) {
|
|
||||||
i = line.indexOf(':');
|
|
||||||
key = line.substring(0, i).trim().toLowerCase();
|
|
||||||
val = line.substring(i + 1).trim();
|
|
||||||
|
|
||||||
if (!key || parsed[key]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const jsonValue = JSON.parse(val);
|
|
||||||
parsed[key] = jsonValue;
|
|
||||||
} catch (error) {
|
|
||||||
parsed[key] = val;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case 'multipart/form-data':
|
case 'multipart/form-data':
|
||||||
|
|||||||
+81
-83
@@ -79,7 +79,7 @@ push_config = {
|
|||||||
'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE
|
'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE
|
||||||
|
|
||||||
'QYWX_ORIGIN': '', # 企业微信代理地址
|
'QYWX_ORIGIN': '', # 企业微信代理地址
|
||||||
|
|
||||||
'QYWX_AM': '', # 企业微信应用
|
'QYWX_AM': '', # 企业微信应用
|
||||||
|
|
||||||
'QYWX_KEY': '', # 企业微信机器人
|
'QYWX_KEY': '', # 企业微信机器人
|
||||||
@@ -102,11 +102,11 @@ push_config = {
|
|||||||
'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写
|
'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写
|
||||||
|
|
||||||
'PUSHME_KEY': '', # PushMe 酱的 PUSHME_KEY
|
'PUSHME_KEY': '', # PushMe 酱的 PUSHME_KEY
|
||||||
|
|
||||||
'CHRONOCAT_QQ': '', # qq号
|
'CHRONOCAT_QQ': '', # qq号
|
||||||
'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token
|
'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token
|
||||||
'CHRONOCAT_URL': '', # CHRONOCAT的url地址
|
'CHRONOCAT_URL': '', # CHRONOCAT的url地址
|
||||||
|
|
||||||
'WEBHOOK_URL': '', # 自定义通知 请求地址
|
'WEBHOOK_URL': '', # 自定义通知 请求地址
|
||||||
'WEBHOOK_BODY': '', # 自定义通知 请求体
|
'WEBHOOK_BODY': '', # 自定义通知 请求体
|
||||||
'WEBHOOK_HEADERS': '', # 自定义通知 请求头
|
'WEBHOOK_HEADERS': '', # 自定义通知 请求头
|
||||||
@@ -534,7 +534,7 @@ def telegram_bot(title: str, content: str) -> None:
|
|||||||
print("tg 服务启动")
|
print("tg 服务启动")
|
||||||
|
|
||||||
if push_config.get("TG_API_HOST"):
|
if push_config.get("TG_API_HOST"):
|
||||||
url = f"https://{push_config.get('TG_API_HOST')}/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage"
|
url = f"{push_config.get('TG_API_HOST')}/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage"
|
||||||
else:
|
else:
|
||||||
url = (
|
url = (
|
||||||
f"https://api.telegram.org/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage"
|
f"https://api.telegram.org/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage"
|
||||||
@@ -578,7 +578,9 @@ def aibotk(title: str, content: str) -> None:
|
|||||||
or not push_config.get("AIBOTK_TYPE")
|
or not push_config.get("AIBOTK_TYPE")
|
||||||
or not push_config.get("AIBOTK_NAME")
|
or not push_config.get("AIBOTK_NAME")
|
||||||
):
|
):
|
||||||
print("智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送")
|
print(
|
||||||
|
"智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
print("智能微秘书 服务启动")
|
print("智能微秘书 服务启动")
|
||||||
|
|
||||||
@@ -748,29 +750,25 @@ def parse_headers(headers):
|
|||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
def parse_body(body, content_type):
|
def parse_string(input_string):
|
||||||
if not body:
|
matches = {}
|
||||||
return ""
|
pattern = r'(\w+):\s*((?:(?!\n\w+:).)*)'
|
||||||
|
regex = re.compile(pattern)
|
||||||
parsed = {}
|
for match in regex.finditer(input_string):
|
||||||
lines = body.split("\n")
|
key, value = match.group(1).strip(), match.group(2).strip()
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
i = line.find(":")
|
|
||||||
if i == -1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
key = line[:i].strip().lower()
|
|
||||||
val = line[i + 1 :].strip()
|
|
||||||
|
|
||||||
if not key or key in parsed:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
json_value = json.loads(val)
|
json_value = json.loads(value)
|
||||||
parsed[key] = json_value
|
matches[key] = json_value
|
||||||
except:
|
except:
|
||||||
parsed[key] = val
|
matches[key] = value
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
def parse_body(body, content_type):
|
||||||
|
if not body or content_type == "text/plain":
|
||||||
|
return body
|
||||||
|
|
||||||
|
parsed = parse_string(input_string)
|
||||||
|
|
||||||
if content_type == "application/x-www-form-urlencoded":
|
if content_type == "application/x-www-form-urlencoded":
|
||||||
data = urlencode(parsed, doseq=True)
|
data = urlencode(parsed, doseq=True)
|
||||||
@@ -841,60 +839,61 @@ def one() -> str:
|
|||||||
return res["hitokoto"] + " ----" + res["from"]
|
return res["hitokoto"] + " ----" + res["from"]
|
||||||
|
|
||||||
|
|
||||||
if push_config.get("BARK_PUSH"):
|
def add_notify_function():
|
||||||
notify_function.append(bark)
|
if push_config.get("BARK_PUSH"):
|
||||||
if push_config.get("CONSOLE"):
|
notify_function.append(bark)
|
||||||
notify_function.append(console)
|
if push_config.get("CONSOLE"):
|
||||||
if push_config.get("DD_BOT_TOKEN") and push_config.get("DD_BOT_SECRET"):
|
notify_function.append(console)
|
||||||
notify_function.append(dingding_bot)
|
if push_config.get("DD_BOT_TOKEN") and push_config.get("DD_BOT_SECRET"):
|
||||||
if push_config.get("FSKEY"):
|
notify_function.append(dingding_bot)
|
||||||
notify_function.append(feishu_bot)
|
if push_config.get("FSKEY"):
|
||||||
if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"):
|
notify_function.append(feishu_bot)
|
||||||
notify_function.append(go_cqhttp)
|
if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"):
|
||||||
if push_config.get("GOTIFY_URL") and push_config.get("GOTIFY_TOKEN"):
|
notify_function.append(go_cqhttp)
|
||||||
notify_function.append(gotify)
|
if push_config.get("GOTIFY_URL") and push_config.get("GOTIFY_TOKEN"):
|
||||||
if push_config.get("IGOT_PUSH_KEY"):
|
notify_function.append(gotify)
|
||||||
notify_function.append(iGot)
|
if push_config.get("IGOT_PUSH_KEY"):
|
||||||
if push_config.get("PUSH_KEY"):
|
notify_function.append(iGot)
|
||||||
notify_function.append(serverJ)
|
if push_config.get("PUSH_KEY"):
|
||||||
if push_config.get("DEER_KEY"):
|
notify_function.append(serverJ)
|
||||||
notify_function.append(pushdeer)
|
if push_config.get("DEER_KEY"):
|
||||||
if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"):
|
notify_function.append(pushdeer)
|
||||||
notify_function.append(chat)
|
if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"):
|
||||||
if push_config.get("PUSH_PLUS_TOKEN"):
|
notify_function.append(chat)
|
||||||
notify_function.append(pushplus_bot)
|
if push_config.get("PUSH_PLUS_TOKEN"):
|
||||||
if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"):
|
notify_function.append(pushplus_bot)
|
||||||
notify_function.append(qmsg_bot)
|
if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"):
|
||||||
if push_config.get("QYWX_AM"):
|
notify_function.append(qmsg_bot)
|
||||||
notify_function.append(wecom_app)
|
if push_config.get("QYWX_AM"):
|
||||||
if push_config.get("QYWX_KEY"):
|
notify_function.append(wecom_app)
|
||||||
notify_function.append(wecom_bot)
|
if push_config.get("QYWX_KEY"):
|
||||||
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
|
notify_function.append(wecom_bot)
|
||||||
notify_function.append(telegram_bot)
|
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
|
||||||
if (
|
notify_function.append(telegram_bot)
|
||||||
push_config.get("AIBOTK_KEY")
|
if (
|
||||||
and push_config.get("AIBOTK_TYPE")
|
push_config.get("AIBOTK_KEY")
|
||||||
and push_config.get("AIBOTK_NAME")
|
and push_config.get("AIBOTK_TYPE")
|
||||||
):
|
and push_config.get("AIBOTK_NAME")
|
||||||
notify_function.append(aibotk)
|
):
|
||||||
if (
|
notify_function.append(aibotk)
|
||||||
push_config.get("SMTP_SERVER")
|
if (
|
||||||
and push_config.get("SMTP_SSL")
|
push_config.get("SMTP_SERVER")
|
||||||
and push_config.get("SMTP_EMAIL")
|
and push_config.get("SMTP_SSL")
|
||||||
and push_config.get("SMTP_PASSWORD")
|
and push_config.get("SMTP_EMAIL")
|
||||||
and push_config.get("SMTP_NAME")
|
and push_config.get("SMTP_PASSWORD")
|
||||||
):
|
and push_config.get("SMTP_NAME")
|
||||||
notify_function.append(smtp)
|
):
|
||||||
if push_config.get("PUSHME_KEY"):
|
notify_function.append(smtp)
|
||||||
notify_function.append(pushme)
|
if push_config.get("PUSHME_KEY"):
|
||||||
if (
|
notify_function.append(pushme)
|
||||||
push_config.get("CHRONOCAT_URL")
|
if (
|
||||||
and push_config.get("CHRONOCAT_QQ")
|
push_config.get("CHRONOCAT_URL")
|
||||||
and push_config.get("CHRONOCAT_TOKEN")
|
and push_config.get("CHRONOCAT_QQ")
|
||||||
):
|
and push_config.get("CHRONOCAT_TOKEN")
|
||||||
notify_function.append(chronocat)
|
):
|
||||||
if push_config.get("WEBHOOK_URL") and push_config.get("WEBHOOK_METHOD"):
|
notify_function.append(chronocat)
|
||||||
notify_function.append(custom_notify)
|
if push_config.get("WEBHOOK_URL") and push_config.get("WEBHOOK_METHOD"):
|
||||||
|
notify_function.append(custom_notify)
|
||||||
|
|
||||||
|
|
||||||
def send(title: str, content: str) -> None:
|
def send(title: str, content: str) -> None:
|
||||||
@@ -910,10 +909,9 @@ def send(title: str, content: str) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
hitokoto = push_config.get("HITOKOTO")
|
hitokoto = push_config.get("HITOKOTO")
|
||||||
|
content += "\n\n" + one() if hitokoto else ""
|
||||||
|
|
||||||
text = one() if hitokoto else ""
|
add_notify_function()
|
||||||
content += "\n\n" + text
|
|
||||||
|
|
||||||
ts = [
|
ts = [
|
||||||
threading.Thread(target=mode, args=(title, content), name=mode.__name__)
|
threading.Thread(target=mode, args=(title, content), name=mode.__name__)
|
||||||
for mode in notify_function
|
for mode in notify_function
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "dependence",
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"got": "^11.5.1",
|
|
||||||
"nodemailer": "^6.8.0",
|
|
||||||
"tough-cookie": "^4.0.0",
|
|
||||||
"tunnel": "0.0.6",
|
|
||||||
"ws": "^7.4.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-4
@@ -49,7 +49,7 @@ add_cron_api() {
|
|||||||
-H "Origin: http://0.0.0.0:5700" \
|
-H "Origin: http://0.0.0.0:5700" \
|
||||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||||
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\",\"sub_id\":$sub_id}" \
|
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"sub_id\":$sub_id}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo "$api" | jq -r .code)
|
code=$(echo "$api" | jq -r .code)
|
||||||
@@ -85,7 +85,7 @@ update_cron_api() {
|
|||||||
-H "Origin: http://0.0.0.0:5700" \
|
-H "Origin: http://0.0.0.0:5700" \
|
||||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||||
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \
|
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo "$api" | jq -r .code)
|
code=$(echo "$api" | jq -r .code)
|
||||||
@@ -117,7 +117,7 @@ update_cron_command_api() {
|
|||||||
-H "Origin: http://0.0.0.0:5700" \
|
-H "Origin: http://0.0.0.0:5700" \
|
||||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||||
--data-raw "{\"command\":\"$command\",\"id\":\"$id\"}" \
|
--data-raw "{\"command\":\"${command//\"/\\\"}\",\"id\":\"$id\"}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo "$api" | jq -r .code)
|
code=$(echo "$api" | jq -r .code)
|
||||||
@@ -196,7 +196,7 @@ notify_api() {
|
|||||||
-H "Origin: http://0.0.0.0:5700" \
|
-H "Origin: http://0.0.0.0:5700" \
|
||||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||||
--data-raw "{\"title\":\"$title\",\"content\":\"$content\"}" \
|
--data-raw "{\"title\":\"${title//\"/\\\"}\",\"content\":\"${content//\"/\\\"}\"}" \
|
||||||
--compressed
|
--compressed
|
||||||
)
|
)
|
||||||
code=$(echo "$api" | jq -r .code)
|
code=$(echo "$api" | jq -r .code)
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ fi
|
|||||||
echo -e "\nbot文件下载成功...\n"
|
echo -e "\nbot文件下载成功...\n"
|
||||||
|
|
||||||
echo -e "3、安装python3依赖...\n"
|
echo -e "3、安装python3依赖...\n"
|
||||||
if [[ $PipMirror ]]; then
|
|
||||||
pip3 config set global.index-url $PipMirror
|
|
||||||
fi
|
|
||||||
cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
|
cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
|
||||||
|
|
||||||
cd $dir_data
|
cd $dir_data
|
||||||
|
|||||||
+1
-13
@@ -9,9 +9,6 @@ reset_env() {
|
|||||||
npm_install_2 $dir_root
|
npm_install_2 $dir_root
|
||||||
echo -e "---> 青龙依赖安装完成\n"
|
echo -e "---> 青龙依赖安装完成\n"
|
||||||
|
|
||||||
echo -e "---> 3. 开始安装脚本依赖\n"
|
|
||||||
cp -f $dir_sample/package.json $dir_scripts/package.json
|
|
||||||
npm_install_2 $dir_scripts
|
|
||||||
echo -e "---> 脚本依赖安装完成\n"
|
echo -e "---> 脚本依赖安装完成\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,18 +73,9 @@ check_pm2() {
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
echo -e "=====> 开始检测"
|
echo -e "=====> 开始检测"
|
||||||
npm i -g pnpm@8.3.1
|
npm i -g pnpm@8.3.1 pm2 tsx
|
||||||
patch_version
|
patch_version
|
||||||
|
|
||||||
if [[ $PipMirror ]]; then
|
|
||||||
pip3 config set global.index-url $PipMirror
|
|
||||||
fi
|
|
||||||
if [[ $NpmMirror ]]; then
|
|
||||||
cd && pnpm config set registry $NpmMirror
|
|
||||||
pnpm install -g
|
|
||||||
fi
|
|
||||||
|
|
||||||
pnpm add -g pm2 tsx
|
|
||||||
reset_env
|
reset_env
|
||||||
copy_dep
|
copy_dep
|
||||||
check_ql
|
check_ql
|
||||||
|
|||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
store_env_vars() {
|
||||||
|
initial_vars=($(env | cut -d= -f1))
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_env_vars() {
|
||||||
|
for key in $(env | cut -d= -f1); do
|
||||||
|
if ! [[ " ${initial_vars[@]} " =~ " $key " ]]; then
|
||||||
|
unset "$key"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
store_env_vars
|
||||||
+4
-6
@@ -19,7 +19,6 @@ dir_update_log=$dir_log/update
|
|||||||
ql_static_repo=$dir_repo/static
|
ql_static_repo=$dir_repo/static
|
||||||
|
|
||||||
## 文件
|
## 文件
|
||||||
file_ecosystem_js=$dir_root/ecosystem.config.js
|
|
||||||
file_config_sample=$dir_sample/config.sample.sh
|
file_config_sample=$dir_sample/config.sample.sh
|
||||||
file_env=$dir_config/env.sh
|
file_env=$dir_config/env.sh
|
||||||
file_sharecode=$dir_config/sharecode.sh
|
file_sharecode=$dir_config/sharecode.sh
|
||||||
@@ -71,8 +70,8 @@ import_config() {
|
|||||||
ql_base_url=${QlBaseUrl:-"/"}
|
ql_base_url=${QlBaseUrl:-"/"}
|
||||||
ql_port=${QlPort:-"5700"}
|
ql_port=${QlPort:-"5700"}
|
||||||
command_timeout_time=${CommandTimeoutTime:-""}
|
command_timeout_time=${CommandTimeoutTime:-""}
|
||||||
proxy_url=${ProxyUrl:-""}
|
|
||||||
file_extensions=${RepoFileExtensions:-"js py"}
|
file_extensions=${RepoFileExtensions:-"js py"}
|
||||||
|
proxy_url=${ProxyUrl:-""}
|
||||||
current_branch=${QL_BRANCH}
|
current_branch=${QL_BRANCH}
|
||||||
|
|
||||||
if [[ -n "${DefaultCronRule}" ]]; then
|
if [[ -n "${DefaultCronRule}" ]]; then
|
||||||
@@ -294,7 +293,7 @@ git_clone_scripts() {
|
|||||||
|
|
||||||
set_proxy "$proxy"
|
set_proxy "$proxy"
|
||||||
|
|
||||||
git clone --depth=1 $part_cmd $url $dir 2>&1
|
git clone --depth=1 $part_cmd $url $dir
|
||||||
exit_status=$?
|
exit_status=$?
|
||||||
|
|
||||||
unset_proxy
|
unset_proxy
|
||||||
@@ -308,8 +307,9 @@ random_range() {
|
|||||||
|
|
||||||
reload_pm2() {
|
reload_pm2() {
|
||||||
cd $dir_root
|
cd $dir_root
|
||||||
|
restore_env_vars
|
||||||
pm2 flush &>/dev/null
|
pm2 flush &>/dev/null
|
||||||
env ALL_PROXY= HTTP_PROXY= HTTPS_PROXY= all_proxy= http_proxy= https_proxy= pm2 startOrGracefulReload $file_ecosystem_js --update-env
|
pm2 startOrGracefulReload ecosystem.config.js
|
||||||
}
|
}
|
||||||
|
|
||||||
diff_time() {
|
diff_time() {
|
||||||
@@ -361,8 +361,6 @@ format_timestamp() {
|
|||||||
patch_version() {
|
patch_version() {
|
||||||
git config --global pull.rebase false
|
git config --global pull.rebase false
|
||||||
|
|
||||||
cp -f $dir_root/.env.example $dir_root/.env
|
|
||||||
|
|
||||||
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
||||||
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
||||||
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
||||||
|
|||||||
+12
-19
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
dir_shell=$QL_DIR/shell
|
dir_shell=$QL_DIR/shell
|
||||||
|
. $dir_shell/env.sh
|
||||||
. $dir_shell/share.sh
|
. $dir_shell/share.sh
|
||||||
. $dir_shell/api.sh
|
. $dir_shell/api.sh
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ update_repo() {
|
|||||||
echo -e "\n拉取 ${uniq_path} 成功...\n"
|
echo -e "\n拉取 ${uniq_path} 成功...\n"
|
||||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
|
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
|
||||||
else
|
else
|
||||||
echo -e "\n拉取 ${uniq_path} 失败,请检查网络...\n"
|
echo -e "\n拉取 ${uniq_path} 失败,请检查日志...\n"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +245,7 @@ reload_qinglong() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$reload_target" == 'data' ]]; then
|
if [[ "$reload_target" == 'data' ]]; then
|
||||||
|
rm -rf ${dir_root}/data
|
||||||
cp -rf ${dir_tmp}/data ${dir_root}/
|
cp -rf ${dir_tmp}/data ${dir_root}/
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -265,8 +267,8 @@ update_qinglong() {
|
|||||||
echo -e "使用 ${mirror} 源更新...\n"
|
echo -e "使用 ${mirror} 源更新...\n"
|
||||||
|
|
||||||
local primary_branch="master"
|
local primary_branch="master"
|
||||||
if [[ "${QL_BRANCH}" == "develop" ]]; then
|
if [[ "${QL_BRANCH}" == "develop" ]] || [[ "${QL_BRANCH}" == "debian" ]] || [[ "${QL_BRANCH}" == "debian-dev" ]]; then
|
||||||
primary_branch="develop"
|
primary_branch="${QL_BRANCH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
wget -cqO "${dir_tmp}/ql.zip" "${downloadQLUrl}/${primary_branch}.zip"
|
wget -cqO "${dir_tmp}/ql.zip" "${downloadQLUrl}/${primary_branch}.zip"
|
||||||
@@ -299,11 +301,6 @@ update_qinglong_static() {
|
|||||||
|
|
||||||
check_update_dep() {
|
check_update_dep() {
|
||||||
echo -e "\n开始检测依赖...\n"
|
echo -e "\n开始检测依赖...\n"
|
||||||
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
|
|
||||||
cp -f $dir_sample/package.json $dir_scripts/package.json
|
|
||||||
npm_install_2 $dir_scripts
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $(diff $dir_root/package.json ${dir_tmp}/qinglong-${primary_branch}/package.json) ]]; then
|
if [[ $(diff $dir_root/package.json ${dir_tmp}/qinglong-${primary_branch}/package.json) ]]; then
|
||||||
npm_install_2 "${dir_tmp}/qinglong-${primary_branch}"
|
npm_install_2 "${dir_tmp}/qinglong-${primary_branch}"
|
||||||
fi
|
fi
|
||||||
@@ -475,8 +472,13 @@ main() {
|
|||||||
local log_path="${log_dir}/${log_time}.log"
|
local log_path="${log_dir}/${log_time}.log"
|
||||||
local file_path="$dir_log/$log_path"
|
local file_path="$dir_log/$log_path"
|
||||||
|
|
||||||
cmd=">> $file_path 2>&1"
|
cmd="2>&1 | tee -a $file_path"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
if [[ "$no_tee" == "true" ]]; then
|
||||||
|
cmd=">> $file_path 2>&1"
|
||||||
|
fi
|
||||||
|
if [[ "$real_time" == "true" ]]; then
|
||||||
|
cmd=""
|
||||||
|
fi
|
||||||
|
|
||||||
local time_format="%Y-%m-%d %H:%M:%S"
|
local time_format="%Y-%m-%d %H:%M:%S"
|
||||||
local time=$(date "+$time_format")
|
local time=$(date "+$time_format")
|
||||||
@@ -489,11 +491,6 @@ main() {
|
|||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
|
|
||||||
eval echo -e "请移除 -l 参数" $cmd
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
case $p1 in
|
case $p1 in
|
||||||
update)
|
update)
|
||||||
fix_config
|
fix_config
|
||||||
@@ -558,10 +555,6 @@ main() {
|
|||||||
if [[ "$p1" != "repo" ]] && [[ "$p1" != "raw" ]]; then
|
if [[ "$p1" != "repo" ]] && [[ "$p1" != "raw" ]]; then
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒 " $cmd
|
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒 " $cmd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f $file_path ]]; then
|
|
||||||
cat $file_path
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFromIconfontCN } from '@ant-design/icons';
|
import { createFromIconfontCN } from '@ant-design/icons';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_ob5y15ewlyq.js'],
|
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_lc939gab1iq.js'],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
+18
-4
@@ -100,12 +100,14 @@
|
|||||||
"删除中": "Deleting",
|
"删除中": "Deleting",
|
||||||
"已删除": "Deleted",
|
"已删除": "Deleted",
|
||||||
"删除失败": "Deletion Failed",
|
"删除失败": "Deletion Failed",
|
||||||
|
"已取消": "Cancelled",
|
||||||
"序号": "Number",
|
"序号": "Number",
|
||||||
"备注": "Remarks",
|
"备注": "Remarks",
|
||||||
"更新时间": "Update Time",
|
"更新时间": "Update Time",
|
||||||
"创建时间": "Creation Time",
|
"创建时间": "Creation Time",
|
||||||
"确认删除依赖": "Confirm to delete the dependency",
|
"确认删除依赖": "Confirm to delete the dependency",
|
||||||
"确认重新安装": "Confirm to reinstall",
|
"确认重新安装": "Confirm to reinstall",
|
||||||
|
"确认取消安装": "Confirm to cancel install",
|
||||||
"确认删除选中的依赖吗": "Confirm to delete the selected dependencies?",
|
"确认删除选中的依赖吗": "Confirm to delete the selected dependencies?",
|
||||||
"确认重新安装选中的依赖吗": "Confirm to reinstall the selected dependencies?",
|
"确认重新安装选中的依赖吗": "Confirm to reinstall the selected dependencies?",
|
||||||
"请输入名称": "Please enter a name",
|
"请输入名称": "Please enter a name",
|
||||||
@@ -141,7 +143,7 @@
|
|||||||
"服务启动超时": "Service startup timeout",
|
"服务启动超时": "Service startup timeout",
|
||||||
"请先按如下方式修复:": "Please fix it as follows:",
|
"请先按如下方式修复:": "Please fix it as follows:",
|
||||||
"1. 宿主机执行 docker run --rm -v\n /var/run/docker.sock:/var/run/docker.sock\n containrrr/watchtower -cR <容器名>": "1. Execute 'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR <container_name>' on the host machine",
|
"1. 宿主机执行 docker run --rm -v\n /var/run/docker.sock:/var/run/docker.sock\n containrrr/watchtower -cR <容器名>": "1. Execute 'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR <container_name>' on the host machine",
|
||||||
"2. 容器内执行 ql -l check、ql -l update": "2. Execute 'ql -l check' and 'ql -l update' inside the container",
|
"2. 容器内执行 ql check、ql update": "2. Execute 'ql check' and 'ql update' inside the container",
|
||||||
"3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果": "3. If the problem persists, execute 'pm2 logs' inside the container and copy the results",
|
"3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果": "3. If the problem persists, execute 'pm2 logs' inside the container and copy the results",
|
||||||
"提交 issue": "Submit an issue",
|
"提交 issue": "Submit an issue",
|
||||||
"启动中,请稍后...": "Starting, please wait...",
|
"启动中,请稍后...": "Starting, please wait...",
|
||||||
@@ -237,6 +239,7 @@
|
|||||||
"系统将在": "The system will restart in",
|
"系统将在": "The system will restart in",
|
||||||
"秒后自动刷新": "seconds and automatically refresh",
|
"秒后自动刷新": "seconds and automatically refresh",
|
||||||
"检查更新": "Check for Updates",
|
"检查更新": "Check for Updates",
|
||||||
|
"重新启动": "Reboot",
|
||||||
"确认删除应用": "Confirm to delete the application",
|
"确认删除应用": "Confirm to delete the application",
|
||||||
"确认重置": "Confirm to reset",
|
"确认重置": "Confirm to reset",
|
||||||
"确认重置应用": "Confirm to reset the application",
|
"确认重置应用": "Confirm to reset the application",
|
||||||
@@ -375,9 +378,9 @@
|
|||||||
"微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/": "After WeChat scan login, one-to-one or one-to-many push using the provided token (your Token). If PUSH_PLUS_USER is not provided, it defaults to one-to-one push. See reference at https://www.pushplus.plus/",
|
"微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/": "After WeChat scan login, one-to-one or one-to-many push using the provided token (your Token). If PUSH_PLUS_USER is not provided, it defaults to one-to-one push. See reference at https://www.pushplus.plus/",
|
||||||
"一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "The 'group code' for one-to-many push (one-to-many push -> your group (if none, create one) -> group code). If you are the creator of the group, you need to click 'View QR code' to scan and bind, otherwise, you won't receive group messages.",
|
"一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "The 'group code' for one-to-many push (one-to-many push -> your group (if none, create one) -> group code). If you are the creator of the group, you need to click 'View QR code' to scan and bind, otherwise, you won't receive group messages.",
|
||||||
"飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "Feishu group bot: https://www.feishu.cn/hc/zh-CN/articles/360024984973",
|
"飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "Feishu group bot: https://www.feishu.cn/hc/zh-CN/articles/360024984973",
|
||||||
"邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://nodemailer.com/smtp/well-known/": "Email service name, e.g., 126, 163, Gmail, QQ, etc. Supported list: https://nodemailer.com/smtp/well-known/",
|
"邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json": "Email service name, e.g., 126, 163, Gmail, QQ, etc. Supported list: https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json",
|
||||||
"邮箱地址": "Email Address",
|
"邮箱地址": "Email Address",
|
||||||
"邮箱SMTP授权码": "Email SMTP Authorization Code",
|
"SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定": "The SMTP login password may also be a special passphrase, depending on the specific email service provider's instructions",
|
||||||
"PushMe的Key,https://push.i-i.me/": "PushMe key, https://push.i-i.me/",
|
"PushMe的Key,https://push.i-i.me/": "PushMe key, https://push.i-i.me/",
|
||||||
"请求方法": "Request Method",
|
"请求方法": "Request Method",
|
||||||
"请求头Content-Type": "Request Header Content-Type",
|
"请求头Content-Type": "Request Header Content-Type",
|
||||||
@@ -393,6 +396,7 @@
|
|||||||
"系统": "System",
|
"系统": "System",
|
||||||
"个人": "Personal",
|
"个人": "Personal",
|
||||||
"重新安装": "Reinstall",
|
"重新安装": "Reinstall",
|
||||||
|
"取消安装": "Cancel Install",
|
||||||
"强制删除": "Force Delete",
|
"强制删除": "Force Delete",
|
||||||
"全部任务": "All Tasks",
|
"全部任务": "All Tasks",
|
||||||
"关联订阅": "Associate Subscription",
|
"关联订阅": "Associate Subscription",
|
||||||
@@ -466,5 +470,15 @@
|
|||||||
"个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx": "Individuals: user_id=individual QQ Groups fill in group_id=QQ Groups more than one with English; separated by the same time to support individuals and groups such as: user_id=xxx;group_id=xxxx;group_id=xxxxx",
|
"个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx": "Individuals: user_id=individual QQ Groups fill in group_id=QQ Groups more than one with English; separated by the same time to support individuals and groups such as: user_id=xxx;group_id=xxxx;group_id=xxxxx",
|
||||||
"docker安装在持久化config目录下的chronocat.yml文件可找到": "The docker installation can be found in the persistence config directory in the chronocat.yml file",
|
"docker安装在持久化config目录下的chronocat.yml文件可找到": "The docker installation can be found in the persistence config directory in the chronocat.yml file",
|
||||||
"请选择": "Please select",
|
"请选择": "Please select",
|
||||||
"请输入": "Please input"
|
"请输入": "Please input",
|
||||||
|
"依赖设置": "Dependence Settings",
|
||||||
|
"Node 软件包镜像源": "Node Software Package Mirror Source",
|
||||||
|
"Python 软件包镜像源": "Python Software Package Mirror Source",
|
||||||
|
"Linux 软件包镜像源": "Linux Software Package Mirror Source",
|
||||||
|
"代理与镜像源二选一即可": "Either Proxy or Mirror Source can be chosen",
|
||||||
|
"代理地址, 支持HTTP(S)/SOCK5": "Proxy Address, supports HTTP(S)/SOCK5",
|
||||||
|
"NPM 镜像源": "NPM Mirror Source",
|
||||||
|
"PyPI 镜像源": "PyPI Mirror Source",
|
||||||
|
"alpine linux 镜像源": "Alpine Linux Mirror Source",
|
||||||
|
"如果恢复失败,可进入容器执行": "If recovery fails, you can enter the container and execute"
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-4
@@ -100,12 +100,14 @@
|
|||||||
"删除中": "删除中",
|
"删除中": "删除中",
|
||||||
"已删除": "已删除",
|
"已删除": "已删除",
|
||||||
"删除失败": "删除失败",
|
"删除失败": "删除失败",
|
||||||
|
"已取消": "已取消",
|
||||||
"序号": "序号",
|
"序号": "序号",
|
||||||
"备注": "备注",
|
"备注": "备注",
|
||||||
"更新时间": "更新时间",
|
"更新时间": "更新时间",
|
||||||
"创建时间": "创建时间",
|
"创建时间": "创建时间",
|
||||||
"确认删除依赖": "确认删除依赖",
|
"确认删除依赖": "确认删除依赖",
|
||||||
"确认重新安装": "确认重新安装",
|
"确认重新安装": "确认重新安装",
|
||||||
|
"确认取消安装": "确认取消安装",
|
||||||
"确认删除选中的依赖吗": "确认删除选中的依赖吗",
|
"确认删除选中的依赖吗": "确认删除选中的依赖吗",
|
||||||
"确认重新安装选中的依赖吗": "确认重新安装选中的依赖吗",
|
"确认重新安装选中的依赖吗": "确认重新安装选中的依赖吗",
|
||||||
"请输入名称": "请输入名称",
|
"请输入名称": "请输入名称",
|
||||||
@@ -141,7 +143,7 @@
|
|||||||
"服务启动超时": "服务启动超时",
|
"服务启动超时": "服务启动超时",
|
||||||
"请先按如下方式修复:": "请先按如下方式修复:",
|
"请先按如下方式修复:": "请先按如下方式修复:",
|
||||||
"1. 宿主机执行 docker run --rm -v\n /var/run/docker.sock:/var/run/docker.sock\n containrrr/watchtower -cR <容器名>": "1. 宿主机执行 docker run --rm -v\n /var/run/docker.sock:/var/run/docker.sock\n containrrr/watchtower -cR <容器名>",
|
"1. 宿主机执行 docker run --rm -v\n /var/run/docker.sock:/var/run/docker.sock\n containrrr/watchtower -cR <容器名>": "1. 宿主机执行 docker run --rm -v\n /var/run/docker.sock:/var/run/docker.sock\n containrrr/watchtower -cR <容器名>",
|
||||||
"2. 容器内执行 ql -l check、ql -l update": "2. 容器内执行 ql -l check、ql -l update",
|
"2. 容器内执行 ql check、ql update": "2. 容器内执行 ql check、ql update",
|
||||||
"3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果": "3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果",
|
"3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果": "3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果",
|
||||||
"提交 issue": "提交 issue",
|
"提交 issue": "提交 issue",
|
||||||
"启动中,请稍后...": "启动中,请稍后...",
|
"启动中,请稍后...": "启动中,请稍后...",
|
||||||
@@ -237,6 +239,7 @@
|
|||||||
"系统将在": "系统将在",
|
"系统将在": "系统将在",
|
||||||
"秒后自动刷新": "秒后自动刷新",
|
"秒后自动刷新": "秒后自动刷新",
|
||||||
"检查更新": "检查更新",
|
"检查更新": "检查更新",
|
||||||
|
"重新启动": "重新启动",
|
||||||
"确认删除应用": "确认删除应用",
|
"确认删除应用": "确认删除应用",
|
||||||
"确认重置": "确认重置",
|
"确认重置": "确认重置",
|
||||||
"确认重置应用": "确认重置应用",
|
"确认重置应用": "确认重置应用",
|
||||||
@@ -375,9 +378,9 @@
|
|||||||
"微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/": "微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/",
|
"微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/": "微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/",
|
||||||
"一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)",
|
"一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)",
|
||||||
"飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973",
|
"飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973",
|
||||||
"邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://nodemailer.com/smtp/well-known/": "邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://nodemailer.com/smtp/well-known/",
|
"邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json": "邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json",
|
||||||
"邮箱地址": "邮箱地址",
|
"邮箱地址": "邮箱地址",
|
||||||
"邮箱SMTP授权码": "邮箱SMTP授权码",
|
"SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定": "SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定",
|
||||||
"PushMe的Key,https://push.i-i.me/": "PushMe的Key,https://push.i-i.me/",
|
"PushMe的Key,https://push.i-i.me/": "PushMe的Key,https://push.i-i.me/",
|
||||||
"请求方法": "请求方法",
|
"请求方法": "请求方法",
|
||||||
"请求头Content-Type": "请求头Content-Type",
|
"请求头Content-Type": "请求头Content-Type",
|
||||||
@@ -393,6 +396,7 @@
|
|||||||
"系统": "系统",
|
"系统": "系统",
|
||||||
"个人": "个人",
|
"个人": "个人",
|
||||||
"重新安装": "重新安装",
|
"重新安装": "重新安装",
|
||||||
|
"取消安装": "取消安装",
|
||||||
"强制删除": "强制删除",
|
"强制删除": "强制删除",
|
||||||
"全部任务": "全部任务",
|
"全部任务": "全部任务",
|
||||||
"关联订阅": "关联订阅",
|
"关联订阅": "关联订阅",
|
||||||
@@ -466,5 +470,15 @@
|
|||||||
"个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx": "个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx",
|
"个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx": "个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx",
|
||||||
"docker安装在持久化config目录下的chronocat.yml文件可找到": "docker安装在持久化config目录下的chronocat.yml文件可找到",
|
"docker安装在持久化config目录下的chronocat.yml文件可找到": "docker安装在持久化config目录下的chronocat.yml文件可找到",
|
||||||
"请选择": "请选择",
|
"请选择": "请选择",
|
||||||
"请输入": "请输入"
|
"请输入": "请输入",
|
||||||
|
"依赖设置": "依赖设置",
|
||||||
|
"Node 软件包镜像源": "Node 软件包镜像源",
|
||||||
|
"Python 软件包镜像源": "Python 软件包镜像源",
|
||||||
|
"Linux 软件包镜像源": "Linux 软件包镜像源",
|
||||||
|
"代理与镜像源二选一即可": "代理与镜像源二选一即可",
|
||||||
|
"代理地址, 支持HTTP(S)/SOCK5": "代理地址, 支持HTTP(S)/SOCK5",
|
||||||
|
"NPM 镜像源": "NPM 镜像源",
|
||||||
|
"PyPI 镜像源": "PyPI 镜像源",
|
||||||
|
"alpine linux 镜像源": "alpine linux 镜像源",
|
||||||
|
"如果恢复失败,可进入容器执行": "如果恢复失败,可进入容器执行"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import intl from 'react-intl-universal'
|
import intl from 'react-intl-universal';
|
||||||
import React, {
|
import React, {
|
||||||
PureComponent,
|
PureComponent,
|
||||||
Fragment,
|
Fragment,
|
||||||
@@ -15,6 +15,8 @@ import CodeMirror from '@uiw/react-codemirror';
|
|||||||
import { useOutletContext } from '@umijs/max';
|
import { useOutletContext } from '@umijs/max';
|
||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
import { langs } from '@uiw/codemirror-extensions-langs';
|
import { langs } from '@uiw/codemirror-extensions-langs';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { getEditorMode } from '@/utils';
|
||||||
|
|
||||||
const Config = () => {
|
const Config = () => {
|
||||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
@@ -25,13 +27,16 @@ const Config = () => {
|
|||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
const [language, setLanguage] = useState<string>('shell');
|
||||||
|
|
||||||
const getConfig = (name: string) => {
|
const getConfig = (name: string) => {
|
||||||
request.get(`${config.apiPrefix}configs/${name}`).then(({ code, data }) => {
|
request
|
||||||
if (code === 200) {
|
.get(`${config.apiPrefix}configs/detail?path=${encodeURIComponent(name)}`)
|
||||||
setValue(data);
|
.then(({ code, data }) => {
|
||||||
}
|
if (code === 200) {
|
||||||
});
|
setValue(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFiles = () => {
|
const getFiles = () => {
|
||||||
@@ -66,8 +71,18 @@ const Config = () => {
|
|||||||
setSelect(value);
|
setSelect(value);
|
||||||
setTitle(node.value);
|
setTitle(node.value);
|
||||||
getConfig(node.value);
|
getConfig(node.value);
|
||||||
|
const newMode = getEditorMode(value);
|
||||||
|
setLanguage(newMode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'mod+s',
|
||||||
|
(e) => {
|
||||||
|
updateConfig();
|
||||||
|
},
|
||||||
|
{ enableOnFormTags: ['textarea'], preventDefault: true },
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getFiles();
|
getFiles();
|
||||||
getConfig('config.sh');
|
getConfig('config.sh');
|
||||||
@@ -113,7 +128,7 @@ const Config = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Editor
|
<Editor
|
||||||
defaultLanguage="shell"
|
language={language}
|
||||||
value={value}
|
value={value}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
options={{
|
options={{
|
||||||
|
|||||||
@@ -107,10 +107,9 @@ const CronDetailModal = ({
|
|||||||
value={value}
|
value={value}
|
||||||
options={{
|
options={{
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
minimap: { enabled: false },
|
||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
fontFamily: 'Source Code Pro',
|
|
||||||
glyphMargin: false,
|
glyphMargin: false,
|
||||||
wordWrap: 'on',
|
|
||||||
}}
|
}}
|
||||||
onMount={(editor, monaco) => {
|
onMount={(editor, monaco) => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
@@ -120,21 +119,17 @@ const CronDetailModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onClickItem = (item: LogItem) => {
|
const onClickItem = (item: LogItem) => {
|
||||||
const url = `${config.apiPrefix}logs/${item.filename}?path=${
|
const url = `${config.apiPrefix}logs/detail?file=${item.filename}&path=${
|
||||||
item.directory || ''
|
item.directory || ''
|
||||||
}`;
|
}`;
|
||||||
localStorage.setItem('logCron', url);
|
localStorage.setItem('logCron', url);
|
||||||
setLogUrl(url);
|
setLogUrl(url);
|
||||||
request
|
request.get(url).then(({ code, data }) => {
|
||||||
.get(
|
if (code === 200) {
|
||||||
`${config.apiPrefix}logs/${item.filename}?path=${item.directory || ''}`,
|
setLog(data);
|
||||||
)
|
setIsLogModalVisible(true);
|
||||||
.then(({ code, data }) => {
|
}
|
||||||
if (code === 200) {
|
});
|
||||||
setLog(data);
|
|
||||||
setIsLogModalVisible(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTabChange = (key: string) => {
|
const onTabChange = (key: string) => {
|
||||||
@@ -160,7 +155,7 @@ const CronDetailModal = ({
|
|||||||
const [s, p] = result;
|
const [s, p] = result;
|
||||||
setScriptInfo({ parent: p, filename: s });
|
setScriptInfo({ parent: p, filename: s });
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
|
.get(`${config.apiPrefix}scripts/detail?file=${s}&path=${p || ''}`)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setValue(data);
|
setValue(data);
|
||||||
@@ -362,8 +357,13 @@ const CronDetailModal = ({
|
|||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<div className="crontab-title-wrapper">
|
<div className="crontab-title-wrapper">
|
||||||
<div>
|
<div style={{ minWidth: 0 }}>
|
||||||
<span>{currentCron.name}</span>
|
<Typography.Text
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
ellipsis={{ tooltip: currentCron.name }}
|
||||||
|
>
|
||||||
|
{currentCron.name}
|
||||||
|
</Typography.Text>
|
||||||
{currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && (
|
{currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && (
|
||||||
<Divider type="vertical"></Divider>
|
<Divider type="vertical"></Divider>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
.card-wrapper {
|
.card-wrapper {
|
||||||
.ant-card:last-child {
|
.ant-card:last-child {
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
min-height: 300px;
|
min-height: 0;
|
||||||
height: calc(90vh - 367px);
|
height: calc(80vh - 314px);
|
||||||
height: calc(90vh - var(--vh-offset, 0px) - 367px);
|
height: calc(80vh - var(--vh-offset, 0px) - 314px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
max-height: calc(80vh - 57px);
|
||||||
|
max-height: calc(80vh - var(--vh-offset, 57px));
|
||||||
|
word-wrap: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
@@ -195,4 +198,4 @@ body[data-mode='desktop'] {
|
|||||||
|
|
||||||
.cron.pinned-cron > td {
|
.cron.pinned-cron > td {
|
||||||
background: #f2f2f2;
|
background: #f2f2f2;
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-38
@@ -22,6 +22,7 @@ import {
|
|||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
CloseCircleOutlined,
|
CloseCircleOutlined,
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
|
MinusCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
@@ -36,20 +37,12 @@ import { SharedContext } from '@/layouts';
|
|||||||
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import WebSocketManager from '@/utils/websocket';
|
import WebSocketManager from '@/utils/websocket';
|
||||||
|
import { DependenceStatus, Status } from './type';
|
||||||
|
import IconFont from '@/components/iconfont';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
|
|
||||||
enum Status {
|
|
||||||
'安装中',
|
|
||||||
'已安装',
|
|
||||||
'安装失败',
|
|
||||||
'删除中',
|
|
||||||
'已删除',
|
|
||||||
'删除失败',
|
|
||||||
'队列中',
|
|
||||||
}
|
|
||||||
|
|
||||||
enum StatusColor {
|
enum StatusColor {
|
||||||
'processing',
|
'processing',
|
||||||
'success',
|
'success',
|
||||||
@@ -85,6 +78,10 @@ const StatusMap: Record<number, { icon: React.ReactNode; color: string }> = {
|
|||||||
icon: <ClockCircleOutlined />,
|
icon: <ClockCircleOutlined />,
|
||||||
color: 'default',
|
color: 'default',
|
||||||
},
|
},
|
||||||
|
7: {
|
||||||
|
icon: <MinusCircleOutlined />,
|
||||||
|
color: 'default',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Dependence = () => {
|
const Dependence = () => {
|
||||||
@@ -108,8 +105,41 @@ const Dependence = () => {
|
|||||||
key: 'status',
|
key: 'status',
|
||||||
width: 120,
|
width: 120,
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: intl.get('队列中'),
|
||||||
|
value: DependenceStatus.queued,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.get('安装中'),
|
||||||
|
value: DependenceStatus.installing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.get('已安装'),
|
||||||
|
value: DependenceStatus.installed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.get('安装失败'),
|
||||||
|
value: DependenceStatus.installFailed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.get('删除中'),
|
||||||
|
value: DependenceStatus.removing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.get('已删除'),
|
||||||
|
value: DependenceStatus.removed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.get('删除失败'),
|
||||||
|
value: DependenceStatus.removeFailed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.get('已取消'),
|
||||||
|
value: DependenceStatus.cancelled,
|
||||||
|
},
|
||||||
|
],
|
||||||
render: (text: string, record: any, index: number) => {
|
render: (text: string, record: any, index: number) => {
|
||||||
console.log(record.status);
|
|
||||||
return (
|
return (
|
||||||
<Space size="middle" style={{ cursor: 'text' }}>
|
<Space size="middle" style={{ cursor: 'text' }}>
|
||||||
<Tag
|
<Tag
|
||||||
@@ -155,35 +185,46 @@ const Dependence = () => {
|
|||||||
const isPc = !isPhone;
|
const isPc = !isPhone;
|
||||||
return (
|
return (
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
<Tooltip title={isPc ? intl.get('日志') : ''}>
|
{![Status.队列中, Status.已取消].includes(record.status) && (
|
||||||
<a
|
<Tooltip title={isPc ? intl.get('日志') : ''}>
|
||||||
onClick={() => {
|
<a
|
||||||
setLogDependence({ ...record, timestamp: Date.now() });
|
onClick={() => {
|
||||||
}}
|
setLogDependence({ ...record, timestamp: Date.now() });
|
||||||
>
|
}}
|
||||||
<FileTextOutlined />
|
>
|
||||||
</a>
|
<FileTextOutlined />
|
||||||
</Tooltip>
|
</a>
|
||||||
{record.status !== Status.安装中 &&
|
</Tooltip>
|
||||||
record.status !== Status.删除中 && (
|
)}
|
||||||
<>
|
{[Status.队列中, Status.安装中, Status.删除中].includes(
|
||||||
<Tooltip title={isPc ? intl.get('重新安装') : ''}>
|
record.status,
|
||||||
<a onClick={() => reInstallDependence(record, index)}>
|
) ? (
|
||||||
<BugOutlined />
|
<Tooltip title={isPc ? intl.get('取消安装') : ''}>
|
||||||
</a>
|
<a onClick={() => cancelDependence(record)}>
|
||||||
</Tooltip>
|
<IconFont type="ql-icon-quxiaoanzhuang" />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Tooltip title={isPc ? intl.get('重新安装') : ''}>
|
||||||
|
<a onClick={() => reInstallDependence(record, index)}>
|
||||||
|
<BugOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
{Status.已安装 === record.status && (
|
||||||
<Tooltip title={isPc ? intl.get('删除') : ''}>
|
<Tooltip title={isPc ? intl.get('删除') : ''}>
|
||||||
<a onClick={() => deleteDependence(record, index)}>
|
<a onClick={() => deleteDependence(record, index)}>
|
||||||
<DeleteOutlined />
|
<DeleteOutlined />
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={isPc ? intl.get('强制删除') : ''}>
|
)}
|
||||||
<a onClick={() => deleteDependence(record, index, true)}>
|
<Tooltip title={isPc ? intl.get('强制删除') : ''}>
|
||||||
<DeleteFilled />
|
<a onClick={() => deleteDependence(record, index, true)}>
|
||||||
</a>
|
<DeleteFilled />
|
||||||
</Tooltip>
|
</a>
|
||||||
</>
|
</Tooltip>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -201,11 +242,15 @@ const Dependence = () => {
|
|||||||
const tableRef = useRef<HTMLDivElement>(null);
|
const tableRef = useRef<HTMLDivElement>(null);
|
||||||
const tableScrollHeight = useTableScrollHeight(tableRef, 59);
|
const tableScrollHeight = useTableScrollHeight(tableRef, 59);
|
||||||
|
|
||||||
const getDependencies = () => {
|
const getDependencies = (status?: number[]) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(
|
.get(
|
||||||
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
|
`${
|
||||||
|
config.apiPrefix
|
||||||
|
}dependencies?searchValue=${searchText}&type=${type}&status=${
|
||||||
|
status || ''
|
||||||
|
}`,
|
||||||
)
|
)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
@@ -290,6 +335,31 @@ const Dependence = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cancelDependence = (record: any) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: intl.get('确认取消安装'),
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
{intl.get('确认取消安装')}{' '}
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{record.name}
|
||||||
|
</Text>{' '}
|
||||||
|
{intl.get('吗')}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}dependencies/cancel`, [record.id])
|
||||||
|
.then(() => {
|
||||||
|
getDependencies();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleCancel = (dependence?: any[]) => {
|
const handleCancel = (dependence?: any[]) => {
|
||||||
setIsModalVisible(false);
|
setIsModalVisible(false);
|
||||||
dependence && handleDependence(dependence);
|
dependence && handleDependence(dependence);
|
||||||
@@ -421,7 +491,7 @@ const Dependence = () => {
|
|||||||
}
|
}
|
||||||
return _result;
|
return _result;
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 300);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,6 +609,9 @@ const Dependence = () => {
|
|||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ x: 768, y: tableScrollHeight }}
|
scroll={{ x: 768, y: tableScrollHeight }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
onChange={(pagination, filters) => {
|
||||||
|
getDependencies(filters?.status as number[]);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
import Ansi from 'ansi-to-react';
|
import Ansi from 'ansi-to-react';
|
||||||
import WebSocketManager from '@/utils/websocket';
|
import WebSocketManager from '@/utils/websocket';
|
||||||
|
import { Status } from './type';
|
||||||
|
|
||||||
const DependenceLogModal = ({
|
const DependenceLogModal = ({
|
||||||
dependence,
|
dependence,
|
||||||
@@ -96,7 +97,11 @@ const DependenceLogModal = ({
|
|||||||
|
|
||||||
const handleMessage = (payload: any) => {
|
const handleMessage = (payload: any) => {
|
||||||
const { message, references } = payload;
|
const { message, references } = payload;
|
||||||
if (references.length > 0 && references.includes(dependence.id)) {
|
if (
|
||||||
|
references.length > 0 &&
|
||||||
|
references.includes(dependence.id) &&
|
||||||
|
[Status.删除中, Status.安装中].includes(dependence.status)
|
||||||
|
) {
|
||||||
if (message.includes('结束时间')) {
|
if (message.includes('结束时间')) {
|
||||||
setExecuting(false);
|
setExecuting(false);
|
||||||
setIsRemoveFailed(message.includes('删除失败'));
|
setIsRemoveFailed(message.includes('删除失败'));
|
||||||
@@ -108,11 +113,13 @@ const DependenceLogModal = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ws = WebSocketManager.getInstance();
|
const ws = WebSocketManager.getInstance();
|
||||||
ws.subscribe('installDependence', handleMessage);
|
ws.subscribe('installDependence', handleMessage);
|
||||||
|
ws.subscribe('uninstallDependence', handleMessage);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ws.unsubscribe('installDependence', handleMessage);
|
ws.unsubscribe('installDependence', handleMessage);
|
||||||
|
ws.unsubscribe('uninstallDependence', handleMessage);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [dependence]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsPhone(document.body.clientWidth < 768);
|
setIsPhone(document.body.clientWidth < 768);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export enum DependenceStatus {
|
||||||
|
'installing',
|
||||||
|
'installed',
|
||||||
|
'installFailed',
|
||||||
|
'removing',
|
||||||
|
'removed',
|
||||||
|
'removeFailed',
|
||||||
|
'queued',
|
||||||
|
'cancelled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Status {
|
||||||
|
'安装中',
|
||||||
|
'已安装',
|
||||||
|
'安装失败',
|
||||||
|
'删除中',
|
||||||
|
'已删除',
|
||||||
|
'删除失败',
|
||||||
|
'队列中',
|
||||||
|
'已取消',
|
||||||
|
}
|
||||||
+13
-18
@@ -9,22 +9,24 @@ import { DiffEditor } from '@monaco-editor/react';
|
|||||||
import ReactDiffViewer from 'react-diff-viewer';
|
import ReactDiffViewer from 'react-diff-viewer';
|
||||||
import { useOutletContext } from '@umijs/max';
|
import { useOutletContext } from '@umijs/max';
|
||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
|
import { getEditorMode } from '@/utils';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const Diff = () => {
|
const Diff = () => {
|
||||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const [origin, setOrigin] = useState('config.sample.sh');
|
const [origin, setOrigin] = useState('sample/config.sample.sh');
|
||||||
const [current, setCurrent] = useState('config.sh');
|
const [current, setCurrent] = useState('config.sh');
|
||||||
const [originValue, setOriginValue] = useState('');
|
const [originValue, setOriginValue] = useState('');
|
||||||
const [currentValue, setCurrentValue] = useState('');
|
const [currentValue, setCurrentValue] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [files, setFiles] = useState<any[]>([]);
|
const [files, setFiles] = useState<any[]>([]);
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
|
const [language, setLanguage] = useState<string>('shell');
|
||||||
|
|
||||||
const getConfig = () => {
|
const getConfig = () => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}configs/${current}`)
|
.get(`${config.apiPrefix}configs/detail?path=${encodeURIComponent(current)}`)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setCurrentValue(data);
|
setCurrentValue(data);
|
||||||
@@ -34,7 +36,7 @@ const Diff = () => {
|
|||||||
|
|
||||||
const getSample = () => {
|
const getSample = () => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}configs/${origin}`)
|
.get(`${config.apiPrefix}configs/detail?path=${encodeURIComponent(origin)}`)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setOriginValue(data);
|
setOriginValue(data);
|
||||||
@@ -62,7 +64,7 @@ const Diff = () => {
|
|||||||
const getFiles = () => {
|
const getFiles = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}configs/files`)
|
.get(`${config.apiPrefix}configs/sample`)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setFiles(data);
|
setFiles(data);
|
||||||
@@ -71,12 +73,11 @@ const Diff = () => {
|
|||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const originFileChange = (value: string) => {
|
const originFileChange = (value: string, op) => {
|
||||||
|
setCurrent(op.extra.target);
|
||||||
setOrigin(value);
|
setOrigin(value);
|
||||||
};
|
const newMode = getEditorMode(value);
|
||||||
|
setLanguage(newMode);
|
||||||
const currentFileChange = (value: string) => {
|
|
||||||
setCurrent(value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -112,7 +113,7 @@ const Diff = () => {
|
|||||||
<Form.Item label={intl.get('源文件')}>
|
<Form.Item label={intl.get('源文件')}>
|
||||||
<Select value={origin} onChange={originFileChange}>
|
<Select value={origin} onChange={originFileChange}>
|
||||||
{files.map((x) => (
|
{files.map((x) => (
|
||||||
<Option key={x.value} value={x.value}>
|
<Option key={x.value} value={x.value} extra={x}>
|
||||||
{x.title}
|
{x.title}
|
||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
@@ -121,13 +122,7 @@ const Diff = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item label={intl.get('当前文件')}>
|
<Form.Item label={intl.get('当前文件')}>
|
||||||
<Select value={current} onChange={currentFileChange}>
|
<span className="ant-form-text">{current}</span>
|
||||||
{files.map((x) => (
|
|
||||||
<Option key={x.value} value={x.value}>
|
|
||||||
{x.title}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -159,7 +154,7 @@ const Diff = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DiffEditor
|
<DiffEditor
|
||||||
language={'shell'}
|
language={language}
|
||||||
original={originValue}
|
original={originValue}
|
||||||
modified={currentValue}
|
modified={currentValue}
|
||||||
options={{
|
options={{
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ const Error = () => {
|
|||||||
/var/run/docker.sock:/var/run/docker.sock
|
/var/run/docker.sock:/var/run/docker.sock
|
||||||
containrrr/watchtower -cR <容器名>
|
containrrr/watchtower -cR <容器名>
|
||||||
</div>
|
</div>
|
||||||
<div>{intl.get('2. 容器内执行 ql -l check、ql -l update')}</div>
|
<div>{intl.get('2. 容器内执行 ql check、ql update')}</div>
|
||||||
<div>
|
<div>
|
||||||
{intl.get(
|
{intl.get(
|
||||||
'3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果'
|
'3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果'
|
||||||
|
|||||||
@@ -95,6 +95,53 @@ const Initialization = () => {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('通知设置'),
|
||||||
|
content: (
|
||||||
|
<Form onFinish={submitNotification} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
label={intl.get('通知方式')}
|
||||||
|
name="type"
|
||||||
|
rules={[{ required: true, message: intl.get('请选择通知方式') }]}
|
||||||
|
style={{ maxWidth: 350 }}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
onChange={notificationModeChange}
|
||||||
|
placeholder={intl.get('请选择通知方式')}
|
||||||
|
>
|
||||||
|
{config.notificationModes
|
||||||
|
.filter((x) => x.value !== 'closed')
|
||||||
|
.map((x) => (
|
||||||
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
{fields.map((x) => (
|
||||||
|
<Form.Item
|
||||||
|
key={x.label}
|
||||||
|
label={x.label}
|
||||||
|
name={x.label}
|
||||||
|
extra={x.tip}
|
||||||
|
rules={[{ required: x.required }]}
|
||||||
|
style={{ maxWidth: 400 }}
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
autoSize={{ minRows: 1, maxRows: 5 }}
|
||||||
|
placeholder={`请输入${x.label}`}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
<Button type="primary" htmlType="submit" loading={loading}>
|
||||||
|
{intl.get('保存')}
|
||||||
|
</Button>
|
||||||
|
<Button type="link" htmlType="button" onClick={() => next()}>
|
||||||
|
{intl.get('跳过')}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: intl.get('账户设置'),
|
title: intl.get('账户设置'),
|
||||||
content: (
|
content: (
|
||||||
@@ -152,53 +199,6 @@ const Initialization = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: intl.get('通知设置'),
|
|
||||||
content: (
|
|
||||||
<Form onFinish={submitNotification} layout="vertical">
|
|
||||||
<Form.Item
|
|
||||||
label={intl.get('通知方式')}
|
|
||||||
name="type"
|
|
||||||
rules={[{ required: true, message: intl.get('请选择通知方式') }]}
|
|
||||||
style={{ maxWidth: 350 }}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
onChange={notificationModeChange}
|
|
||||||
placeholder={intl.get('请选择通知方式')}
|
|
||||||
>
|
|
||||||
{config.notificationModes
|
|
||||||
.filter((x) => x.value !== 'closed')
|
|
||||||
.map((x) => (
|
|
||||||
<Option key={x.value} value={x.value}>
|
|
||||||
{x.label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
{fields.map((x) => (
|
|
||||||
<Form.Item
|
|
||||||
key={x.label}
|
|
||||||
label={x.label}
|
|
||||||
name={x.label}
|
|
||||||
extra={x.tip}
|
|
||||||
rules={[{ required: x.required }]}
|
|
||||||
style={{ maxWidth: 400 }}
|
|
||||||
>
|
|
||||||
<Input.TextArea
|
|
||||||
autoSize={{ minRows: 1, maxRows: 5 }}
|
|
||||||
placeholder={`请输入${x.label}`}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Button type="primary" htmlType="submit" loading={loading}>
|
|
||||||
{intl.get('保存')}
|
|
||||||
</Button>
|
|
||||||
<Button type="link" htmlType="button" onClick={() => next()}>
|
|
||||||
{intl.get('跳过')}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: intl.get('完成安装'),
|
title: intl.get('完成安装'),
|
||||||
content: (
|
content: (
|
||||||
|
|||||||
+32
-10
@@ -1,4 +1,4 @@
|
|||||||
import intl from 'react-intl-universal'
|
import intl from 'react-intl-universal';
|
||||||
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
TreeSelect,
|
TreeSelect,
|
||||||
@@ -25,13 +25,14 @@ import { depthFirstSearch } from '@/utils';
|
|||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||||
|
import prettyBytes from 'pretty-bytes';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const Log = () => {
|
const Log = () => {
|
||||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const [value, setValue] = useState(intl.get('请选择日志文件'));
|
const [value, setValue] = useState(intl.get('请选择日志文件'));
|
||||||
const [select, setSelect] = useState<string>('');
|
const [select, setSelect] = useState<string>(intl.get('请选择日志文件'));
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [height, setHeight] = useState<number>();
|
const [height, setHeight] = useState<number>();
|
||||||
@@ -54,7 +55,11 @@ const Log = () => {
|
|||||||
|
|
||||||
const getLog = (node: any) => {
|
const getLog = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
|
.get(
|
||||||
|
`${config.apiPrefix}logs/detail?file=${node.title}&path=${
|
||||||
|
node.parent || ''
|
||||||
|
}`,
|
||||||
|
)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setValue(data);
|
setValue(data);
|
||||||
@@ -63,13 +68,13 @@ const Log = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
setCurrentNode(node);
|
|
||||||
setSelect(value);
|
|
||||||
|
|
||||||
if (node.key === select || !value) {
|
if (node.key === select || !value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCurrentNode(node);
|
||||||
|
setSelect(value);
|
||||||
|
|
||||||
if (node.type === 'directory') {
|
if (node.type === 'directory') {
|
||||||
setValue(intl.get('请选择日志文件'));
|
setValue(intl.get('请选择日志文件'));
|
||||||
return;
|
return;
|
||||||
@@ -117,7 +122,8 @@ const Log = () => {
|
|||||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
{select}
|
{select}
|
||||||
</Text>
|
</Text>
|
||||||
{intl.get('文件')}{currentNode.type === 'directory' ? intl.get('夹下所以日志') : ''}
|
{intl.get('文件')}
|
||||||
|
{currentNode.type === 'directory' ? intl.get('夹下所以日志') : ''}
|
||||||
{intl.get(',删除后不可恢复')}
|
{intl.get(',删除后不可恢复')}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@@ -181,7 +187,24 @@ const Log = () => {
|
|||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper log-wrapper"
|
className="ql-container-wrapper log-wrapper"
|
||||||
title={select}
|
title={
|
||||||
|
<>
|
||||||
|
{select}
|
||||||
|
{currentNode?.type === 'file' && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: 6,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#999',
|
||||||
|
display: 'inline-block',
|
||||||
|
height: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{prettyBytes(currentNode.size)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
extra={
|
extra={
|
||||||
isPhone
|
isPhone
|
||||||
@@ -266,11 +289,10 @@ const Log = () => {
|
|||||||
options={{
|
options={{
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
minimap: { enabled: false },
|
||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
fontFamily: 'Source Code Pro',
|
|
||||||
folding: false,
|
folding: false,
|
||||||
glyphMargin: false,
|
glyphMargin: false,
|
||||||
wordWrap: 'on',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const EditModal = ({
|
|||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [language, setLanguage] = useState<string>('javascript');
|
const [language, setLanguage] = useState<string>('javascript');
|
||||||
const [cNode, setCNode] = useState<any>();
|
const [cNode, setCNode] = useState<any>();
|
||||||
const [selectedKey, setSelectedKey] = useState<string>('');
|
const [selectedKey, setSelectedKey] = useState<string>();
|
||||||
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
||||||
const [settingModalVisible, setSettingModalVisible] =
|
const [settingModalVisible, setSettingModalVisible] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@@ -67,7 +67,11 @@ const EditModal = ({
|
|||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
.get(
|
||||||
|
`${config.apiPrefix}scripts/detail?file=${node.title}&path=${
|
||||||
|
node.parent || ''
|
||||||
|
}`,
|
||||||
|
)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setValue(data);
|
setValue(data);
|
||||||
@@ -148,7 +152,7 @@ const EditModal = ({
|
|||||||
<>
|
<>
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
treeExpandAction="click"
|
treeExpandAction="click"
|
||||||
style={{ marginRight: 8, width: 150 }}
|
style={{ marginRight: 8, width: 300 }}
|
||||||
value={selectedKey}
|
value={selectedKey}
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={treeData}
|
treeData={treeData}
|
||||||
@@ -156,6 +160,7 @@ const EditModal = ({
|
|||||||
fieldNames={{ value: 'key', label: 'title' }}
|
fieldNames={{ value: 'key', label: 'title' }}
|
||||||
showSearch
|
showSearch
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
treeLine={{ showLeafIcon: true }}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
value={language}
|
value={language}
|
||||||
|
|||||||
+94
-22
@@ -44,14 +44,14 @@ import uniq from 'lodash/uniq';
|
|||||||
import IconFont from '@/components/iconfont';
|
import IconFont from '@/components/iconfont';
|
||||||
import RenameModal from './renameModal';
|
import RenameModal from './renameModal';
|
||||||
import { langs } from '@uiw/codemirror-extensions-langs';
|
import { langs } from '@uiw/codemirror-extensions-langs';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import prettyBytes from 'pretty-bytes';
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const Script = () => {
|
const Script = () => {
|
||||||
const { headerStyle, isPhone, theme } =
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
useOutletContext<SharedContext>();
|
|
||||||
const [value, setValue] = useState(intl.get('请选择脚本文件'));
|
const [value, setValue] = useState(intl.get('请选择脚本文件'));
|
||||||
const [select, setSelect] = useState<string>('');
|
const [select, setSelect] = useState<string>(intl.get('请选择脚本文件'));
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [mode, setMode] = useState('');
|
const [mode, setMode] = useState('');
|
||||||
@@ -84,9 +84,9 @@ const Script = () => {
|
|||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(
|
.get(
|
||||||
`${config.apiPrefix}scripts/${encodeURIComponent(node.title)}?path=${
|
`${config.apiPrefix}scripts/detail?file=${encodeURIComponent(
|
||||||
node.parent || ''
|
node.title,
|
||||||
}`,
|
)}&path=${node.parent || ''}`,
|
||||||
)
|
)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
@@ -115,13 +115,13 @@ const Script = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value: any, node: any) => {
|
const onSelect = (value: any, node: any) => {
|
||||||
setSelect(node.key);
|
|
||||||
setCurrentNode(node);
|
|
||||||
|
|
||||||
if (node.key === select || !value) {
|
if (node.key === select || !value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSelect(node.key);
|
||||||
|
setCurrentNode(node);
|
||||||
|
|
||||||
if (node.type === 'directory') {
|
if (node.type === 'directory') {
|
||||||
setValue(intl.get('请选择脚本文件'));
|
setValue(intl.get('请选择脚本文件'));
|
||||||
return;
|
return;
|
||||||
@@ -135,6 +135,10 @@ const Script = () => {
|
|||||||
|
|
||||||
const onTreeSelect = useCallback(
|
const onTreeSelect = useCallback(
|
||||||
(keys: Key[], e: any) => {
|
(keys: Key[], e: any) => {
|
||||||
|
const node = e.node;
|
||||||
|
if (node.key === select && isEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const content = editorRef.current
|
const content = editorRef.current
|
||||||
? editorRef.current.getValue().replace(/\r\n/g, '\n')
|
? editorRef.current.getValue().replace(/\r\n/g, '\n')
|
||||||
: value;
|
: value;
|
||||||
@@ -155,7 +159,7 @@ const Script = () => {
|
|||||||
onSelect(keys[0], e.node);
|
onSelect(keys[0], e.node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[value],
|
[value, select, isEditing],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
@@ -187,6 +191,14 @@ const Script = () => {
|
|||||||
setExpandedKeys(expKeys);
|
setExpandedKeys(expKeys);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDoubleClick = (e: any, node: any) => {
|
||||||
|
if (node.type === 'file') {
|
||||||
|
setSelect(node.key);
|
||||||
|
setCurrentNode(node);
|
||||||
|
setIsEditing(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const editFile = () => {
|
const editFile = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
@@ -352,7 +364,7 @@ const Script = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initState = () => {
|
const initState = () => {
|
||||||
setSelect('');
|
setSelect(intl.get('请选择脚本文件'));
|
||||||
setCurrentNode(null);
|
setCurrentNode(null);
|
||||||
setValue(intl.get('请选择脚本文件'));
|
setValue(intl.get('请选择脚本文件'));
|
||||||
};
|
};
|
||||||
@@ -367,6 +379,46 @@ const Script = () => {
|
|||||||
}
|
}
|
||||||
}, [treeDom.current, data]);
|
}, [treeDom.current, data]);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'mod+s',
|
||||||
|
(e) => {
|
||||||
|
if (isEditing) {
|
||||||
|
saveFile();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ enableOnFormTags: ['textarea'], preventDefault: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'mod+d',
|
||||||
|
(e) => {
|
||||||
|
if (currentNode.title) {
|
||||||
|
deleteFile();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ preventDefault: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'mod+o',
|
||||||
|
(e) => {
|
||||||
|
if (!isEditing) {
|
||||||
|
addFile();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ preventDefault: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'mod+e',
|
||||||
|
(e) => {
|
||||||
|
if (currentNode.title) {
|
||||||
|
cancelEdit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ preventDefault: true },
|
||||||
|
);
|
||||||
|
|
||||||
const action = (key: string | number) => {
|
const action = (key: string | number) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'save':
|
case 'save':
|
||||||
@@ -441,7 +493,24 @@ const Script = () => {
|
|||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper log-wrapper"
|
className="ql-container-wrapper log-wrapper"
|
||||||
title={select}
|
title={
|
||||||
|
<>
|
||||||
|
{select}
|
||||||
|
{currentNode?.type === 'file' && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: 6,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#999',
|
||||||
|
display: 'inline-block',
|
||||||
|
height: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{prettyBytes(currentNode.size)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
extra={
|
extra={
|
||||||
isPhone
|
isPhone
|
||||||
@@ -543,6 +612,7 @@ const Script = () => {
|
|||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
showLine={{ showLeafIcon: true }}
|
showLine={{ showLeafIcon: true }}
|
||||||
onSelect={onTreeSelect}
|
onSelect={onTreeSelect}
|
||||||
|
onDoubleClick={onDoubleClick}
|
||||||
></Tree>
|
></Tree>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -591,15 +661,17 @@ const Script = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLogModalVisible && <EditModal
|
{isLogModalVisible && (
|
||||||
visible={isLogModalVisible}
|
<EditModal
|
||||||
treeData={data}
|
visible={isLogModalVisible}
|
||||||
currentNode={currentNode}
|
treeData={data}
|
||||||
content={value}
|
currentNode={currentNode}
|
||||||
handleCancel={() => {
|
content={value}
|
||||||
setIsLogModalVisible(false);
|
handleCancel={() => {
|
||||||
}}
|
setIsLogModalVisible(false);
|
||||||
/>}
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<EditScriptNameModal
|
<EditScriptNameModal
|
||||||
visible={isAddFileModalVisible}
|
visible={isAddFileModalVisible}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
|
|||||||
@@ -62,7 +62,12 @@ const RenameModal = ({
|
|||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
confirmLoading={loading}
|
confirmLoading={loading}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" name="edit_name_modal">
|
<Form
|
||||||
|
form={form}
|
||||||
|
initialValues={{ name: currentNode?.title }}
|
||||||
|
layout="vertical"
|
||||||
|
name="edit_name_modal"
|
||||||
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
rules={[{ required: true, message: intl.get('请输入新名称') }]}
|
rules={[{ required: true, message: intl.get('请输入新名称') }]}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ const SaveModal = ({
|
|||||||
message.success(intl.get('保存文件成功'));
|
message.success(intl.get('保存文件成功'));
|
||||||
handleCancel(data);
|
handleCancel(data);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,7 +77,11 @@ const CheckUpdate = ({ systemInfo }: any) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
content: <pre><Ansi>{lastLog}</Ansi></pre>,
|
content: (
|
||||||
|
<pre>
|
||||||
|
<Ansi>{lastLog}</Ansi>
|
||||||
|
</pre>
|
||||||
|
),
|
||||||
okText: intl.get('下载更新'),
|
okText: intl.get('下载更新'),
|
||||||
cancelText: intl.get('以后再说'),
|
cancelText: intl.get('以后再说'),
|
||||||
onOk() {
|
onOk() {
|
||||||
@@ -102,10 +106,41 @@ const CheckUpdate = ({ systemInfo }: any) => {
|
|||||||
okButtonProps: { disabled: true },
|
okButtonProps: { disabled: true },
|
||||||
title: intl.get('下载更新中...'),
|
title: intl.get('下载更新中...'),
|
||||||
centered: true,
|
centered: true,
|
||||||
content: <pre><Ansi>{value}</Ansi></pre>,
|
content: (
|
||||||
|
<pre>
|
||||||
|
<Ansi>{value}</Ansi>
|
||||||
|
</pre>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reloadSystem = (type?: string) => {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}system/reload`, { type })
|
||||||
|
.then((_data: any) => {
|
||||||
|
message.success({
|
||||||
|
content: (
|
||||||
|
<span>
|
||||||
|
{intl.get('系统将在')}
|
||||||
|
<Countdown
|
||||||
|
className="inline-countdown"
|
||||||
|
format="ss"
|
||||||
|
value={Date.now() + 1000 * 30}
|
||||||
|
/>
|
||||||
|
{intl.get('秒后自动刷新')}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
duration: 30,
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 30000);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const showReloadModal = () => {
|
const showReloadModal = () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
width: 600,
|
width: 600,
|
||||||
@@ -115,30 +150,7 @@ const CheckUpdate = ({ systemInfo }: any) => {
|
|||||||
content: intl.get('系统安装包下载成功,确认重启'),
|
content: intl.get('系统安装包下载成功,确认重启'),
|
||||||
okText: intl.get('重启'),
|
okText: intl.get('重启'),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
reloadSystem('system');
|
||||||
.put(`${config.apiPrefix}system/reload`, { type: 'system' })
|
|
||||||
.then((_data: any) => {
|
|
||||||
message.success({
|
|
||||||
content: (
|
|
||||||
<span>
|
|
||||||
{intl.get('系统将在')}
|
|
||||||
<Countdown
|
|
||||||
className="inline-countdown"
|
|
||||||
format="ss"
|
|
||||||
value={Date.now() + 1000 * 30}
|
|
||||||
/>
|
|
||||||
{intl.get('秒后自动刷新')}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
duration: 30,
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 30000);
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onCancel() {
|
onCancel() {
|
||||||
modalRef.current.update({
|
modalRef.current.update({
|
||||||
@@ -206,6 +218,13 @@ const CheckUpdate = ({ systemInfo }: any) => {
|
|||||||
<Button type="primary" onClick={checkUpgrade}>
|
<Button type="primary" onClick={checkUpgrade}>
|
||||||
{intl.get('检查更新')}
|
{intl.get('检查更新')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => reloadSystem()}
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
>
|
||||||
|
{intl.get('重新启动')}
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Button, InputNumber, Form, message, Input, Alert } from 'antd';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import './index.less';
|
||||||
|
import Ansi from 'ansi-to-react';
|
||||||
|
import pick from 'lodash/pick';
|
||||||
|
import WebSocketManager from '@/utils/websocket';
|
||||||
|
|
||||||
|
const dataMap = {
|
||||||
|
'dependence-proxy': 'dependenceProxy',
|
||||||
|
'node-mirror': 'nodeMirror',
|
||||||
|
'python-mirror': 'pythonMirror',
|
||||||
|
'linux-mirror': 'linuxMirror',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Dependence = () => {
|
||||||
|
const [systemConfig, setSystemConfig] = useState<{
|
||||||
|
dependenceProxy?: string;
|
||||||
|
nodeMirror?: string;
|
||||||
|
pythonMirror?: string;
|
||||||
|
linuxMirror?: string;
|
||||||
|
}>();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [log, setLog] = useState<string>('');
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const getSystemConfig = () => {
|
||||||
|
request
|
||||||
|
.get(`${config.apiPrefix}system/config`)
|
||||||
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200 && data.info) {
|
||||||
|
setSystemConfig(data.info);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSystemConfigStream = (path: keyof typeof dataMap) => {
|
||||||
|
setLoading(true);
|
||||||
|
setLog('in progress...\n');
|
||||||
|
request
|
||||||
|
.put<string>(
|
||||||
|
`${config.apiPrefix}system/config/${path}`,
|
||||||
|
pick(systemConfig, dataMap[path]),
|
||||||
|
)
|
||||||
|
.then((res) => {})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSystemConfig = (path: keyof typeof dataMap) => {
|
||||||
|
setLoading(true);
|
||||||
|
setLog('');
|
||||||
|
request
|
||||||
|
.put(
|
||||||
|
`${config.apiPrefix}system/config/${path}`,
|
||||||
|
pick(systemConfig, dataMap[path]),
|
||||||
|
)
|
||||||
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(intl.get('更新成功'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMessage = (payload: any) => {
|
||||||
|
const { message } = payload;
|
||||||
|
setLog((p) => `${p}${message}`);
|
||||||
|
if (
|
||||||
|
message.includes('update node mirror end') ||
|
||||||
|
message.includes('update linux mirror end')
|
||||||
|
) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ws = WebSocketManager.getInstance();
|
||||||
|
ws.subscribe('updateNodeMirror', handleMessage);
|
||||||
|
ws.subscribe('updateLinuxMirror', handleMessage);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ws.subscribe('updateNodeMirror', handleMessage);
|
||||||
|
ws.unsubscribe('updateLinuxMirror', handleMessage);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getSystemConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dependence-config-wrapper">
|
||||||
|
<Form layout="vertical" form={form} style={{ flexShrink: 0 }}>
|
||||||
|
<Form.Item
|
||||||
|
label={intl.get('代理')}
|
||||||
|
name="proxy"
|
||||||
|
extra={intl.get('代理与镜像源二选一即可')}
|
||||||
|
tooltip={{
|
||||||
|
title: intl.get('代理地址, 支持HTTP(S)/SOCK5'),
|
||||||
|
placement: 'topLeft',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input.Group compact>
|
||||||
|
<Input
|
||||||
|
placeholder={'http://1.1.1.1:8080'}
|
||||||
|
style={{ width: 250 }}
|
||||||
|
value={systemConfig?.dependenceProxy}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSystemConfig({
|
||||||
|
...systemConfig,
|
||||||
|
dependenceProxy: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
loading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
updateSystemConfig('dependence-proxy');
|
||||||
|
}}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={intl.get('Node 软件包镜像源')}
|
||||||
|
name="node"
|
||||||
|
tooltip={intl.get('NPM 镜像源')}
|
||||||
|
>
|
||||||
|
<Input.Group compact>
|
||||||
|
<Input
|
||||||
|
style={{ width: 250 }}
|
||||||
|
placeholder={'https://registry.npmmirror.com'}
|
||||||
|
value={systemConfig?.nodeMirror}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSystemConfig({
|
||||||
|
...systemConfig,
|
||||||
|
nodeMirror: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
loading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
updateSystemConfigStream('node-mirror');
|
||||||
|
}}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={intl.get('Python 软件包镜像源')}
|
||||||
|
name="python"
|
||||||
|
tooltip={intl.get('PyPI 镜像源')}
|
||||||
|
>
|
||||||
|
<Input.Group compact>
|
||||||
|
<Input
|
||||||
|
style={{ width: 250 }}
|
||||||
|
placeholder={'https://pypi.doubanio.com/simple/'}
|
||||||
|
value={systemConfig?.pythonMirror}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSystemConfig({
|
||||||
|
...systemConfig,
|
||||||
|
pythonMirror: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
loading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
updateSystemConfig('python-mirror');
|
||||||
|
}}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={intl.get('Linux 软件包镜像源')}
|
||||||
|
name="linux"
|
||||||
|
tooltip={intl.get('alpine linux 镜像源')}
|
||||||
|
>
|
||||||
|
<Input.Group compact>
|
||||||
|
<Input
|
||||||
|
style={{ width: 250 }}
|
||||||
|
placeholder={'https://mirrors.aliyun.com'}
|
||||||
|
value={systemConfig?.linuxMirror}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSystemConfig({
|
||||||
|
...systemConfig,
|
||||||
|
linuxMirror: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
loading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
updateSystemConfigStream('linux-mirror');
|
||||||
|
}}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
<pre
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Source Code Pro',
|
||||||
|
zoom: 0.83,
|
||||||
|
maxHeight: '100%',
|
||||||
|
overflowY: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ansi>{log}</Ansi>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dependence;
|
||||||
@@ -27,3 +27,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dependence-config-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { SharedContext } from '@/layouts';
|
|||||||
import './index.less';
|
import './index.less';
|
||||||
import useResizeObserver from '@react-hook/resize-observer';
|
import useResizeObserver from '@react-hook/resize-observer';
|
||||||
import SystemLog from './systemLog';
|
import SystemLog from './systemLog';
|
||||||
|
import Dependence from './dependence';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const isDemoEnv = window.__ENV__DeployEnv === 'demo';
|
const isDemoEnv = window.__ENV__DeployEnv === 'demo';
|
||||||
@@ -74,11 +75,19 @@ const Setting = () => {
|
|||||||
title: intl.get('权限'),
|
title: intl.get('权限'),
|
||||||
dataIndex: 'scopes',
|
dataIndex: 'scopes',
|
||||||
key: 'scopes',
|
key: 'scopes',
|
||||||
width: '40%',
|
width: 500,
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
return record.scopes.map((scope: any) => {
|
return (
|
||||||
return <Tag key={scope}>{(config.scopesMap as any)[scope]}</Tag>;
|
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
|
||||||
});
|
{record.scopes.map((scope: any) => {
|
||||||
|
return (
|
||||||
|
<Tag style={{ marginRight: 0 }} key={scope}>
|
||||||
|
{(config.scopesMap as any)[scope]}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -347,21 +356,25 @@ const Setting = () => {
|
|||||||
{
|
{
|
||||||
key: 'syslog',
|
key: 'syslog',
|
||||||
label: intl.get('系统日志'),
|
label: intl.get('系统日志'),
|
||||||
children: <SystemLog data={systemLogData} height={height} theme={theme}/>,
|
children: (
|
||||||
|
<SystemLog data={systemLogData} height={height} theme={theme} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'login',
|
key: 'login',
|
||||||
label: intl.get('登录日志'),
|
label: intl.get('登录日志'),
|
||||||
children: <LoginLog data={loginLogData} />,
|
children: <LoginLog data={loginLogData} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'dependence',
|
||||||
|
label: intl.get('依赖设置'),
|
||||||
|
children: <Dependence />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'other',
|
key: 'other',
|
||||||
label: intl.get('其他设置'),
|
label: intl.get('其他设置'),
|
||||||
children: (
|
children: (
|
||||||
<Other
|
<Other reloadTheme={reloadTheme} systemInfo={systemInfo} />
|
||||||
reloadTheme={reloadTheme}
|
|
||||||
systemInfo={systemInfo}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ import './index.less';
|
|||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
import Countdown from 'antd/lib/statistic/Countdown';
|
import Countdown from 'antd/lib/statistic/Countdown';
|
||||||
import useProgress from './progress';
|
import useProgress from './progress';
|
||||||
|
import pick from 'lodash/pick';
|
||||||
|
|
||||||
|
const dataMap = {
|
||||||
|
'log-remove-frequency': 'logRemoveFrequency',
|
||||||
|
'cron-concurrency': 'cronConcurrency',
|
||||||
|
};
|
||||||
|
|
||||||
const Other = ({
|
const Other = ({
|
||||||
systemInfo,
|
systemInfo,
|
||||||
@@ -32,7 +38,6 @@ const Other = ({
|
|||||||
cronConcurrency?: number | null;
|
cronConcurrency?: number | null;
|
||||||
}>();
|
}>();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const modalRef = useRef<any>();
|
|
||||||
const [exportLoading, setExportLoading] = useState(false);
|
const [exportLoading, setExportLoading] = useState(false);
|
||||||
const showUploadProgress = useProgress(intl.get('上传'));
|
const showUploadProgress = useProgress(intl.get('上传'));
|
||||||
const showDownloadProgress = useProgress(intl.get('下载'));
|
const showDownloadProgress = useProgress(intl.get('下载'));
|
||||||
@@ -80,9 +85,12 @@ const Other = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSystemConfig = () => {
|
const updateSystemConfig = (path: keyof typeof dataMap) => {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}system/config`, systemConfig)
|
.put(
|
||||||
|
`${config.apiPrefix}system/config/${path}`,
|
||||||
|
pick(systemConfig, dataMap[path]),
|
||||||
|
)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(intl.get('更新成功'));
|
message.success(intl.get('更新成功'));
|
||||||
@@ -124,7 +132,12 @@ const Other = ({
|
|||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
title: intl.get('确认重启'),
|
title: intl.get('确认重启'),
|
||||||
centered: true,
|
centered: true,
|
||||||
content: intl.get('备份数据上传成功,确认覆盖数据'),
|
content: (
|
||||||
|
<>
|
||||||
|
<div>{intl.get('备份数据上传成功,确认覆盖数据')}</div>
|
||||||
|
<div>{intl.get('如果恢复失败,可进入容器执行')} ql reload data</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
okText: intl.get('重启'),
|
okText: intl.get('重启'),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
@@ -207,7 +220,9 @@ const Other = ({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={updateSystemConfig}
|
onClick={() => {
|
||||||
|
updateSystemConfig('log-remove-frequency');
|
||||||
|
}}
|
||||||
style={{ width: 84 }}
|
style={{ width: 84 }}
|
||||||
>
|
>
|
||||||
{intl.get('确认')}
|
{intl.get('确认')}
|
||||||
@@ -226,7 +241,9 @@ const Other = ({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={updateSystemConfig}
|
onClick={() => {
|
||||||
|
updateSystemConfig('cron-concurrency');
|
||||||
|
}}
|
||||||
style={{ width: 84 }}
|
style={{ width: 84 }}
|
||||||
>
|
>
|
||||||
{intl.get('确认')}
|
{intl.get('确认')}
|
||||||
|
|||||||
+4
-2
@@ -3,6 +3,7 @@ const baseUrl = window.__ENV__QlBaseUrl || '/';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
siteName: intl.get('青龙'),
|
siteName: intl.get('青龙'),
|
||||||
|
baseUrl,
|
||||||
apiPrefix: `${baseUrl}api/`,
|
apiPrefix: `${baseUrl}api/`,
|
||||||
authKey: 'token',
|
authKey: 'token',
|
||||||
|
|
||||||
@@ -320,12 +321,12 @@ export default {
|
|||||||
{
|
{
|
||||||
label: 'emailService',
|
label: 'emailService',
|
||||||
tip: intl.get(
|
tip: intl.get(
|
||||||
'邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://nodemailer.com/smtp/well-known/',
|
'邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json',
|
||||||
),
|
),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{ label: 'emailUser', tip: intl.get('邮箱地址'), required: true },
|
{ label: 'emailUser', tip: intl.get('邮箱地址'), required: true },
|
||||||
{ label: 'emailPass', tip: intl.get('邮箱SMTP授权码'), required: true },
|
{ label: 'emailPass', tip: intl.get('SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定'), required: true },
|
||||||
],
|
],
|
||||||
pushMe: [
|
pushMe: [
|
||||||
{
|
{
|
||||||
@@ -369,6 +370,7 @@ export default {
|
|||||||
tip: intl.get('请求头Content-Type'),
|
tip: intl.get('请求头Content-Type'),
|
||||||
required: true,
|
required: true,
|
||||||
items: [
|
items: [
|
||||||
|
{ value: 'text/plain' },
|
||||||
{ value: 'application/json' },
|
{ value: 'application/json' },
|
||||||
{ value: 'multipart/form-data' },
|
{ value: 'multipart/form-data' },
|
||||||
{ value: 'application/x-www-form-urlencoded' },
|
{ value: 'application/x-www-form-urlencoded' },
|
||||||
|
|||||||
+4
-4
@@ -1,13 +1,13 @@
|
|||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Integrations } from '@sentry/tracing';
|
|
||||||
import { loader } from '@monaco-editor/react';
|
import { loader } from '@monaco-editor/react';
|
||||||
|
import config from './config';
|
||||||
|
|
||||||
export function init(version: string) {
|
export function init(version: string) {
|
||||||
// sentry监控 init
|
// sentry监控 init
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
dsn: 'https://49b9ad1a6201bfe027db296ab7c6d672@o1098464.ingest.sentry.io/6122818',
|
||||||
integrations: [
|
integrations: [
|
||||||
new Integrations.BrowserTracing({
|
new Sentry.BrowserTracing({
|
||||||
shouldCreateSpanForRequest(url) {
|
shouldCreateSpanForRequest(url) {
|
||||||
return !url.includes('/api/ws') && !url.includes('/api/static');
|
return !url.includes('/api/ws') && !url.includes('/api/static');
|
||||||
},
|
},
|
||||||
@@ -27,7 +27,7 @@ export function init(version: string) {
|
|||||||
// monaco 编辑器配置cdn和locale
|
// monaco 编辑器配置cdn和locale
|
||||||
loader.config({
|
loader.config({
|
||||||
paths: {
|
paths: {
|
||||||
vs: 'https://cdn.staticfile.org/monaco-editor/0.33.0/min/vs',
|
vs: `${config.baseUrl}monaco-editor/min/vs`,
|
||||||
},
|
},
|
||||||
'vs/nls': {
|
'vs/nls': {
|
||||||
availableLanguages: {
|
availableLanguages: {
|
||||||
|
|||||||
+3
-1
@@ -5,4 +5,6 @@ export type SockMessageType =
|
|||||||
| 'updateSystemVersion'
|
| 'updateSystemVersion'
|
||||||
| 'manuallyRunScript'
|
| 'manuallyRunScript'
|
||||||
| 'runSubscriptionEnd'
|
| 'runSubscriptionEnd'
|
||||||
| 'reloadSystem';
|
| 'reloadSystem'
|
||||||
|
| 'updateNodeMirror'
|
||||||
|
| 'updateLinuxMirror';
|
||||||
|
|||||||
+6
-17
@@ -1,18 +1,7 @@
|
|||||||
version: 2.16.4
|
version: 2.17.2
|
||||||
changeLogLink: https://t.me/jiao_long/395
|
changeLogLink: https://t.me/jiao_long/403
|
||||||
publishTime: 2023-10-18 23:00
|
publishTime: 2024-03-02 17:00
|
||||||
changeLog: |
|
changeLog: |
|
||||||
1. 脚本推送增加自定义 webhook 方式
|
1. 依赖管理支持队列中依赖取消安装,支持状态筛选
|
||||||
3. 增加 chronocat 无头模式的QQNT推送
|
2. 修复 webhook 通知 body 拆分逻辑
|
||||||
2. task 命令支持给脚本传参,使用 -- 分割,后面的参数都会传给脚本
|
3. 企业微信有长度限制,超长的进行分段提交 https://github.com/pharaoh2012
|
||||||
4. 系统日志增加置顶置底按钮
|
|
||||||
5. 定时任务详情增加额外定时展示
|
|
||||||
6. 定时任务增加前后规则校验,不能包含 task 命令
|
|
||||||
7. 修复定时任务间隔较小,任务状态不准确
|
|
||||||
8. 修复定时任务日志滚动
|
|
||||||
9. 修复 QlBaseUrl 末尾不加斜杠无法访问
|
|
||||||
10. 修复定时任务详情页查看历史日志异常
|
|
||||||
11. 修复定时任务添加 task_after 命令无法停止
|
|
||||||
12. 修复清空文件夹报错
|
|
||||||
13. 修复设置代理时, schedule 服务通信异常
|
|
||||||
14. 其他优化
|
|
||||||
|
|||||||
Reference in New Issue
Block a user