mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 03:10:48 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a2caeb66b | ||
|
|
ee6e5bd8b4 | ||
|
|
f970322f0a | ||
|
|
1718120623 | ||
|
|
702c3160ec | ||
|
|
db227e56bf | ||
|
|
84828865f7 | ||
|
|
87455cebc0 | ||
|
|
9f4435b237 | ||
|
|
e5bc4b3b04 | ||
|
|
11e1b50000 | ||
|
|
bec226ae13 | ||
|
|
c9ecdc6c97 | ||
|
|
4cf64e7af0 | ||
|
|
4ce01c5f0e | ||
|
|
2eff99abe1 | ||
|
|
5bf57185d9 | ||
|
|
c59dac19ae | ||
|
|
f1ef8e72ae | ||
|
|
5b1accffb7 | ||
|
|
f64ff83414 | ||
|
|
26c914d1db | ||
|
|
8f75a54110 | ||
|
|
874b6e3a56 | ||
|
|
f305d45225 | ||
|
|
a7f97bd729 | ||
|
|
0bbb7c7b89 | ||
|
|
f7465759c7 | ||
|
|
998e82d323 | ||
|
|
f8dfee8945 | ||
|
|
86e3d8736b | ||
|
|
85d32d00e1 | ||
|
|
6a52e2f804 | ||
|
|
b4cb72c5d0 | ||
|
|
79fd4ec58f | ||
|
|
49ed5c6632 | ||
|
|
ffba7ee4a1 | ||
|
|
91d080472f | ||
|
|
826f214f0f | ||
|
|
ca150dc6a6 | ||
|
|
ae5db60211 | ||
|
|
490bdc15f6 | ||
|
|
1377fe31aa | ||
|
|
3d1cf6bf2e | ||
|
|
6ac755b478 | ||
|
|
e70fa83074 | ||
|
|
41521f1d08 | ||
|
|
8b7bef097c | ||
|
|
618756d4a9 | ||
|
|
70d0c71414 | ||
|
|
65eb9c806d | ||
|
|
50718ba714 | ||
|
|
7c3bb90d12 | ||
|
|
f66390eabd | ||
|
|
481646501c | ||
|
|
5baa8e4391 | ||
|
|
3d11210aeb | ||
|
|
e1863407f2 | ||
|
|
0c28b63ab7 | ||
|
|
8b8bd279c6 | ||
|
|
01b404765e | ||
|
|
c231a871d7 | ||
|
|
598c6b4e57 | ||
|
|
2295fabcc5 | ||
|
|
be94d796ac | ||
|
|
c84908d7fa | ||
|
|
d8ae039b92 | ||
|
|
bce1431d03 | ||
|
|
9db0095e29 | ||
|
|
77dc7817fb | ||
|
|
d0dd97631e | ||
|
|
d11d6d0c18 | ||
|
|
0af687f781 | ||
|
|
8db997abe8 | ||
|
|
aab6bbeb15 | ||
|
|
df1addc1ff | ||
|
|
1f8f35476a | ||
|
|
5354fc76db | ||
|
|
b29a9e012c | ||
|
|
6e5d89c197 | ||
|
|
d6cfb18f06 | ||
|
|
9a3e38051d | ||
|
|
b705ad6ee8 |
@@ -45,7 +45,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: '8.3.1'
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
@@ -85,8 +85,6 @@ jobs:
|
|||||||
git push --force --quiet gitlab ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
git push --force --quiet gitlab ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
if: github.ref != 'refs/heads/master'
|
|
||||||
|
|
||||||
needs: build-static
|
needs: build-static
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -99,7 +97,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: '8.3.1'
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -131,11 +129,14 @@ jobs:
|
|||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
# generate Docker tags based on the following events/attributes
|
# generate Docker tags based on the following events/attributes
|
||||||
# nightly, master, pr-2, 1.2.3, 1.2, 1
|
# nightly, master, pr-2, 1.2.3, 1.2, 1
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
tags: |
|
tags: |
|
||||||
type=schedule,pattern=nightly
|
type=schedule,pattern=nightly
|
||||||
type=edge
|
type=edge
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
|
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', 'master') }}
|
||||||
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
import { defineConfig } from '@umijs/max';
|
import { defineConfig } from '@umijs/max';
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
|
|
||||||
|
const baseUrl = process.env.QlBaseUrl || '/';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
hash: true,
|
hash: true,
|
||||||
antd: {},
|
antd: {},
|
||||||
outputPath: 'static/dist',
|
outputPath: 'static/dist',
|
||||||
fastRefresh: true,
|
fastRefresh: true,
|
||||||
favicons: ['./images/favicon.svg'],
|
favicons: [`https://qn.whyour.cn/favicon.svg`],
|
||||||
mfsu: {
|
mfsu: {
|
||||||
strategy: 'eager',
|
strategy: 'eager',
|
||||||
},
|
},
|
||||||
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/public': {
|
[`${baseUrl}api/public`]: {
|
||||||
target: 'http://127.0.0.1:5400/',
|
target: 'http://127.0.0.1:5400/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
pathRewrite: { [`^${baseUrl}api/public`]: '/api' },
|
||||||
},
|
},
|
||||||
'/api': {
|
[`${baseUrl}api`]: {
|
||||||
target: 'http://127.0.0.1:5600/',
|
target: 'http://127.0.0.1:5600/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
|
pathRewrite: { [`^${baseUrl}api`]: '/api' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chainWebpack: ((config: any) => {
|
chainWebpack: ((config: any) => {
|
||||||
@@ -37,6 +40,7 @@ export default defineConfig({
|
|||||||
'react-dom': 'window.ReactDOM',
|
'react-dom': 'window.ReactDOM',
|
||||||
},
|
},
|
||||||
headScripts: [
|
headScripts: [
|
||||||
|
`./api/env.js`,
|
||||||
'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',
|
||||||
],
|
],
|
||||||
|
|||||||
+56
-97
@@ -1,30 +1,29 @@
|
|||||||
<p align="center">
|
|
||||||
<a href="https://github.com/whyour/qinglong">
|
|
||||||
<img width="150" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h1 align="center">Green Dragon</h1>
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
<img width="100" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
||||||
|
|
||||||
|
<h1 align="center">Qinglong</h1>
|
||||||
|
|
||||||
|
[简体中文](./README.md) | English
|
||||||
|
|
||||||
Timed task management platform supporting Python3, JavaScript, Shell, Typescript
|
Timed task management platform supporting Python3, JavaScript, Shell, Typescript
|
||||||
|
|
||||||
[![docker version][docker-version-image]][docker-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
|
[![npm version][npm-version-image]][npm-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
|
||||||
|
|
||||||
|
[npm-version-image]: https://img.shields.io/npm/v/@whyour/qinglong?style=flat
|
||||||
|
[npm-version-url]: https://www.npmjs.com/package/@whyour/qinglong?activeTab=readme
|
||||||
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
|
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
|
||||||
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
[docker-version-image]: https://img.shields.io/docker/v/whyour/qinglong?style=flat
|
|
||||||
[docker-version-url]: https://hub.docker.com/r/whyour/qinglong/tags?page=1&ordering=last_updated
|
|
||||||
[docker-stars-image]: https://img.shields.io/docker/stars/whyour/qinglong?style=flat
|
[docker-stars-image]: https://img.shields.io/docker/stars/whyour/qinglong?style=flat
|
||||||
[docker-stars-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-stars-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
||||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
|
|
||||||
|
[Demo](http://demo.dlww.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
|
||||||
|
|
||||||
|
[演示](http://demo.dlww.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[](https://whyour.cn)
|

|
||||||
|
|
||||||
[简体中文](./README.md) | English
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -38,62 +37,10 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript
|
|||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Local Deployment
|
### Docker (Recommended)
|
||||||
|
|
||||||
```bash
|
|
||||||
# To be refined, see the development steps first (not supported on windows yet)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Podman Deployment
|
|
||||||
|
|
||||||
1. podman installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
https://podman.io/getting-started/installation
|
|
||||||
```
|
|
||||||
|
|
||||||
2. start the container
|
|
||||||
|
|
||||||
```bash
|
|
||||||
podman run -dit \
|
|
||||||
--network bridge \
|
|
||||||
-v $PWD/ql/data:/ql/data \
|
|
||||||
-p 5700:5700 \
|
|
||||||
--name qinglong \
|
|
||||||
--hostname qinglong \
|
|
||||||
docker.io/whyour/qinglong:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker Deployment
|
|
||||||
|
|
||||||
1. docker installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo curl -sSL get.docker.com | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. configure domestic mirror sources
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p /etc/docker
|
|
||||||
tee /etc/docker/daemon.json <<-'EOF'
|
|
||||||
{
|
|
||||||
"registry-mirrors": [
|
|
||||||
"https://0b27f0a81a00f3560fbdc00ddd2f99e0.mirror.swr.myhuaweicloud.com",
|
|
||||||
"https://ypzju6vq.mirror.aliyuncs.com",
|
|
||||||
"https://registry.docker-cn.com",
|
|
||||||
"http://hub-mirror.c.163.com",
|
|
||||||
"https://docker.mirrors.ustc.edu.cn"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl restart docker
|
|
||||||
```
|
|
||||||
|
|
||||||
3. start the container
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# curl -sSL get.docker.com | sh
|
||||||
docker run -dit \
|
docker run -dit \
|
||||||
-v $PWD/ql/data:/ql/data \
|
-v $PWD/ql/data:/ql/data \
|
||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
@@ -103,17 +50,10 @@ docker run -dit \
|
|||||||
whyour/qinglong:latest
|
whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker-compose Deployment
|
### Docker-compose (Recommended)
|
||||||
|
|
||||||
1. docker-compose installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
|
||||||
```
|
|
||||||
|
|
||||||
2. start the container
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
||||||
mkdir qinglong
|
mkdir qinglong
|
||||||
wget https://raw.githubusercontent.com/whyour/qinglong/master/docker/docker-compose.yml
|
wget https://raw.githubusercontent.com/whyour/qinglong/master/docker/docker-compose.yml
|
||||||
|
|
||||||
@@ -123,9 +63,32 @@ docker-compose up -d
|
|||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
3. access
|
### Podman (Recommended)
|
||||||
|
|
||||||
Open your browser and visit http://{ip}:5700
|
```bash
|
||||||
|
# https://podman.io/getting-started/installation
|
||||||
|
podman run -dit \
|
||||||
|
--network bridge \
|
||||||
|
-v $PWD/ql/data:/ql/data \
|
||||||
|
-p 5700:5700 \
|
||||||
|
--name qinglong \
|
||||||
|
--hostname qinglong \
|
||||||
|
docker.io/whyour/qinglong:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local
|
||||||
|
|
||||||
|
It is recommended to use a pure system installation to avoid losing the original system data, you need to install node/npm/python3/pip3 yourself
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @whyour/qinglong
|
||||||
|
qinglong
|
||||||
|
# Add the environment variables QL_DIR and QL_DATA_DIR when prompted
|
||||||
|
export QL_DIR=""
|
||||||
|
export QL_DATA_DIR=""
|
||||||
|
# Run again
|
||||||
|
qinglong
|
||||||
|
```
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
@@ -181,6 +144,20 @@ task -l <file_path>
|
|||||||
* account_number: Specify the account number of an environment variable to be executed when the task is executed
|
* 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
|
* max_time: Timeout, suffix "s" for seconds (default), "m" for minutes, "h" for hours, "d" for days
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone git@github.com:whyour/qinglong.git
|
||||||
|
$ cd qinglong
|
||||||
|
$ cp .env.example .env
|
||||||
|
# Recommended use pnpm https://pnpm.io/zh/installation
|
||||||
|
$ npm install -g pnpm@8.3.1
|
||||||
|
$ pnpm install
|
||||||
|
$ pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Open your browser and visit http://127.0.0.1:5700
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [nevinee](https://gitee.com/evine)
|
- [nevinee](https://gitee.com/evine)
|
||||||
@@ -191,24 +168,6 @@ task -l <file_path>
|
|||||||
- [darkreader](https://github.com/darkreader/darkreader)
|
- [darkreader](https://github.com/darkreader/darkreader)
|
||||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git clone git@github.com:whyour/qinglong.git
|
|
||||||
$ cd qinglong
|
|
||||||
$ cp .env.example .env
|
|
||||||
# Recommended use pnpm https://pnpm.io/zh/installation
|
|
||||||
$ npm install -g pnpm
|
|
||||||
$ pnpm install
|
|
||||||
$ pnpm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Open your browser and visit http://127.0.0.1:5700
|
|
||||||
|
|
||||||
## Communication
|
|
||||||
|
|
||||||
[telegram channel](https://t.me/jiao_long)
|
|
||||||
|
|
||||||
## Name Origin
|
## Name Origin
|
||||||
|
|
||||||
The Green Dragon, also known as the Canglong, is one of the four elephants and one of the [four spirits of the heavens](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5) in traditional Chinese culture. According to the Five Elements, it is a spirit animal representing the East as a green dragon, the five elements are wood, and the season represented is spring, with the eight trigrams dominating vibration. Like the Ying Long, the Cang Long has feathered wings. According to the Zhang Guo Xing Zong (Zhang Guo Xing Zong), "a true dragon is one that has complementary wings".
|
The Green Dragon, also known as the Canglong, is one of the four elephants and one of the [four spirits of the heavens](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5) in traditional Chinese culture. According to the Five Elements, it is a spirit animal representing the East as a green dragon, the five elements are wood, and the season represented is spring, with the eight trigrams dominating vibration. Like the Ying Long, the Cang Long has feathered wings. According to the Zhang Guo Xing Zong (Zhang Guo Xing Zong), "a true dragon is one that has complementary wings".
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
<p align="center">
|
<div align="center">
|
||||||
<a href="https://github.com/whyour/qinglong">
|
<img width="100" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
||||||
<img width="150" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h1 align="center">青龙</h1>
|
<h1 align="center">青龙</h1>
|
||||||
|
|
||||||
<div align="center">
|
简体中文 | [English](./README-en.md)
|
||||||
|
|
||||||
支持 Python3、JavaScript、Shell、Typescript 的定时任务管理平台
|
支持 Python3、JavaScript、Shell、Typescript 的定时任务管理平台
|
||||||
|
|
||||||
[![docker version][docker-version-image]][docker-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
|
Timed task management platform supporting Python3, JavaScript, Shell, Typescript
|
||||||
|
|
||||||
|
[![npm version][npm-version-image]][npm-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
|
||||||
|
|
||||||
|
[npm-version-image]: https://img.shields.io/npm/v/@whyour/qinglong?style=flat
|
||||||
|
[npm-version-url]: https://www.npmjs.com/package/@whyour/qinglong?activeTab=readme
|
||||||
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
|
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=flat
|
||||||
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
[docker-version-image]: https://img.shields.io/docker/v/whyour/qinglong?style=flat
|
|
||||||
[docker-version-url]: https://hub.docker.com/r/whyour/qinglong/tags?page=1&ordering=last_updated
|
|
||||||
[docker-stars-image]: https://img.shields.io/docker/stars/whyour/qinglong?style=flat
|
[docker-stars-image]: https://img.shields.io/docker/stars/whyour/qinglong?style=flat
|
||||||
[docker-stars-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-stars-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
|
||||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
|
|
||||||
|
[Demo](http://demo.dlww.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
|
||||||
|
|
||||||
|
[演示](http://demo.dlww.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[](https://whyour.cn)
|

|
||||||
|
|
||||||
简体中文 | [English](./README-en.md)
|
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
@@ -38,82 +39,25 @@
|
|||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
### 本机部署
|
### docker (推荐)
|
||||||
|
|
||||||
```bash
|
|
||||||
# 待完善,可先参考开发步骤 (windows暂时不支持)
|
|
||||||
```
|
|
||||||
|
|
||||||
### podman 部署
|
|
||||||
|
|
||||||
1. podman 安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
https://podman.io/getting-started/installation
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 启动容器
|
|
||||||
|
|
||||||
```bash
|
|
||||||
podman run -dit \
|
|
||||||
--network bridge \
|
|
||||||
-v $PWD/ql/data:/ql/data \
|
|
||||||
-p 5700:5700 \
|
|
||||||
--name qinglong \
|
|
||||||
--hostname qinglong \
|
|
||||||
docker.io/whyour/qinglong:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### docker 部署
|
|
||||||
|
|
||||||
1. docker 安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo curl -sSL get.docker.com | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 配置国内镜像源
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p /etc/docker
|
|
||||||
tee /etc/docker/daemon.json <<-'EOF'
|
|
||||||
{
|
|
||||||
"registry-mirrors": [
|
|
||||||
"https://0b27f0a81a00f3560fbdc00ddd2f99e0.mirror.swr.myhuaweicloud.com",
|
|
||||||
"https://ypzju6vq.mirror.aliyuncs.com",
|
|
||||||
"https://registry.docker-cn.com",
|
|
||||||
"http://hub-mirror.c.163.com",
|
|
||||||
"https://docker.mirrors.ustc.edu.cn"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl restart docker
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 启动容器
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# curl -sSL get.docker.com | sh
|
||||||
docker run -dit \
|
docker run -dit \
|
||||||
-v $PWD/ql/data:/ql/data \
|
-v $PWD/ql/data:/ql/data \
|
||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
|
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
|
||||||
|
-e QlBaseUrl="/" \
|
||||||
--name qinglong \
|
--name qinglong \
|
||||||
--hostname qinglong \
|
--hostname qinglong \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
whyour/qinglong:latest
|
whyour/qinglong:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### docker-compose 部署
|
### docker-compose (推荐)
|
||||||
|
|
||||||
1. docker-compose 安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 启动容器
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
||||||
mkdir qinglong
|
mkdir qinglong
|
||||||
wget https://raw.githubusercontent.com/whyour/qinglong/master/docker/docker-compose.yml
|
wget https://raw.githubusercontent.com/whyour/qinglong/master/docker/docker-compose.yml
|
||||||
|
|
||||||
@@ -123,9 +67,34 @@ docker-compose up -d
|
|||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 访问
|
### podman (推荐)
|
||||||
|
|
||||||
打开你的浏览器,访问 http://{ip}:5700
|
```bash
|
||||||
|
# https://podman.io/getting-started/installation
|
||||||
|
podman run -dit \
|
||||||
|
--network bridge \
|
||||||
|
-v $PWD/ql/data:/ql/data \
|
||||||
|
-p 5700:5700 \
|
||||||
|
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
|
||||||
|
-e QlBaseUrl="/" \
|
||||||
|
--name qinglong \
|
||||||
|
--hostname qinglong \
|
||||||
|
docker.io/whyour/qinglong:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 本机
|
||||||
|
|
||||||
|
建议使用纯净系统安装,避免系统原有数据丢失,需要自己安装 node/npm/python3/pip3
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @whyour/qinglong
|
||||||
|
qinglong
|
||||||
|
# 根据提示增加环境变量 QL_DIR 和 QL_DATA_DIR
|
||||||
|
export QL_DIR=""
|
||||||
|
export QL_DATA_DIR=""
|
||||||
|
# 再次执行
|
||||||
|
qinglong
|
||||||
|
```
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
@@ -180,6 +149,20 @@ task -l <file_path>
|
|||||||
* account_number: 任务执行时指定某个环境变量需要执行的账号序号
|
* account_number: 任务执行时指定某个环境变量需要执行的账号序号
|
||||||
* max_time: 超时时间,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
* max_time: 超时时间,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone git@github.com:whyour/qinglong.git
|
||||||
|
$ cd qinglong
|
||||||
|
$ cp .env.example .env
|
||||||
|
# 推荐使用 pnpm https://pnpm.io/zh/installation
|
||||||
|
$ npm install -g pnpm@8.3.1
|
||||||
|
$ pnpm install
|
||||||
|
$ pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
打开你的浏览器,访问 http://127.0.0.1:5700
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [nevinee](https://gitee.com/evine)
|
- [nevinee](https://gitee.com/evine)
|
||||||
@@ -190,24 +173,6 @@ task -l <file_path>
|
|||||||
- [darkreader](https://github.com/darkreader/darkreader)
|
- [darkreader](https://github.com/darkreader/darkreader)
|
||||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||||
|
|
||||||
## 开发
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git clone git@github.com:whyour/qinglong.git
|
|
||||||
$ cd qinglong
|
|
||||||
$ cp .env.example .env
|
|
||||||
# 推荐使用 pnpm https://pnpm.io/zh/installation
|
|
||||||
$ npm install -g pnpm
|
|
||||||
$ pnpm install
|
|
||||||
$ pnpm start
|
|
||||||
```
|
|
||||||
|
|
||||||
打开你的浏览器,访问 http://127.0.0.1:5700
|
|
||||||
|
|
||||||
## 交流
|
|
||||||
|
|
||||||
[telegram频道](https://t.me/jiao_long)
|
|
||||||
|
|
||||||
## 名称来源
|
## 名称来源
|
||||||
|
|
||||||
青龙,又名苍龙,在中国传统文化中是四象之一、[天之四灵](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5)之一,根据五行学说,它是代表东方的灵兽,为青色的龙,五行属木,代表的季节是春季,八卦主震。苍龙与应龙一样,都是身具羽翼。《张果星宗》称“又有辅翼,方为真龙”。
|
青龙,又名苍龙,在中国传统文化中是四象之一、[天之四灵](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5)之一,根据五行学说,它是代表东方的灵兽,为青色的龙,五行属木,代表的季节是春季,八卦主震。苍龙与应龙一样,都是身具羽翼。《张果星宗》称“又有辅翼,方为真龙”。
|
||||||
|
|||||||
+4
-3
@@ -5,6 +5,7 @@ import { Logger } from 'winston';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
|
import { join } from 'path';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
@@ -41,11 +42,11 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
if (req.params.file.includes('sample')) {
|
if (req.params.file.includes('sample')) {
|
||||||
content = getFileContentByName(
|
content = getFileContentByName(
|
||||||
`${config.samplePath}${req.params.file}`,
|
join(config.samplePath, req.params.file),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = getFileContentByName(
|
content = getFileContentByName(
|
||||||
`${config.configPath}${req.params.file}`,
|
join(config.configPath, req.params.file),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
@@ -70,7 +71,7 @@ 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 = `${config.configPath}${name}`;
|
const path = join(config.configPath, name);
|
||||||
fs.writeFileSync(path, content);
|
fs.writeFileSync(path, content);
|
||||||
res.send({ code: 200, message: '保存成功' });
|
res.send({ code: 200, message: '保存成功' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
+3
-3
@@ -94,7 +94,7 @@ export default (app: Router) => {
|
|||||||
path += '/';
|
path += '/';
|
||||||
}
|
}
|
||||||
if (!path.startsWith('/')) {
|
if (!path.startsWith('/')) {
|
||||||
path = `${config.scriptPath}${path}`;
|
path = join(config.scriptPath, path);
|
||||||
}
|
}
|
||||||
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
||||||
return res.send({
|
return res.send({
|
||||||
@@ -124,7 +124,7 @@ export default (app: Router) => {
|
|||||||
if (fs.existsSync(originFilePath)) {
|
if (fs.existsSync(originFilePath)) {
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
originFilePath,
|
originFilePath,
|
||||||
`${config.bakPath}${originFilename.replace(/\//g, '')}`,
|
join(config.bakPath, originFilename.replace(/\//g, '')),
|
||||||
);
|
);
|
||||||
if (filename !== originFilename) {
|
if (filename !== originFilename) {
|
||||||
fs.unlinkSync(originFilePath);
|
fs.unlinkSync(originFilePath);
|
||||||
@@ -207,7 +207,7 @@ export default (app: Router) => {
|
|||||||
let { filename } = req.body as {
|
let { filename } = req.body as {
|
||||||
filename: string;
|
filename: string;
|
||||||
};
|
};
|
||||||
const filePath = `${config.scriptPath}${filename}`;
|
const filePath = join(config.scriptPath, filename);
|
||||||
// const stats = fs.statSync(filePath);
|
// const stats = fs.statSync(filePath);
|
||||||
// res.set({
|
// res.set({
|
||||||
// 'Content-Type': 'application/octet-stream', //告诉浏览器这是一个二进制文件
|
// 'Content-Type': 'application/octet-stream', //告诉浏览器这是一个二进制文件
|
||||||
|
|||||||
+74
-9
@@ -7,7 +7,12 @@ 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 { EnvModel } from '../data/env';
|
||||||
import { parseVersion, promiseExec } from '../config/util';
|
import {
|
||||||
|
getUniqPath,
|
||||||
|
handleLogPath,
|
||||||
|
parseVersion,
|
||||||
|
promiseExec,
|
||||||
|
} from '../config/util';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const route = Router();
|
const route = Router();
|
||||||
@@ -42,8 +47,7 @@ export default (app: Router) => {
|
|||||||
if (
|
if (
|
||||||
Object.keys(authInfo).length === 2 &&
|
Object.keys(authInfo).length === 2 &&
|
||||||
authInfo.username === 'admin' &&
|
authInfo.username === 'admin' &&
|
||||||
authInfo.password === 'admin' &&
|
authInfo.password === 'admin'
|
||||||
envCount === 0
|
|
||||||
) {
|
) {
|
||||||
isInitialized = false;
|
isInitialized = false;
|
||||||
}
|
}
|
||||||
@@ -66,12 +70,12 @@ export default (app: Router) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
'/log/remove',
|
'/config',
|
||||||
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 systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const data = await systemService.getLogRemoveFrequency();
|
const data = await systemService.getSystemConfig();
|
||||||
res.send({ code: 200, data });
|
res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
@@ -80,18 +84,19 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/log/remove',
|
'/config',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
frequency: Joi.number().required(),
|
logRemoveFrequency: Joi.number().optional().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');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const result = await systemService.updateLogRemoveFrequency(
|
const result = await systemService.updateSystemConfig(
|
||||||
req.body.frequency,
|
req.body,
|
||||||
);
|
);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -147,4 +152,64 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/command-run',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
command: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const uniqPath = await getUniqPath(req.body.command);
|
||||||
|
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
|
||||||
|
const logPath = `${uniqPath}/${logTime}.log`;
|
||||||
|
res.setHeader('Content-type', 'application/octet-stream');
|
||||||
|
await systemService.run(
|
||||||
|
{ ...req.body, logPath },
|
||||||
|
{
|
||||||
|
onStart: async (cp, startTime) => {
|
||||||
|
res.setHeader('QL-Task-Pid', `${cp.pid}`);
|
||||||
|
},
|
||||||
|
onEnd: async (cp, endTime, diff) => {
|
||||||
|
res.end();
|
||||||
|
},
|
||||||
|
onError: async (message: string) => {
|
||||||
|
res.write(`\n${message}`);
|
||||||
|
const absolutePath = await handleLogPath(logPath);
|
||||||
|
fs.appendFileSync(absolutePath, `\n${message}`);
|
||||||
|
},
|
||||||
|
onLog: async (message: string) => {
|
||||||
|
res.write(`\n${message}`);
|
||||||
|
const absolutePath = await handleLogPath(logPath);
|
||||||
|
fs.appendFileSync(absolutePath, `\n${message}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/command-stop',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
command: Joi.string().optional(),
|
||||||
|
pid: Joi.number().optional(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.stop(req.body);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { celebrate, Joi } from 'celebrate';
|
|||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
import rateLimit from 'express-rate-limit';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
@@ -26,6 +27,10 @@ export default (app: Router) => {
|
|||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/login',
|
'/login',
|
||||||
|
rateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000,
|
||||||
|
max: 100,
|
||||||
|
}),
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
username: Joi.string().required(),
|
username: Joi.string().required(),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ async function startServer() {
|
|||||||
const server = app
|
const server = app
|
||||||
.listen(config.port, () => {
|
.listen(config.port, () => {
|
||||||
Logger.debug(`✌️ 后端服务启动成功!`);
|
Logger.debug(`✌️ 后端服务启动成功!`);
|
||||||
|
process.send?.('ready');
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
Logger.error(err);
|
Logger.error(err);
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ export default {
|
|||||||
'/api/system',
|
'/api/system',
|
||||||
'/api/user/init',
|
'/api/user/init',
|
||||||
'/api/user/notification/init',
|
'/api/user/notification/init',
|
||||||
|
'/open/user/login',
|
||||||
|
'/open/user/two-factor/login',
|
||||||
|
'/open/system',
|
||||||
|
'/open/user/init',
|
||||||
|
'/open/user/notification/init',
|
||||||
],
|
],
|
||||||
versionFile,
|
versionFile,
|
||||||
lastVersionFile,
|
lastVersionFile,
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
|
let pickedEnv: Record<string, string>;
|
||||||
|
|
||||||
|
function getPickedEnv() {
|
||||||
|
if (pickedEnv) return pickedEnv;
|
||||||
|
const picked = pick(process.env, ['QlBaseUrl', 'DeployEnv']);
|
||||||
|
pickedEnv = picked as Record<string, string>;
|
||||||
|
return picked;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serveEnv(_req: Request, res: Response) {
|
||||||
|
res.type('.js');
|
||||||
|
res.send(
|
||||||
|
Object.entries(getPickedEnv())
|
||||||
|
.map(([k, v]) => `window.__ENV__${k}=${JSON.stringify(v)};`)
|
||||||
|
.join('\n'),
|
||||||
|
);
|
||||||
|
}
|
||||||
+61
-7
@@ -7,6 +7,8 @@ import FormData from 'form-data';
|
|||||||
import psTreeFun from 'pstree.remy';
|
import psTreeFun from 'pstree.remy';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { load } from 'js-yaml';
|
import { load } from 'js-yaml';
|
||||||
|
import config from './index';
|
||||||
|
import { TASK_COMMAND } from './const';
|
||||||
|
|
||||||
export function getFileContentByName(fileName: string) {
|
export function getFileContentByName(fileName: string) {
|
||||||
if (fs.existsSync(fileName)) {
|
if (fs.existsSync(fileName)) {
|
||||||
@@ -245,6 +247,18 @@ export async function createFile(file: string, data: string = '') {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function handleLogPath(
|
||||||
|
logPath: string,
|
||||||
|
data: string = '',
|
||||||
|
): Promise<string> {
|
||||||
|
const absolutePath = path.resolve(config.logPath, logPath);
|
||||||
|
const logFileExist = await fileExist(absolutePath);
|
||||||
|
if (!logFileExist) {
|
||||||
|
await createFile(absolutePath, data);
|
||||||
|
}
|
||||||
|
return absolutePath;
|
||||||
|
}
|
||||||
|
|
||||||
export async function concurrentRun(
|
export async function concurrentRun(
|
||||||
fnList: Array<() => Promise<any>> = [],
|
fnList: Array<() => Promise<any>> = [],
|
||||||
max = 5,
|
max = 5,
|
||||||
@@ -469,19 +483,22 @@ export function psTree(pid: number): Promise<number[]> {
|
|||||||
|
|
||||||
export async function killTask(pid: number) {
|
export async function killTask(pid: number) {
|
||||||
const pids = await psTree(pid);
|
const pids = await psTree(pid);
|
||||||
// SIGALRM 14 时钟信号
|
// SIGINT 2 程序终止(interrupt)信号,不会打印额外信息
|
||||||
if (pids.length) {
|
if (pids.length) {
|
||||||
process.kill(pids[0], 14);
|
try {
|
||||||
|
[pid, ...pids].forEach((x) => {
|
||||||
|
process.kill(x, 2);
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
} else {
|
} else {
|
||||||
process.kill(pid, 14);
|
process.kill(pid, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPid(name: string) {
|
export async function getPid(name: string) {
|
||||||
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
|
const taskCommand = `ps -eo pid,command | grep "${name}" | grep -v grep | awk '{print $1}' | head -1 | xargs echo -n`;
|
||||||
const execAsync = promisify(exec);
|
const pid = await promiseExec(taskCommand);
|
||||||
let pid = (await execAsync(taskCommand)).stdout;
|
return pid ? Number(pid) : undefined;
|
||||||
return Number(pid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IVersion {
|
interface IVersion {
|
||||||
@@ -497,3 +514,40 @@ export async function parseVersion(path: string): Promise<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): Promise<string> {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (/^\d\d*\d$/.test(id)) {
|
||||||
|
id = `_${id}`;
|
||||||
|
} else {
|
||||||
|
id = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = command.split(/ +/);
|
||||||
|
let str = items[0];
|
||||||
|
if (items[0] === TASK_COMMAND) {
|
||||||
|
str = items[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const dotIndex = str.lastIndexOf('.');
|
||||||
|
|
||||||
|
if (dotIndex !== -1) {
|
||||||
|
str = str.slice(0, dotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const slashIndex = str.lastIndexOf('/');
|
||||||
|
|
||||||
|
let tempStr = '';
|
||||||
|
if (slashIndex !== -1) {
|
||||||
|
tempStr = str.slice(0, slashIndex);
|
||||||
|
const _slashIndex = tempStr.lastIndexOf('/');
|
||||||
|
if (_slashIndex !== -1) {
|
||||||
|
tempStr = tempStr.slice(_slashIndex + 1);
|
||||||
|
}
|
||||||
|
str = `${tempStr}_${str.slice(slashIndex + 1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${str}${id}`;
|
||||||
|
}
|
||||||
|
|||||||
+19
-2
@@ -1,10 +1,11 @@
|
|||||||
import { sequelize } from '.';
|
import { sequelize } from '.';
|
||||||
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
||||||
|
import { NotificationInfo } from './notify';
|
||||||
|
|
||||||
export class AuthInfo {
|
export class AuthInfo {
|
||||||
ip?: string;
|
ip?: string;
|
||||||
type: AuthDataType;
|
type: AuthDataType;
|
||||||
info?: any;
|
info?: AuthModelInfo;
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
constructor(options: AuthInfo) {
|
constructor(options: AuthInfo) {
|
||||||
@@ -25,9 +26,25 @@ export enum AuthDataType {
|
|||||||
'authToken' = 'authToken',
|
'authToken' = 'authToken',
|
||||||
'notification' = 'notification',
|
'notification' = 'notification',
|
||||||
'removeLogFrequency' = 'removeLogFrequency',
|
'removeLogFrequency' = 'removeLogFrequency',
|
||||||
|
'systemConfig' = 'systemConfig',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthInstance extends Model<AuthInfo, AuthInfo>, AuthInfo {}
|
export interface SystemConfigInfo {
|
||||||
|
logRemoveFrequency?: number;
|
||||||
|
cronConcurrency?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginLogInfo {
|
||||||
|
timestamp?: number;
|
||||||
|
address?: string;
|
||||||
|
ip?: string;
|
||||||
|
platform?: string;
|
||||||
|
status?: LoginStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthModelInfo = SystemConfigInfo & Partial<NotificationInfo> & LoginLogInfo;
|
||||||
|
|
||||||
|
interface AuthInstance extends Model<AuthInfo, AuthInfo>, AuthInfo { }
|
||||||
export const AuthModel = sequelize.define<AuthInstance>('Auth', {
|
export const AuthModel = sequelize.define<AuthInstance>('Auth', {
|
||||||
ip: DataTypes.STRING,
|
ip: DataTypes.STRING,
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
|
|||||||
+4
-4
@@ -26,7 +26,7 @@ export class Crontab {
|
|||||||
this.saved = options.saved;
|
this.saved = options.saved;
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
this.status =
|
this.status =
|
||||||
options.status && CrontabStatus[options.status]
|
typeof options.status === 'number' && CrontabStatus[options.status]
|
||||||
? options.status
|
? options.status
|
||||||
: CrontabStatus.idle;
|
: CrontabStatus.idle;
|
||||||
this.timestamp = new Date().toString();
|
this.timestamp = new Date().toString();
|
||||||
@@ -43,10 +43,10 @@ export class Crontab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum CrontabStatus {
|
export enum CrontabStatus {
|
||||||
'running',
|
'running' = 0,
|
||||||
'idle',
|
'queued' = 0.5,
|
||||||
|
'idle' = 1,
|
||||||
'disabled',
|
'disabled',
|
||||||
'queued',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
|
interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ export class Dependence {
|
|||||||
|
|
||||||
constructor(options: Dependence) {
|
constructor(options: Dependence) {
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
this.status = options.status || DependenceStatus.installing;
|
this.status =
|
||||||
|
typeof options.status === 'number' && DependenceStatus[options.status]
|
||||||
|
? options.status
|
||||||
|
: DependenceStatus.queued;
|
||||||
this.type = options.type || DependenceTypes.nodejs;
|
this.type = options.type || DependenceTypes.nodejs;
|
||||||
this.timestamp = new Date().toString();
|
this.timestamp = new Date().toString();
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
|
|||||||
+4
-1
@@ -13,7 +13,10 @@ export class Env {
|
|||||||
constructor(options: Env) {
|
constructor(options: Env) {
|
||||||
this.value = options.value;
|
this.value = options.value;
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
this.status = options.status || EnvStatus.normal;
|
this.status =
|
||||||
|
typeof options.status === 'number' && EnvStatus[options.status]
|
||||||
|
? options.status
|
||||||
|
: EnvStatus.normal;
|
||||||
this.timestamp = new Date().toString();
|
this.timestamp = new Date().toString();
|
||||||
this.position = options.position;
|
this.position = options.position;
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
|
|||||||
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
import { Sequelize, Transaction } from 'sequelize';
|
import { Sequelize, Transaction } from 'sequelize';
|
||||||
import config from '../config/index';
|
import config from '../config/index';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
export const sequelize = new Sequelize({
|
export const sequelize = new Sequelize({
|
||||||
dialect: 'sqlite',
|
dialect: 'sqlite',
|
||||||
storage: `${config.dbPath}database.sqlite`,
|
storage: join(config.dbPath, 'database.sqlite'),
|
||||||
logging: false,
|
logging: false,
|
||||||
retry: {
|
retry: {
|
||||||
max: 10,
|
max: 10,
|
||||||
|
|||||||
@@ -72,10 +72,12 @@ export class DingtalkBotNotification extends NotificationBaseInfo {
|
|||||||
|
|
||||||
export class WeWorkBotNotification extends NotificationBaseInfo {
|
export class WeWorkBotNotification extends NotificationBaseInfo {
|
||||||
public weWorkBotKey = '';
|
public weWorkBotKey = '';
|
||||||
|
public weWorkOrigin = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WeWorkAppNotification extends NotificationBaseInfo {
|
export class WeWorkAppNotification extends NotificationBaseInfo {
|
||||||
public weWorkAppKey = '';
|
public weWorkAppKey = '';
|
||||||
|
public weWorkOrigin = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AibotkNotification extends NotificationBaseInfo {
|
export class AibotkNotification extends NotificationBaseInfo {
|
||||||
|
|||||||
@@ -26,13 +26,6 @@ export interface AppToken {
|
|||||||
|
|
||||||
export type AppScope = 'envs' | 'crons' | 'configs' | 'scripts' | 'logs';
|
export type AppScope = 'envs' | 'crons' | 'configs' | 'scripts' | 'logs';
|
||||||
|
|
||||||
export enum CrontabStatus {
|
|
||||||
'running',
|
|
||||||
'idle',
|
|
||||||
'disabled',
|
|
||||||
'queued',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AppInstance extends Model<App, App>, App {}
|
interface AppInstance extends Model<App, App>, App {}
|
||||||
export const AppModel = sequelize.define<AppInstance>('App', {
|
export const AppModel = sequelize.define<AppInstance>('App', {
|
||||||
name: { type: DataTypes.STRING, unique: 'name' },
|
name: { type: DataTypes.STRING, unique: 'name' },
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export class Subscription {
|
|||||||
this.name = options.name || options.alias;
|
this.name = options.name || options.alias;
|
||||||
this.type = options.type;
|
this.type = options.type;
|
||||||
this.schedule = options.schedule;
|
this.schedule = options.schedule;
|
||||||
this.status =
|
this.status = this.status =
|
||||||
options.status && SubscriptionStatus[options.status]
|
typeof options.status === 'number' && SubscriptionStatus[options.status]
|
||||||
? options.status
|
? options.status
|
||||||
: SubscriptionStatus.idle;
|
: SubscriptionStatus.idle;
|
||||||
this.url = options.url;
|
this.url = options.url;
|
||||||
|
|||||||
+13
-25
@@ -10,26 +10,27 @@ import Container from 'typedi';
|
|||||||
import OpenService from '../services/open';
|
import OpenService from '../services/open';
|
||||||
import rewrite from 'express-urlrewrite';
|
import rewrite from 'express-urlrewrite';
|
||||||
import UserService from '../services/user';
|
import UserService from '../services/user';
|
||||||
import handler from 'serve-handler';
|
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { EnvModel } from '../data/env';
|
import { EnvModel } from '../data/env';
|
||||||
import { errors } from 'celebrate';
|
import { errors } from 'celebrate';
|
||||||
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
|
import { serveEnv } from '../config/serverEnv';
|
||||||
|
|
||||||
export default ({ app }: { app: Application }) => {
|
export default ({ app }: { app: Application }) => {
|
||||||
app.enable('trust proxy');
|
app.enable('trust proxy');
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
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));
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use(
|
||||||
if (req.path.startsWith('/api') || req.path.startsWith('/open')) {
|
'/api/public',
|
||||||
next();
|
createProxyMiddleware({
|
||||||
} else {
|
target: `http://localhost:${config.publicPort}/api`,
|
||||||
return handler(req, res, {
|
changeOrigin: true,
|
||||||
public: 'static/dist',
|
pathRewrite: { '/api/public': '' },
|
||||||
rewrites: [{ source: '**', destination: '/index.html' }],
|
}),
|
||||||
});
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
app.use(bodyParser.json({ limit: '50mb' }));
|
app.use(bodyParser.json({ limit: '50mb' }));
|
||||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||||
|
|
||||||
@@ -108,8 +109,7 @@ export default ({ app }: { app: Application }) => {
|
|||||||
if (
|
if (
|
||||||
Object.keys(authInfo).length === 2 &&
|
Object.keys(authInfo).length === 2 &&
|
||||||
authInfo.username === 'admin' &&
|
authInfo.username === 'admin' &&
|
||||||
authInfo.password === 'admin' &&
|
authInfo.password === 'admin'
|
||||||
envCount === 0
|
|
||||||
) {
|
) {
|
||||||
isInitialized = false;
|
isInitialized = false;
|
||||||
}
|
}
|
||||||
@@ -170,18 +170,6 @@ export default ({ app }: { app: Application }) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use(
|
|
||||||
Sentry.Handlers.errorHandler({
|
|
||||||
shouldHandleError(error) {
|
|
||||||
// 排除 SequelizeUniqueConstraintError / NotFound
|
|
||||||
return (
|
|
||||||
!['SequelizeUniqueConstraintError'].includes(error.name) ||
|
|
||||||
!['Not Found'].includes(error.message)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
(
|
(
|
||||||
err: Error & { status: number },
|
err: Error & { status: number },
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default async () => {
|
|||||||
// 初始化更新所有任务状态为空闲
|
// 初始化更新所有任务状态为空闲
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
{ status: CrontabStatus.idle },
|
{ status: CrontabStatus.idle },
|
||||||
{ where: { status: [CrontabStatus.running, CrontabStatus.queued] } },
|
{ where: {} },
|
||||||
);
|
);
|
||||||
|
|
||||||
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
||||||
|
|||||||
@@ -13,10 +13,18 @@ const logPath = path.join(dataPath, 'log/');
|
|||||||
const uploadPath = path.join(dataPath, 'upload/');
|
const uploadPath = path.join(dataPath, 'upload/');
|
||||||
const bakPath = path.join(dataPath, 'bak/');
|
const bakPath = path.join(dataPath, 'bak/');
|
||||||
const samplePath = path.join(rootPath, 'sample/');
|
const samplePath = path.join(rootPath, 'sample/');
|
||||||
|
const tmpPath = path.join(logPath, '.tmp/');
|
||||||
const confFile = path.join(configPath, 'config.sh');
|
const confFile = path.join(configPath, 'config.sh');
|
||||||
const authConfigFile = path.join(configPath, 'auth.json');
|
const authConfigFile = path.join(configPath, 'auth.json');
|
||||||
const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
|
const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
|
||||||
const sampleAuthFile = path.join(samplePath, 'auth.sample.json');
|
const sampleAuthFile = path.join(samplePath, 'auth.sample.json');
|
||||||
|
const sampleTaskShellFile = path.join(samplePath, 'task.sample.sh');
|
||||||
|
const sampleNotifyJsFile = path.join(samplePath, 'notify.js');
|
||||||
|
const sampleNotifyPyFile = path.join(samplePath, 'notify.py');
|
||||||
|
const scriptNotifyJsFile = path.join(scriptPath, 'sendNotify.js');
|
||||||
|
const scriptNotifyPyFile = path.join(scriptPath, 'notify.py');
|
||||||
|
const TaskBeforeFile = path.join(configPath, 'task_before.sh');
|
||||||
|
const TaskAfterFile = path.join(configPath, 'task_after.sh');
|
||||||
const homedir = os.homedir();
|
const homedir = os.homedir();
|
||||||
const sshPath = path.resolve(homedir, '.ssh');
|
const sshPath = path.resolve(homedir, '.ssh');
|
||||||
const sshdPath = path.join(dataPath, 'ssh.d');
|
const sshdPath = path.join(dataPath, 'ssh.d');
|
||||||
@@ -31,19 +39,16 @@ export default async () => {
|
|||||||
const sshDirExist = await fileExist(sshPath);
|
const sshDirExist = await fileExist(sshPath);
|
||||||
const bakDirExist = await fileExist(bakPath);
|
const bakDirExist = await fileExist(bakPath);
|
||||||
const sshdDirExist = await fileExist(sshdPath);
|
const sshdDirExist = await fileExist(sshdPath);
|
||||||
|
const tmpDirExist = await fileExist(tmpPath);
|
||||||
|
const scriptNotifyJsFileExist = await fileExist(scriptNotifyJsFile);
|
||||||
|
const scriptNotifyPyFileExist = await fileExist(scriptNotifyPyFile);
|
||||||
|
const TaskBeforeFileExist = await fileExist(TaskBeforeFile);
|
||||||
|
const TaskAfterFileExist = await fileExist(TaskAfterFile);
|
||||||
|
|
||||||
if (!configDirExist) {
|
if (!configDirExist) {
|
||||||
fs.mkdirSync(configPath);
|
fs.mkdirSync(configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authFileExist) {
|
|
||||||
fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confFileExist) {
|
|
||||||
fs.writeFileSync(confFile, fs.readFileSync(sampleConfigFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scriptDirExist) {
|
if (!scriptDirExist) {
|
||||||
fs.mkdirSync(scriptPath);
|
fs.mkdirSync(scriptPath);
|
||||||
}
|
}
|
||||||
@@ -52,6 +57,10 @@ export default async () => {
|
|||||||
fs.mkdirSync(logPath);
|
fs.mkdirSync(logPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!tmpDirExist) {
|
||||||
|
fs.mkdirSync(tmpPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (!uploadDirExist) {
|
if (!uploadDirExist) {
|
||||||
fs.mkdirSync(uploadPath);
|
fs.mkdirSync(uploadPath);
|
||||||
}
|
}
|
||||||
@@ -68,6 +77,31 @@ export default async () => {
|
|||||||
fs.mkdirSync(sshdPath);
|
fs.mkdirSync(sshdPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化文件
|
||||||
|
if (!authFileExist) {
|
||||||
|
fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confFileExist) {
|
||||||
|
fs.writeFileSync(confFile, fs.readFileSync(sampleConfigFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scriptNotifyJsFileExist) {
|
||||||
|
fs.writeFileSync(scriptNotifyJsFile, fs.readFileSync(sampleNotifyJsFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scriptNotifyPyFileExist) {
|
||||||
|
fs.writeFileSync(scriptNotifyPyFile, fs.readFileSync(sampleNotifyPyFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TaskBeforeFileExist) {
|
||||||
|
fs.writeFileSync(TaskBeforeFile, fs.readFileSync(sampleTaskShellFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TaskAfterFileExist) {
|
||||||
|
fs.writeFileSync(TaskAfterFile, fs.readFileSync(sampleTaskShellFile));
|
||||||
|
}
|
||||||
|
|
||||||
dotenv.config({ path: confFile });
|
dotenv.config({ path: confFile });
|
||||||
|
|
||||||
Logger.info('✌️ Init file down');
|
Logger.info('✌️ Init file down');
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ScheduleService from '../services/schedule';
|
|||||||
import SubscriptionService from '../services/subscription';
|
import SubscriptionService from '../services/subscription';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { fileExist } from '../config/util';
|
import { fileExist } from '../config/util';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
@@ -11,8 +12,9 @@ export default async () => {
|
|||||||
const subscriptionService = Container.get(SubscriptionService);
|
const subscriptionService = Container.get(SubscriptionService);
|
||||||
|
|
||||||
// 生成内置token
|
// 生成内置token
|
||||||
let tokenCommand = `tsx ${config.rootPath}/back/token.ts`;
|
let tokenCommand = `tsx ${join(config.rootPath, 'back/token.ts')}`;
|
||||||
const tokenFile = `${config.rootPath}static/build/token.js`;
|
const tokenFile = join(config.rootPath, 'static/build/token.js');
|
||||||
|
|
||||||
if (await fileExist(tokenFile)) {
|
if (await fileExist(tokenFile)) {
|
||||||
tokenCommand = `node ${tokenFile}`;
|
tokenCommand = `node ${tokenFile}`;
|
||||||
}
|
}
|
||||||
@@ -27,7 +29,7 @@ export default async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 运行删除日志任务
|
// 运行删除日志任务
|
||||||
const data = await systemService.getLogRemoveFrequency();
|
const data = await systemService.getSystemConfig();
|
||||||
if (data && data.info && data.info.frequency) {
|
if (data && data.info && data.info.frequency) {
|
||||||
const rmlogCron = {
|
const rmlogCron = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
|
|||||||
@@ -10,12 +10,18 @@ export default async ({ expressApp }: { expressApp: Application }) => {
|
|||||||
const { version } = await parseVersion(config.versionFile);
|
const { version } = await parseVersion(config.versionFile);
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
|
ignoreErrors: [
|
||||||
|
/SequelizeUniqueConstraintError/i,
|
||||||
|
/Validation error/i,
|
||||||
|
/UnauthorizedError/i,
|
||||||
|
/celebrate request validation failed/i,
|
||||||
|
],
|
||||||
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
||||||
integrations: [
|
integrations: [
|
||||||
new Sentry.Integrations.Http({ tracing: true }),
|
new Sentry.Integrations.Http({ tracing: true }),
|
||||||
new Tracing.Integrations.Express({ app: expressApp }),
|
new Tracing.Integrations.Express({ app: expressApp }),
|
||||||
],
|
],
|
||||||
tracesSampleRate: 0.1,
|
tracesSampleRate: 0.8,
|
||||||
release: version,
|
release: version,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,14 @@ export default async ({ server }: { server: Server }) => {
|
|||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
Logger.info('✌️ Server need close');
|
Logger.info('✌️ Server need close');
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
Logger.info('✌️ Server closed');
|
setTimeout(() => {
|
||||||
process.exit(0);
|
process.exit();
|
||||||
|
}, 10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Forcing server close !!!');
|
||||||
|
process.exit(1);
|
||||||
|
}, 15000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package com.ql.cron;
|
package com.ql.cron;
|
||||||
|
|
||||||
service CronService {
|
service Cron {
|
||||||
rpc addCron(AddCronRequest) returns (AddCronResponse);
|
rpc addCron(AddCronRequest) returns (AddCronResponse);
|
||||||
rpc delCron(DeleteCronRequest) returns (DeleteCronResponse);
|
rpc delCron(DeleteCronRequest) returns (DeleteCronResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message Cron {
|
message ICron {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string schedule = 2;
|
string schedule = 2;
|
||||||
string command = 3;
|
string command = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddCronRequest { repeated Cron crons = 1; }
|
message AddCronRequest { repeated ICron crons = 1; }
|
||||||
|
|
||||||
message AddCronResponse {}
|
message AddCronResponse {}
|
||||||
|
|
||||||
|
|||||||
+29
-29
@@ -15,14 +15,14 @@ import _m0 from 'protobufjs/minimal';
|
|||||||
|
|
||||||
export const protobufPackage = 'com.ql.cron';
|
export const protobufPackage = 'com.ql.cron';
|
||||||
|
|
||||||
export interface Cron {
|
export interface ICron {
|
||||||
id: string;
|
id: string;
|
||||||
schedule: string;
|
schedule: string;
|
||||||
command: string;
|
command: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddCronRequest {
|
export interface AddCronRequest {
|
||||||
crons: Cron[];
|
crons: ICron[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddCronResponse {}
|
export interface AddCronResponse {}
|
||||||
@@ -33,12 +33,12 @@ export interface DeleteCronRequest {
|
|||||||
|
|
||||||
export interface DeleteCronResponse {}
|
export interface DeleteCronResponse {}
|
||||||
|
|
||||||
function createBaseCron(): Cron {
|
function createBaseICron(): ICron {
|
||||||
return { id: '', schedule: '', command: '' };
|
return { id: '', schedule: '', command: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Cron = {
|
export const ICron = {
|
||||||
encode(message: Cron, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
encode(message: ICron, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||||
if (message.id !== '') {
|
if (message.id !== '') {
|
||||||
writer.uint32(10).string(message.id);
|
writer.uint32(10).string(message.id);
|
||||||
}
|
}
|
||||||
@@ -51,11 +51,11 @@ export const Cron = {
|
|||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
|
|
||||||
decode(input: _m0.Reader | Uint8Array, length?: number): Cron {
|
decode(input: _m0.Reader | Uint8Array, length?: number): ICron {
|
||||||
const reader =
|
const reader =
|
||||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||||
let end = length === undefined ? reader.len : reader.pos + length;
|
let end = length === undefined ? reader.len : reader.pos + length;
|
||||||
const message = createBaseCron();
|
const message = createBaseICron();
|
||||||
while (reader.pos < end) {
|
while (reader.pos < end) {
|
||||||
const tag = reader.uint32();
|
const tag = reader.uint32();
|
||||||
switch (tag >>> 3) {
|
switch (tag >>> 3) {
|
||||||
@@ -89,7 +89,7 @@ export const Cron = {
|
|||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
|
|
||||||
fromJSON(object: any): Cron {
|
fromJSON(object: any): ICron {
|
||||||
return {
|
return {
|
||||||
id: isSet(object.id) ? String(object.id) : '',
|
id: isSet(object.id) ? String(object.id) : '',
|
||||||
schedule: isSet(object.schedule) ? String(object.schedule) : '',
|
schedule: isSet(object.schedule) ? String(object.schedule) : '',
|
||||||
@@ -97,7 +97,7 @@ export const Cron = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
toJSON(message: Cron): unknown {
|
toJSON(message: ICron): unknown {
|
||||||
const obj: any = {};
|
const obj: any = {};
|
||||||
message.id !== undefined && (obj.id = message.id);
|
message.id !== undefined && (obj.id = message.id);
|
||||||
message.schedule !== undefined && (obj.schedule = message.schedule);
|
message.schedule !== undefined && (obj.schedule = message.schedule);
|
||||||
@@ -105,12 +105,12 @@ export const Cron = {
|
|||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
create<I extends Exact<DeepPartial<Cron>, I>>(base?: I): Cron {
|
create<I extends Exact<DeepPartial<ICron>, I>>(base?: I): ICron {
|
||||||
return Cron.fromPartial(base ?? {});
|
return ICron.fromPartial(base ?? {});
|
||||||
},
|
},
|
||||||
|
|
||||||
fromPartial<I extends Exact<DeepPartial<Cron>, I>>(object: I): Cron {
|
fromPartial<I extends Exact<DeepPartial<ICron>, I>>(object: I): ICron {
|
||||||
const message = createBaseCron();
|
const message = createBaseICron();
|
||||||
message.id = object.id ?? '';
|
message.id = object.id ?? '';
|
||||||
message.schedule = object.schedule ?? '';
|
message.schedule = object.schedule ?? '';
|
||||||
message.command = object.command ?? '';
|
message.command = object.command ?? '';
|
||||||
@@ -128,7 +128,7 @@ export const AddCronRequest = {
|
|||||||
writer: _m0.Writer = _m0.Writer.create(),
|
writer: _m0.Writer = _m0.Writer.create(),
|
||||||
): _m0.Writer {
|
): _m0.Writer {
|
||||||
for (const v of message.crons) {
|
for (const v of message.crons) {
|
||||||
Cron.encode(v!, writer.uint32(10).fork()).ldelim();
|
ICron.encode(v!, writer.uint32(10).fork()).ldelim();
|
||||||
}
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
@@ -146,7 +146,7 @@ export const AddCronRequest = {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.crons.push(Cron.decode(reader, reader.uint32()));
|
message.crons.push(ICron.decode(reader, reader.uint32()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((tag & 7) == 4 || tag == 0) {
|
if ((tag & 7) == 4 || tag == 0) {
|
||||||
@@ -160,7 +160,7 @@ export const AddCronRequest = {
|
|||||||
fromJSON(object: any): AddCronRequest {
|
fromJSON(object: any): AddCronRequest {
|
||||||
return {
|
return {
|
||||||
crons: Array.isArray(object?.crons)
|
crons: Array.isArray(object?.crons)
|
||||||
? object.crons.map((e: any) => Cron.fromJSON(e))
|
? object.crons.map((e: any) => ICron.fromJSON(e))
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -168,7 +168,7 @@ export const AddCronRequest = {
|
|||||||
toJSON(message: AddCronRequest): unknown {
|
toJSON(message: AddCronRequest): unknown {
|
||||||
const obj: any = {};
|
const obj: any = {};
|
||||||
if (message.crons) {
|
if (message.crons) {
|
||||||
obj.crons = message.crons.map((e) => (e ? Cron.toJSON(e) : undefined));
|
obj.crons = message.crons.map((e) => (e ? ICron.toJSON(e) : undefined));
|
||||||
} else {
|
} else {
|
||||||
obj.crons = [];
|
obj.crons = [];
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ export const AddCronRequest = {
|
|||||||
object: I,
|
object: I,
|
||||||
): AddCronRequest {
|
): AddCronRequest {
|
||||||
const message = createBaseAddCronRequest();
|
const message = createBaseAddCronRequest();
|
||||||
message.crons = object.crons?.map((e) => Cron.fromPartial(e)) || [];
|
message.crons = object.crons?.map((e) => ICron.fromPartial(e)) || [];
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -366,10 +366,10 @@ export const DeleteCronResponse = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CronServiceService = typeof CronServiceService;
|
export type CronService = typeof CronService;
|
||||||
export const CronServiceService = {
|
export const CronService = {
|
||||||
addCron: {
|
addCron: {
|
||||||
path: '/com.ql.cron.CronService/addCron',
|
path: '/com.ql.cron.Cron/addCron',
|
||||||
requestStream: false,
|
requestStream: false,
|
||||||
responseStream: false,
|
responseStream: false,
|
||||||
requestSerialize: (value: AddCronRequest) =>
|
requestSerialize: (value: AddCronRequest) =>
|
||||||
@@ -380,7 +380,7 @@ export const CronServiceService = {
|
|||||||
responseDeserialize: (value: Buffer) => AddCronResponse.decode(value),
|
responseDeserialize: (value: Buffer) => AddCronResponse.decode(value),
|
||||||
},
|
},
|
||||||
delCron: {
|
delCron: {
|
||||||
path: '/com.ql.cron.CronService/delCron',
|
path: '/com.ql.cron.Cron/delCron',
|
||||||
requestStream: false,
|
requestStream: false,
|
||||||
responseStream: false,
|
responseStream: false,
|
||||||
requestSerialize: (value: DeleteCronRequest) =>
|
requestSerialize: (value: DeleteCronRequest) =>
|
||||||
@@ -392,12 +392,12 @@ export const CronServiceService = {
|
|||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export interface CronServiceServer extends UntypedServiceImplementation {
|
export interface CronServer extends UntypedServiceImplementation {
|
||||||
addCron: handleUnaryCall<AddCronRequest, AddCronResponse>;
|
addCron: handleUnaryCall<AddCronRequest, AddCronResponse>;
|
||||||
delCron: handleUnaryCall<DeleteCronRequest, DeleteCronResponse>;
|
delCron: handleUnaryCall<DeleteCronRequest, DeleteCronResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CronServiceClient extends Client {
|
export interface CronClient extends Client {
|
||||||
addCron(
|
addCron(
|
||||||
request: AddCronRequest,
|
request: AddCronRequest,
|
||||||
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
||||||
@@ -439,16 +439,16 @@ export interface CronServiceClient extends Client {
|
|||||||
): ClientUnaryCall;
|
): ClientUnaryCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CronServiceClient = makeGenericClientConstructor(
|
export const CronClient = makeGenericClientConstructor(
|
||||||
CronServiceService,
|
CronService,
|
||||||
'com.ql.cron.CronService',
|
'com.ql.cron.Cron',
|
||||||
) as unknown as {
|
) as unknown as {
|
||||||
new (
|
new (
|
||||||
address: string,
|
address: string,
|
||||||
credentials: ChannelCredentials,
|
credentials: ChannelCredentials,
|
||||||
options?: Partial<ClientOptions>,
|
options?: Partial<ClientOptions>,
|
||||||
): CronServiceClient;
|
): CronClient;
|
||||||
service: typeof CronServiceService;
|
service: typeof CronService;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Builtin =
|
type Builtin =
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package com.ql.health;
|
||||||
|
|
||||||
|
message HealthCheckRequest {
|
||||||
|
string service = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HealthCheckResponse {
|
||||||
|
enum ServingStatus {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
SERVING = 1;
|
||||||
|
NOT_SERVING = 2;
|
||||||
|
SERVICE_UNKNOWN = 3;
|
||||||
|
}
|
||||||
|
ServingStatus status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service Health {
|
||||||
|
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
|
||||||
|
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import {
|
||||||
|
CallOptions,
|
||||||
|
ChannelCredentials,
|
||||||
|
Client,
|
||||||
|
ClientOptions,
|
||||||
|
ClientReadableStream,
|
||||||
|
ClientUnaryCall,
|
||||||
|
handleServerStreamingCall,
|
||||||
|
handleUnaryCall,
|
||||||
|
makeGenericClientConstructor,
|
||||||
|
Metadata,
|
||||||
|
ServiceError,
|
||||||
|
UntypedServiceImplementation,
|
||||||
|
} from '@grpc/grpc-js';
|
||||||
|
import _m0 from 'protobufjs/minimal';
|
||||||
|
|
||||||
|
export const protobufPackage = 'com.ql.health';
|
||||||
|
|
||||||
|
export interface HealthCheckRequest {
|
||||||
|
service: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HealthCheckResponse {
|
||||||
|
status: HealthCheckResponse_ServingStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HealthCheckResponse_ServingStatus {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
SERVING = 1,
|
||||||
|
NOT_SERVING = 2,
|
||||||
|
SERVICE_UNKNOWN = 3,
|
||||||
|
UNRECOGNIZED = -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function healthCheckResponse_ServingStatusFromJSON(
|
||||||
|
object: any,
|
||||||
|
): HealthCheckResponse_ServingStatus {
|
||||||
|
switch (object) {
|
||||||
|
case 0:
|
||||||
|
case 'UNKNOWN':
|
||||||
|
return HealthCheckResponse_ServingStatus.UNKNOWN;
|
||||||
|
case 1:
|
||||||
|
case 'SERVING':
|
||||||
|
return HealthCheckResponse_ServingStatus.SERVING;
|
||||||
|
case 2:
|
||||||
|
case 'NOT_SERVING':
|
||||||
|
return HealthCheckResponse_ServingStatus.NOT_SERVING;
|
||||||
|
case 3:
|
||||||
|
case 'SERVICE_UNKNOWN':
|
||||||
|
return HealthCheckResponse_ServingStatus.SERVICE_UNKNOWN;
|
||||||
|
case -1:
|
||||||
|
case 'UNRECOGNIZED':
|
||||||
|
default:
|
||||||
|
return HealthCheckResponse_ServingStatus.UNRECOGNIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function healthCheckResponse_ServingStatusToJSON(
|
||||||
|
object: HealthCheckResponse_ServingStatus,
|
||||||
|
): string {
|
||||||
|
switch (object) {
|
||||||
|
case HealthCheckResponse_ServingStatus.UNKNOWN:
|
||||||
|
return 'UNKNOWN';
|
||||||
|
case HealthCheckResponse_ServingStatus.SERVING:
|
||||||
|
return 'SERVING';
|
||||||
|
case HealthCheckResponse_ServingStatus.NOT_SERVING:
|
||||||
|
return 'NOT_SERVING';
|
||||||
|
case HealthCheckResponse_ServingStatus.SERVICE_UNKNOWN:
|
||||||
|
return 'SERVICE_UNKNOWN';
|
||||||
|
case HealthCheckResponse_ServingStatus.UNRECOGNIZED:
|
||||||
|
default:
|
||||||
|
return 'UNRECOGNIZED';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBaseHealthCheckRequest(): HealthCheckRequest {
|
||||||
|
return { service: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HealthCheckRequest = {
|
||||||
|
encode(
|
||||||
|
message: HealthCheckRequest,
|
||||||
|
writer: _m0.Writer = _m0.Writer.create(),
|
||||||
|
): _m0.Writer {
|
||||||
|
if (message.service !== '') {
|
||||||
|
writer.uint32(10).string(message.service);
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode(input: _m0.Reader | Uint8Array, length?: number): HealthCheckRequest {
|
||||||
|
const reader =
|
||||||
|
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||||
|
let end = length === undefined ? reader.len : reader.pos + length;
|
||||||
|
const message = createBaseHealthCheckRequest();
|
||||||
|
while (reader.pos < end) {
|
||||||
|
const tag = reader.uint32();
|
||||||
|
switch (tag >>> 3) {
|
||||||
|
case 1:
|
||||||
|
if (tag != 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.service = reader.string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((tag & 7) == 4 || tag == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.skipType(tag & 7);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON(object: any): HealthCheckRequest {
|
||||||
|
return { service: isSet(object.service) ? String(object.service) : '' };
|
||||||
|
},
|
||||||
|
|
||||||
|
toJSON(message: HealthCheckRequest): unknown {
|
||||||
|
const obj: any = {};
|
||||||
|
message.service !== undefined && (obj.service = message.service);
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
create<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(
|
||||||
|
base?: I,
|
||||||
|
): HealthCheckRequest {
|
||||||
|
return HealthCheckRequest.fromPartial(base ?? {});
|
||||||
|
},
|
||||||
|
|
||||||
|
fromPartial<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(
|
||||||
|
object: I,
|
||||||
|
): HealthCheckRequest {
|
||||||
|
const message = createBaseHealthCheckRequest();
|
||||||
|
message.service = object.service ?? '';
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function createBaseHealthCheckResponse(): HealthCheckResponse {
|
||||||
|
return { status: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HealthCheckResponse = {
|
||||||
|
encode(
|
||||||
|
message: HealthCheckResponse,
|
||||||
|
writer: _m0.Writer = _m0.Writer.create(),
|
||||||
|
): _m0.Writer {
|
||||||
|
if (message.status !== 0) {
|
||||||
|
writer.uint32(8).int32(message.status);
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode(input: _m0.Reader | Uint8Array, length?: number): HealthCheckResponse {
|
||||||
|
const reader =
|
||||||
|
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||||
|
let end = length === undefined ? reader.len : reader.pos + length;
|
||||||
|
const message = createBaseHealthCheckResponse();
|
||||||
|
while (reader.pos < end) {
|
||||||
|
const tag = reader.uint32();
|
||||||
|
switch (tag >>> 3) {
|
||||||
|
case 1:
|
||||||
|
if (tag != 8) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.status = reader.int32() as any;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((tag & 7) == 4 || tag == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.skipType(tag & 7);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON(object: any): HealthCheckResponse {
|
||||||
|
return {
|
||||||
|
status: isSet(object.status)
|
||||||
|
? healthCheckResponse_ServingStatusFromJSON(object.status)
|
||||||
|
: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toJSON(message: HealthCheckResponse): unknown {
|
||||||
|
const obj: any = {};
|
||||||
|
message.status !== undefined &&
|
||||||
|
(obj.status = healthCheckResponse_ServingStatusToJSON(message.status));
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
create<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(
|
||||||
|
base?: I,
|
||||||
|
): HealthCheckResponse {
|
||||||
|
return HealthCheckResponse.fromPartial(base ?? {});
|
||||||
|
},
|
||||||
|
|
||||||
|
fromPartial<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(
|
||||||
|
object: I,
|
||||||
|
): HealthCheckResponse {
|
||||||
|
const message = createBaseHealthCheckResponse();
|
||||||
|
message.status = object.status ?? 0;
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthService = typeof HealthService;
|
||||||
|
export const HealthService = {
|
||||||
|
check: {
|
||||||
|
path: '/com.ql.health.Health/Check',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: false,
|
||||||
|
requestSerialize: (value: HealthCheckRequest) =>
|
||||||
|
Buffer.from(HealthCheckRequest.encode(value).finish()),
|
||||||
|
requestDeserialize: (value: Buffer) => HealthCheckRequest.decode(value),
|
||||||
|
responseSerialize: (value: HealthCheckResponse) =>
|
||||||
|
Buffer.from(HealthCheckResponse.encode(value).finish()),
|
||||||
|
responseDeserialize: (value: Buffer) => HealthCheckResponse.decode(value),
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
path: '/com.ql.health.Health/Watch',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: true,
|
||||||
|
requestSerialize: (value: HealthCheckRequest) =>
|
||||||
|
Buffer.from(HealthCheckRequest.encode(value).finish()),
|
||||||
|
requestDeserialize: (value: Buffer) => HealthCheckRequest.decode(value),
|
||||||
|
responseSerialize: (value: HealthCheckResponse) =>
|
||||||
|
Buffer.from(HealthCheckResponse.encode(value).finish()),
|
||||||
|
responseDeserialize: (value: Buffer) => HealthCheckResponse.decode(value),
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export interface HealthServer extends UntypedServiceImplementation {
|
||||||
|
check: handleUnaryCall<HealthCheckRequest, HealthCheckResponse>;
|
||||||
|
watch: handleServerStreamingCall<HealthCheckRequest, HealthCheckResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HealthClient extends Client {
|
||||||
|
check(
|
||||||
|
request: HealthCheckRequest,
|
||||||
|
callback: (
|
||||||
|
error: ServiceError | null,
|
||||||
|
response: HealthCheckResponse,
|
||||||
|
) => void,
|
||||||
|
): ClientUnaryCall;
|
||||||
|
check(
|
||||||
|
request: HealthCheckRequest,
|
||||||
|
metadata: Metadata,
|
||||||
|
callback: (
|
||||||
|
error: ServiceError | null,
|
||||||
|
response: HealthCheckResponse,
|
||||||
|
) => void,
|
||||||
|
): ClientUnaryCall;
|
||||||
|
check(
|
||||||
|
request: HealthCheckRequest,
|
||||||
|
metadata: Metadata,
|
||||||
|
options: Partial<CallOptions>,
|
||||||
|
callback: (
|
||||||
|
error: ServiceError | null,
|
||||||
|
response: HealthCheckResponse,
|
||||||
|
) => void,
|
||||||
|
): ClientUnaryCall;
|
||||||
|
watch(
|
||||||
|
request: HealthCheckRequest,
|
||||||
|
options?: Partial<CallOptions>,
|
||||||
|
): ClientReadableStream<HealthCheckResponse>;
|
||||||
|
watch(
|
||||||
|
request: HealthCheckRequest,
|
||||||
|
metadata?: Metadata,
|
||||||
|
options?: Partial<CallOptions>,
|
||||||
|
): ClientReadableStream<HealthCheckResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HealthClient = makeGenericClientConstructor(
|
||||||
|
HealthService,
|
||||||
|
'com.ql.health.Health',
|
||||||
|
) as unknown as {
|
||||||
|
new (
|
||||||
|
address: string,
|
||||||
|
credentials: ChannelCredentials,
|
||||||
|
options?: Partial<ClientOptions>,
|
||||||
|
): HealthClient;
|
||||||
|
service: typeof HealthService;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Builtin =
|
||||||
|
| Date
|
||||||
|
| Function
|
||||||
|
| Uint8Array
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
export type DeepPartial<T> = T extends Builtin
|
||||||
|
? T
|
||||||
|
: T extends Array<infer U>
|
||||||
|
? Array<DeepPartial<U>>
|
||||||
|
: T extends ReadonlyArray<infer U>
|
||||||
|
? ReadonlyArray<DeepPartial<U>>
|
||||||
|
: T extends {}
|
||||||
|
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||||
|
: Partial<T>;
|
||||||
|
|
||||||
|
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
||||||
|
export type Exact<P, I extends P> = P extends Builtin
|
||||||
|
? P
|
||||||
|
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
||||||
|
[K in Exclude<keyof I, KeysOfUnion<P>>]: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSet(value: any): boolean {
|
||||||
|
return value !== null && value !== undefined;
|
||||||
|
}
|
||||||
+12
-6
@@ -1,16 +1,21 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { exec } from 'child_process';
|
|
||||||
import Logger from './loaders/logger';
|
import Logger from './loaders/logger';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
import { HealthClient } from './protos/health';
|
||||||
|
import { credentials } from '@grpc/grpc-js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const client = new HealthClient(
|
||||||
|
`localhost:${config.cronPort}`,
|
||||||
|
credentials.createInsecure(),
|
||||||
|
);
|
||||||
|
|
||||||
app.get('/api/public/panel/log', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
exec('tail -n 300 ~/.pm2/logs/panel-error.log', (err, stdout, stderr) => {
|
client.check({ service: 'cron' }, (err, response) => {
|
||||||
if (err || stderr) {
|
if (err) {
|
||||||
return res.send({ code: 400, message: (err && err.message) || stderr });
|
return res.status(500).send({ code: 500, error: err });
|
||||||
}
|
}
|
||||||
return res.send({ code: 200, data: stdout });
|
return res.status(200).send({ code: 200, data: response });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -20,6 +25,7 @@ app
|
|||||||
await require('./loaders/db').default();
|
await require('./loaders/db').default();
|
||||||
|
|
||||||
Logger.debug(`✌️ 公共服务启动成功!`);
|
Logger.debug(`✌️ 公共服务启动成功!`);
|
||||||
|
process.send?.('ready');
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
Logger.error(err);
|
Logger.error(err);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
|||||||
import { AddCronRequest, AddCronResponse } from '../protos/cron';
|
import { AddCronRequest, AddCronResponse } from '../protos/cron';
|
||||||
import nodeSchedule from 'node-schedule';
|
import nodeSchedule from 'node-schedule';
|
||||||
import { scheduleStacks } from './data';
|
import { scheduleStacks } from './data';
|
||||||
import { exec } from 'child_process';
|
import { runCron } from '../shared/runCron';
|
||||||
|
|
||||||
const addCron = (
|
const addCron = (
|
||||||
call: ServerUnaryCall<AddCronRequest, AddCronResponse>,
|
call: ServerUnaryCall<AddCronRequest, AddCronResponse>,
|
||||||
@@ -16,7 +16,7 @@ const addCron = (
|
|||||||
scheduleStacks.set(
|
scheduleStacks.set(
|
||||||
id,
|
id,
|
||||||
nodeSchedule.scheduleJob(id, schedule, async () => {
|
nodeSchedule.scheduleJob(id, schedule, async () => {
|
||||||
exec(`ID=${id} ${command}`);
|
runCron(`ID=${id} ${command}`)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { credentials } from '@grpc/grpc-js';
|
|||||||
import {
|
import {
|
||||||
AddCronRequest,
|
AddCronRequest,
|
||||||
AddCronResponse,
|
AddCronResponse,
|
||||||
CronServiceClient,
|
CronClient,
|
||||||
DeleteCronRequest,
|
DeleteCronRequest,
|
||||||
DeleteCronResponse,
|
DeleteCronResponse,
|
||||||
} from '../protos/cron';
|
} from '../protos/cron';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
private client = new CronServiceClient(
|
private client = new CronClient(
|
||||||
`localhost:${config.cronPort}`,
|
`localhost:${config.cronPort}`,
|
||||||
credentials.createInsecure(),
|
credentials.createInsecure(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
||||||
|
import { HealthCheckRequest, HealthCheckResponse } from '../protos/health';
|
||||||
|
import config from '../config';
|
||||||
|
import { promiseExec } from '../config/util';
|
||||||
|
|
||||||
|
const check = async (
|
||||||
|
call: ServerUnaryCall<HealthCheckRequest, HealthCheckResponse>,
|
||||||
|
callback: sendUnaryData<HealthCheckResponse>,
|
||||||
|
) => {
|
||||||
|
switch (call.request.service) {
|
||||||
|
case 'cron':
|
||||||
|
const res = await promiseExec(
|
||||||
|
`curl -s http://localhost:${config.port}/api/system`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.includes('200')) {
|
||||||
|
return callback(null, { status: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const panelErrLog = await promiseExec(
|
||||||
|
`tail -n 300 ~/.pm2/logs/panel-error.log`,
|
||||||
|
);
|
||||||
|
const scheduleErrLog = await promiseExec(
|
||||||
|
`tail -n 300 ~/.pm2/logs/schedule-error.log`,
|
||||||
|
);
|
||||||
|
return callback(
|
||||||
|
new Error(`${scheduleErrLog || ''}\n${panelErrLog || ''}\n${res}`),
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return callback(null, { status: 1 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { check };
|
||||||
+10
-3
@@ -1,17 +1,24 @@
|
|||||||
import { Server, ServerCredentials } from '@grpc/grpc-js';
|
import { Server, ServerCredentials } from '@grpc/grpc-js';
|
||||||
import { CronServiceService } from '../protos/cron';
|
import { CronService } from '../protos/cron';
|
||||||
import { addCron } from './addCron';
|
import { addCron } from './addCron';
|
||||||
import { delCron } from './delCron';
|
import { delCron } from './delCron';
|
||||||
|
import { HealthService } from '../protos/health';
|
||||||
|
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();
|
||||||
server.addService(CronServiceService, { addCron, delCron });
|
server.addService(HealthService, { check });
|
||||||
|
server.addService(CronService, { addCron, delCron });
|
||||||
server.bindAsync(
|
server.bindAsync(
|
||||||
`localhost:${config.cronPort}`,
|
`localhost:${config.cronPort}`,
|
||||||
ServerCredentials.createInsecure(),
|
ServerCredentials.createInsecure(),
|
||||||
() => {
|
(err, port) => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
server.start();
|
server.start();
|
||||||
Logger.debug(`✌️ 定时服务启动成功!`);
|
Logger.debug(`✌️ 定时服务启动成功!`);
|
||||||
|
process.send?.('ready');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
+14
-16
@@ -2,20 +2,21 @@ import { Service, Inject } from 'typedi';
|
|||||||
import winston from 'winston';
|
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, spawn } from 'child_process';
|
import { exec, execSync } from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
import {
|
import {
|
||||||
getFileContentByName,
|
getFileContentByName,
|
||||||
concurrentRun,
|
|
||||||
fileExist,
|
fileExist,
|
||||||
killTask,
|
killTask,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import { promises, existsSync } from 'fs';
|
import { promises, existsSync } from 'fs';
|
||||||
import { Op, where, col as colFn, FindOptions } 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';
|
||||||
import cronClient from '../schedule/client';
|
import cronClient from '../schedule/client';
|
||||||
|
import taskLimit from '../shared/pLimit';
|
||||||
|
import { spawn } from 'cross-spawn';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronService {
|
export default class CronService {
|
||||||
@@ -192,17 +193,13 @@ export default class CronService {
|
|||||||
{
|
{
|
||||||
[operate2]: [
|
[operate2]: [
|
||||||
{ [operate]: `%${value}%` },
|
{ [operate]: `%${value}%` },
|
||||||
{ [operate]: `%${encodeURIComponent(value)}%` },
|
{ [operate]: `%${encodeURI(value)}%` },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
[operate2]: [
|
[operate2]: [
|
||||||
where(colFn(property), operate, `%${value}%`),
|
where(colFn(property), operate, `%${value}%`),
|
||||||
where(
|
where(colFn(property), operate, `%${encodeURI(value)}%`),
|
||||||
colFn(property),
|
|
||||||
operate,
|
|
||||||
`%${encodeURIComponent(value)}%`,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -229,7 +226,7 @@ export default class CronService {
|
|||||||
q[column] = {
|
q[column] = {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ [Op.like]: `%${textArray[1]}%` },
|
{ [Op.like]: `%${textArray[1]}%` },
|
||||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
{ [Op.like]: `%${encodeURI(textArray[1])}%` },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -237,7 +234,7 @@ export default class CronService {
|
|||||||
const reg = {
|
const reg = {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ [Op.like]: `%${searchText}%` },
|
{ [Op.like]: `%${searchText}%` },
|
||||||
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
{ [Op.like]: `%${encodeURI(searchText)}%` },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
q[Op.or] = [
|
q[Op.or] = [
|
||||||
@@ -270,7 +267,7 @@ export default class CronService {
|
|||||||
let q: any = {};
|
let q: any = {};
|
||||||
if (!filterQuery[key]) continue;
|
if (!filterQuery[key]) continue;
|
||||||
if (key === 'status') {
|
if (key === 'status') {
|
||||||
if (filterQuery[key].includes(2)) {
|
if (filterQuery[key].includes(CrontabStatus.disabled)) {
|
||||||
q = { [Op.or]: [{ [key]: filterQuery[key] }, { isDisabled: 1 }] };
|
q = { [Op.or]: [{ [key]: filterQuery[key] }, { isDisabled: 1 }] };
|
||||||
} else {
|
} else {
|
||||||
q = { [Op.and]: [{ [key]: filterQuery[key] }, { isDisabled: 0 }] };
|
q = { [Op.and]: [{ [key]: filterQuery[key] }, { isDisabled: 0 }] };
|
||||||
@@ -365,10 +362,9 @@ export default class CronService {
|
|||||||
{ status: CrontabStatus.queued },
|
{ status: CrontabStatus.queued },
|
||||||
{ where: { id: ids } },
|
{ where: { id: ids } },
|
||||||
);
|
);
|
||||||
concurrentRun(
|
ids.forEach((id) => {
|
||||||
ids.map((id) => async () => await this.runSingle(id)),
|
this.runSingle(id);
|
||||||
10,
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(ids: number[]) {
|
public async stop(ids: number[]) {
|
||||||
@@ -390,6 +386,7 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async runSingle(cronId: number): Promise<number> {
|
private async runSingle(cronId: number): Promise<number> {
|
||||||
|
return taskLimit.runWithCpuLimit(() => {
|
||||||
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 });
|
||||||
if (cron.status !== CrontabStatus.queued) {
|
if (cron.status !== CrontabStatus.queued) {
|
||||||
@@ -449,6 +446,7 @@ export default class CronService {
|
|||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disabled(ids: number[]) {
|
public async disabled(ids: number[]) {
|
||||||
|
|||||||
+23
-29
@@ -9,18 +9,19 @@ import {
|
|||||||
unInstallDependenceCommandTypes,
|
unInstallDependenceCommandTypes,
|
||||||
DependenceModel,
|
DependenceModel,
|
||||||
} from '../data/dependence';
|
} from '../data/dependence';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import { FindOptions, Op } from 'sequelize';
|
import { FindOptions, Op } from 'sequelize';
|
||||||
import { concurrentRun } from '../config/util';
|
import { concurrentRun } from '../config/util';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import taskLimit from '../shared/pLimit';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class DependenceService {
|
export default class DependenceService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
public async create(payloads: Dependence[]): Promise<Dependence[]> {
|
public async create(payloads: Dependence[]): Promise<Dependence[]> {
|
||||||
const tabs = payloads.map((x) => {
|
const tabs = payloads.map((x) => {
|
||||||
@@ -78,7 +79,7 @@ export default class DependenceService {
|
|||||||
): Promise<Dependence[]> {
|
): Promise<Dependence[]> {
|
||||||
let condition = { ...query, type: DependenceTypes[type as any] };
|
let condition = { ...query, type: DependenceTypes[type as any] };
|
||||||
if (searchValue) {
|
if (searchValue) {
|
||||||
const encodeText = encodeURIComponent(searchValue);
|
const encodeText = encodeURI(searchValue);
|
||||||
const reg = {
|
const reg = {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ [Op.like]: `%${searchValue}%` },
|
{ [Op.like]: `%${searchValue}%` },
|
||||||
@@ -104,20 +105,9 @@ export default class DependenceService {
|
|||||||
isInstall: boolean = true,
|
isInstall: boolean = true,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
) {
|
) {
|
||||||
concurrentRun(
|
docs.forEach((dep) => {
|
||||||
docs.map((dep) => async () => {
|
this.installOrUninstallDependency(dep, isInstall, force);
|
||||||
const status = isInstall
|
});
|
||||||
? DependenceStatus.installing
|
|
||||||
: DependenceStatus.removing;
|
|
||||||
await DependenceModel.update({ status }, { where: { id: dep.id } });
|
|
||||||
return await this.installOrUninstallDependencies(
|
|
||||||
[dep],
|
|
||||||
isInstall,
|
|
||||||
force,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reInstall(ids: number[]): Promise<Dependence[]> {
|
public async reInstall(ids: number[]): Promise<Dependence[]> {
|
||||||
@@ -152,30 +142,32 @@ export default class DependenceService {
|
|||||||
await DependenceModel.update({ log: newLog }, { where: { id: ids } });
|
await DependenceModel.update({ log: newLog }, { where: { id: ids } });
|
||||||
}
|
}
|
||||||
|
|
||||||
public installOrUninstallDependencies(
|
public installOrUninstallDependency(
|
||||||
dependencies: Dependence[],
|
dependency: Dependence,
|
||||||
isInstall: boolean = true,
|
isInstall: boolean = true,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
) {
|
) {
|
||||||
|
return taskLimit.runOneByOne(() => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
if (dependencies.length === 0) {
|
const depIds = [dependency.id!];
|
||||||
resolve(null);
|
const status = isInstall
|
||||||
return;
|
? DependenceStatus.installing
|
||||||
}
|
: DependenceStatus.removing;
|
||||||
|
await DependenceModel.update({ status }, { where: { id: depIds } });
|
||||||
|
|
||||||
const socketMessageType = !force
|
const socketMessageType = !force
|
||||||
? 'installDependence'
|
? 'installDependence'
|
||||||
: 'uninstallDependence';
|
: 'uninstallDependence';
|
||||||
const depNames = dependencies.map((x) => x.name).join(' ');
|
const depName = dependency.name;
|
||||||
const depRunCommand = (
|
const depRunCommand = (
|
||||||
isInstall
|
isInstall
|
||||||
? InstallDependenceCommandTypes
|
? InstallDependenceCommandTypes
|
||||||
: unInstallDependenceCommandTypes
|
: unInstallDependenceCommandTypes
|
||||||
)[dependencies[0].type as any];
|
)[dependency.type as any];
|
||||||
const actionText = isInstall ? '安装' : '删除';
|
const actionText = isInstall ? '安装' : '删除';
|
||||||
const depIds = dependencies.map((x) => x.id) as number[];
|
|
||||||
const startTime = dayjs();
|
const startTime = dayjs();
|
||||||
|
|
||||||
const message = `开始${actionText}依赖 ${depNames},开始时间 ${startTime.format(
|
const message = `开始${actionText}依赖 ${depName},开始时间 ${startTime.format(
|
||||||
'YYYY-MM-DD HH:mm:ss',
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
)}\n\n`;
|
)}\n\n`;
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
@@ -185,7 +177,9 @@ export default class DependenceService {
|
|||||||
});
|
});
|
||||||
await this.updateLog(depIds, message);
|
await this.updateLog(depIds, message);
|
||||||
|
|
||||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
const cp = spawn(`${depRunCommand} ${depName}`, {
|
||||||
|
shell: '/bin/bash',
|
||||||
|
});
|
||||||
|
|
||||||
cp.stdout.on('data', async (data) => {
|
cp.stdout.on('data', async (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
@@ -212,7 +206,6 @@ export default class DependenceService {
|
|||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
await this.updateLog(depIds, JSON.stringify(err));
|
await this.updateLog(depIds, JSON.stringify(err));
|
||||||
resolve(null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('close', async (code) => {
|
cp.on('close', async (code) => {
|
||||||
@@ -250,5 +243,6 @@ export default class DependenceService {
|
|||||||
resolve(null);
|
resolve(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default class EnvService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async update(payload: Env): Promise<Env> {
|
public async update(payload: Env): Promise<Env> {
|
||||||
const doc = await this.getDb({ id: payload.id })
|
const doc = await this.getDb({ id: payload.id });
|
||||||
const tab = new Env({ ...doc, ...payload });
|
const tab = new Env({ ...doc, ...payload });
|
||||||
const newDoc = await this.updateDb(tab);
|
const newDoc = await this.updateDb(tab);
|
||||||
await this.set_envs();
|
await this.set_envs();
|
||||||
@@ -121,7 +121,7 @@ export default class EnvService {
|
|||||||
public async envs(searchText: string = '', query: any = {}): Promise<Env[]> {
|
public async envs(searchText: string = '', query: any = {}): Promise<Env[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const encodeText = encodeURIComponent(searchText);
|
const encodeText = encodeURI(searchText);
|
||||||
const reg = {
|
const reg = {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ [Op.like]: `%${searchText}%` },
|
{ [Op.like]: `%${searchText}%` },
|
||||||
@@ -146,7 +146,6 @@ export default class EnvService {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await this.find(condition, [
|
const result = await this.find(condition, [
|
||||||
['status', 'ASC'],
|
|
||||||
['position', 'DESC'],
|
['position', 'DESC'],
|
||||||
['createdAt', 'ASC'],
|
['createdAt', 'ASC'],
|
||||||
]);
|
]);
|
||||||
|
|||||||
+164
-29
@@ -36,7 +36,7 @@ export default class NotificationService {
|
|||||||
private content = '';
|
private content = '';
|
||||||
private params!: Omit<NotificationInfo, 'type'>;
|
private params!: Omit<NotificationInfo, 'type'>;
|
||||||
private gotOption = {
|
private gotOption = {
|
||||||
timeout: 30000,
|
timeout: 10000,
|
||||||
retry: 1,
|
retry: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,7 +78,8 @@ export default class NotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async gotify() {
|
private async gotify() {
|
||||||
const { gotifyUrl, gotifyToken, gotifyPriority } = this.params;
|
const { gotifyUrl, gotifyToken, gotifyPriority = 1 } = this.params;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
|
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -92,11 +93,19 @@ export default class NotificationService {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return typeof res.id === 'number';
|
if (typeof res.id === 'number') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async goCqHttpBot() {
|
private async goCqHttpBot() {
|
||||||
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(`${goCqHttpBotUrl}?${goCqHttpBotQq}`, {
|
.post(`${goCqHttpBotUrl}?${goCqHttpBotQq}`, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -104,7 +113,14 @@ export default class NotificationService {
|
|||||||
headers: { Authorization: 'Bearer ' + goCqHttpBotToken },
|
headers: { Authorization: 'Bearer ' + goCqHttpBotToken },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return res.retcode === 0;
|
if (res.retcode === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async serverChan() {
|
private async serverChan() {
|
||||||
@@ -112,6 +128,7 @@ export default class NotificationService {
|
|||||||
const url = serverChanKey.startsWith('SCT')
|
const url = serverChanKey.startsWith('SCT')
|
||||||
? `https://sctapi.ftqq.com/${serverChanKey}.send`
|
? `https://sctapi.ftqq.com/${serverChanKey}.send`
|
||||||
: `https://sc.ftqq.com/${serverChanKey}.send`;
|
: `https://sc.ftqq.com/${serverChanKey}.send`;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -119,12 +136,20 @@ export default class NotificationService {
|
|||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return res.errno === 0 || res.data.errno === 0;
|
if (res.errno === 0 || res.data.errno === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pushDeer() {
|
private async pushDeer() {
|
||||||
const { pushDeerKey, pushDeerUrl } = this.params;
|
const { pushDeerKey, pushDeerUrl } = this.params;
|
||||||
const url = pushDeerUrl || `https://api2.pushdeer.com/message/push`;
|
const url = pushDeerUrl || `https://api2.pushdeer.com/message/push`;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -134,14 +159,23 @@ export default class NotificationService {
|
|||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return (
|
if (
|
||||||
res.content.result.length !== undefined && res.content.result.length > 0
|
res.content.result.length !== undefined &&
|
||||||
);
|
res.content.result.length > 0
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async chat() {
|
private async chat() {
|
||||||
const { chatUrl, chatToken } = this.params;
|
const { chatUrl, chatToken } = this.params;
|
||||||
const url = `${chatUrl}${chatToken}`;
|
const url = `${chatUrl}${chatToken}`;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -149,12 +183,19 @@ export default class NotificationService {
|
|||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return res.success;
|
if (res.success) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async bark() {
|
private async bark() {
|
||||||
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
||||||
if (!barkPush.startsWith('http') && !barkPush.startsWith('https')) {
|
if (!barkPush.startsWith('http')) {
|
||||||
barkPush = `https://api.day.app/${barkPush}`;
|
barkPush = `https://api.day.app/${barkPush}`;
|
||||||
}
|
}
|
||||||
const url = `${barkPush}/${encodeURIComponent(
|
const url = `${barkPush}/${encodeURIComponent(
|
||||||
@@ -162,13 +203,22 @@ export default class NotificationService {
|
|||||||
)}/${encodeURIComponent(
|
)}/${encodeURIComponent(
|
||||||
this.content,
|
this.content,
|
||||||
)}?icon=${barkIcon}&sound=${barkSound}&group=${barkGroup}`;
|
)}?icon=${barkIcon}&sound=${barkSound}&group=${barkGroup}`;
|
||||||
|
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.get(url, {
|
.get(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return res.code === 200;
|
if (res.code === 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async telegramBot() {
|
private async telegramBot() {
|
||||||
@@ -200,6 +250,7 @@ export default class NotificationService {
|
|||||||
https: httpsAgent,
|
https: httpsAgent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -208,7 +259,14 @@ export default class NotificationService {
|
|||||||
agent,
|
agent,
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return !!res.ok;
|
if (res.ok) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async dingtalkBot() {
|
private async dingtalkBot() {
|
||||||
@@ -222,6 +280,7 @@ export default class NotificationService {
|
|||||||
secretParam = `×tamp=${dateNow}&sign=${result}`;
|
secretParam = `×tamp=${dateNow}&sign=${result}`;
|
||||||
}
|
}
|
||||||
const url = `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkBotToken}${secretParam}`;
|
const url = `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkBotToken}${secretParam}`;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -233,12 +292,20 @@ export default class NotificationService {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return res.errcode === 0;
|
if (res.errcode === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async weWorkBot() {
|
private async weWorkBot() {
|
||||||
const { weWorkBotKey } = this.params;
|
const { weWorkBotKey, weWorkOrigin = 'https://qyapi.weixin.qq.com' } = this.params;
|
||||||
const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weWorkBotKey}`;
|
const url = `${weWorkOrigin}/cgi-bin/webhook/send?key=${weWorkBotKey}`;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -250,14 +317,21 @@ export default class NotificationService {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return res.errcode === 0;
|
if (res.errcode === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async weWorkApp() {
|
private async weWorkApp() {
|
||||||
const { weWorkAppKey } = this.params;
|
const { weWorkAppKey, weWorkOrigin = 'https://qyapi.weixin.qq.com' } = this.params;
|
||||||
const [corpid, corpsecret, touser, agentid, thumb_media_id = '1'] =
|
const [corpid, corpsecret, touser, agentid, thumb_media_id = '1'] =
|
||||||
weWorkAppKey.split(',');
|
weWorkAppKey.split(',');
|
||||||
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
|
const url = `${weWorkOrigin}/cgi-bin/gettoken`;
|
||||||
const tokenRes: any = await got
|
const tokenRes: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -306,9 +380,10 @@ export default class NotificationService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(
|
.post(
|
||||||
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
|
`${weWorkOrigin}/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
|
||||||
{
|
{
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
json: {
|
json: {
|
||||||
@@ -321,7 +396,14 @@ export default class NotificationService {
|
|||||||
)
|
)
|
||||||
.json();
|
.json();
|
||||||
|
|
||||||
return res.errcode === 0;
|
if (res.errcode === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async aibotk() {
|
private async aibotk() {
|
||||||
@@ -353,6 +435,7 @@ export default class NotificationService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -361,13 +444,20 @@ export default class NotificationService {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
|
if (res.code === 0) {
|
||||||
return res.code === 0;
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async iGot() {
|
private async iGot() {
|
||||||
const { iGotPushKey } = this.params;
|
const { iGotPushKey } = this.params;
|
||||||
const url = `https://push.hellyw.com/${iGotPushKey.toLowerCase()}`;
|
const url = `https://push.hellyw.com/${iGotPushKey.toLowerCase()}`;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -376,12 +466,20 @@ export default class NotificationService {
|
|||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
|
|
||||||
return res.ret === 0;
|
if (res.ret === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pushPlus() {
|
private async pushPlus() {
|
||||||
const { pushPlusToken, pushPlusUser } = this.params;
|
const { pushPlusToken, pushPlusUser } = this.params;
|
||||||
const url = `https://www.pushplus.plus/send`;
|
const url = `https://www.pushplus.plus/send`;
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
@@ -394,13 +492,26 @@ export default class NotificationService {
|
|||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
|
|
||||||
return res.code === 200;
|
if (res.code === 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async lark() {
|
private async lark() {
|
||||||
const { larkKey } = this.params;
|
let { larkKey } = this.params;
|
||||||
|
|
||||||
|
if (!larkKey.startsWith('http')) {
|
||||||
|
larkKey = `https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(`https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`, {
|
.post(larkKey, {
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
json: {
|
json: {
|
||||||
msg_type: 'text',
|
msg_type: 'text',
|
||||||
@@ -409,11 +520,20 @@ export default class NotificationService {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
return res.StatusCode === 0;
|
if (res.StatusCode === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async email() {
|
private async email() {
|
||||||
const { emailPass, emailService, emailUser } = this.params;
|
const { emailPass, emailService, emailUser } = this.params;
|
||||||
|
|
||||||
|
try {
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
service: emailService,
|
service: emailService,
|
||||||
auth: {
|
auth: {
|
||||||
@@ -431,7 +551,14 @@ export default class NotificationService {
|
|||||||
|
|
||||||
transporter.close();
|
transporter.close();
|
||||||
|
|
||||||
return !!info.messageId;
|
if (info.messageId) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(info));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async webhook() {
|
private async webhook() {
|
||||||
@@ -460,8 +587,16 @@ export default class NotificationService {
|
|||||||
allowGetBody: true,
|
allowGetBody: true,
|
||||||
...bodyParam,
|
...bodyParam,
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
const res = await got(formatUrl, options);
|
const res = await got(formatUrl, options);
|
||||||
return String(res.statusCode).startsWith('20');
|
if (String(res.statusCode).startsWith('20')) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(JSON.stringify(res));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response ? error.response.body : error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatBody(contentType: string, body: any): object {
|
private formatBody(contentType: string, body: any): object {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export default class OpenService {
|
|||||||
): Promise<App[]> {
|
): Promise<App[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const encodeText = encodeURIComponent(searchText);
|
const encodeText = encodeURI(searchText);
|
||||||
const reg = {
|
const reg = {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ [Op.like]: `%${searchText}%` },
|
{ [Op.like]: `%${searchText}%` },
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import nodeSchedule from 'node-schedule';
|
import nodeSchedule from 'node-schedule';
|
||||||
import { ChildProcessWithoutNullStreams, exec, spawn } from 'child_process';
|
import { ChildProcessWithoutNullStreams } from 'child_process';
|
||||||
import {
|
import {
|
||||||
ToadScheduler,
|
ToadScheduler,
|
||||||
LongIntervalJob,
|
LongIntervalJob,
|
||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
Task,
|
Task,
|
||||||
} from 'toad-scheduler';
|
} from 'toad-scheduler';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import taskLimit from '../shared/pLimit';
|
||||||
|
import { spawn } from 'cross-spawn';
|
||||||
|
|
||||||
interface ScheduleTaskType {
|
interface ScheduleTaskType {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -40,13 +42,14 @@ export default class ScheduleService {
|
|||||||
|
|
||||||
private maxBuffer = 200 * 1024 * 1024;
|
private maxBuffer = 200 * 1024 * 1024;
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) { }
|
||||||
|
|
||||||
async runTask(
|
async runTask(
|
||||||
command: string,
|
command: string,
|
||||||
callbacks: TaskCallbacks = {},
|
callbacks: TaskCallbacks = {},
|
||||||
completionTime: 'start' | 'end' = 'end',
|
completionTime: 'start' | 'end' = 'end',
|
||||||
) {
|
) {
|
||||||
|
return taskLimit.runWithCpuLimit(() => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const startTime = dayjs();
|
const startTime = dayjs();
|
||||||
@@ -54,7 +57,6 @@ export default class ScheduleService {
|
|||||||
|
|
||||||
const cp = spawn(command, { shell: '/bin/bash' });
|
const cp = spawn(command, { shell: '/bin/bash' });
|
||||||
|
|
||||||
// TODO:
|
|
||||||
callbacks.onStart?.(cp, startTime);
|
callbacks.onStart?.(cp, startTime);
|
||||||
completionTime === 'start' && resolve(cp.pid);
|
completionTime === 'start' && resolve(cp.pid);
|
||||||
|
|
||||||
@@ -107,6 +109,7 @@ export default class ScheduleService {
|
|||||||
await callbacks.onError?.(JSON.stringify(error));
|
await callbacks.onError?.(JSON.stringify(error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async createCronTask(
|
async createCronTask(
|
||||||
@@ -126,12 +129,12 @@ export default class ScheduleService {
|
|||||||
this.scheduleStacks.set(
|
this.scheduleStacks.set(
|
||||||
_id,
|
_id,
|
||||||
nodeSchedule.scheduleJob(_id, schedule, async () => {
|
nodeSchedule.scheduleJob(_id, schedule, async () => {
|
||||||
await this.runTask(command, callbacks);
|
this.runTask(command, callbacks);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (runImmediately) {
|
if (runImmediately) {
|
||||||
await this.runTask(command, callbacks);
|
this.runTask(command, callbacks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ export default class SshKeyService {
|
|||||||
if (host === 'github.com') {
|
if (host === 'github.com') {
|
||||||
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa\n PubkeyAcceptedAlgorithms +ssh-rsa`;
|
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa\n PubkeyAcceptedAlgorithms +ssh-rsa`;
|
||||||
}
|
}
|
||||||
const proxyStr = proxy ? ` ProxyCommand nc -v -x ${proxy} %h %p\n` : '';
|
const proxyStr = proxy
|
||||||
|
? ` ProxyCommand nc -v -x ${proxy} %h %p 2>/dev/null\n`
|
||||||
|
: '';
|
||||||
const config = `Host ${alias}\n Hostname ${host}\n IdentityFile ${path.join(
|
const config = `Host ${alias}\n Hostname ${host}\n IdentityFile ${path.join(
|
||||||
this.sshPath,
|
this.sshPath,
|
||||||
alias,
|
alias,
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import {
|
|||||||
SubscriptionModel,
|
SubscriptionModel,
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
} from '../data/subscription';
|
} from '../data/subscription';
|
||||||
import {
|
import { ChildProcessWithoutNullStreams } from 'child_process';
|
||||||
ChildProcessWithoutNullStreams,
|
|
||||||
exec,
|
|
||||||
execSync,
|
|
||||||
spawn,
|
|
||||||
} from 'child_process';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import {
|
import {
|
||||||
getFileContentByName,
|
getFileContentByName,
|
||||||
@@ -19,6 +14,8 @@ import {
|
|||||||
fileExist,
|
fileExist,
|
||||||
createFile,
|
createFile,
|
||||||
killTask,
|
killTask,
|
||||||
|
handleLogPath,
|
||||||
|
promiseExec,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import { promises, existsSync } from 'fs';
|
import { promises, existsSync } from 'fs';
|
||||||
import { FindOptions, Op } from 'sequelize';
|
import { FindOptions, Op } from 'sequelize';
|
||||||
@@ -46,7 +43,7 @@ export default class SubscriptionService {
|
|||||||
const reg = {
|
const reg = {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ [Op.like]: `%${searchText}%` },
|
{ [Op.like]: `%${searchText}%` },
|
||||||
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
{ [Op.like]: `%${encodeURI(searchText)}%` },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
query = {
|
query = {
|
||||||
@@ -109,30 +106,6 @@ export default class SubscriptionService {
|
|||||||
this.sshKeyService.setSshConfig(docs);
|
this.sshKeyService.setSshConfig(docs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async promiseExec(command: string): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
exec(
|
|
||||||
command,
|
|
||||||
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
|
|
||||||
(err, stdout, stderr) => {
|
|
||||||
resolve(stdout || stderr || JSON.stringify(err));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleLogPath(
|
|
||||||
logPath: string,
|
|
||||||
data: string = '',
|
|
||||||
): Promise<string> {
|
|
||||||
const absolutePath = path.resolve(config.logPath, logPath);
|
|
||||||
const logFileExist = await fileExist(absolutePath);
|
|
||||||
if (!logFileExist) {
|
|
||||||
await createFile(absolutePath, data);
|
|
||||||
}
|
|
||||||
return absolutePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private taskCallbacks(doc: Subscription): TaskCallbacks {
|
private taskCallbacks(doc: Subscription): TaskCallbacks {
|
||||||
return {
|
return {
|
||||||
onBefore: async (startTime) => {
|
onBefore: async (startTime) => {
|
||||||
@@ -145,7 +118,7 @@ export default class SubscriptionService {
|
|||||||
},
|
},
|
||||||
{ where: { id: doc.id } },
|
{ where: { id: doc.id } },
|
||||||
);
|
);
|
||||||
const absolutePath = await this.handleLogPath(
|
const absolutePath = await handleLogPath(
|
||||||
logPath as string,
|
logPath as string,
|
||||||
`## 开始执行... ${startTime.format('YYYY-MM-DD HH:mm:ss')}\n`,
|
`## 开始执行... ${startTime.format('YYYY-MM-DD HH:mm:ss')}\n`,
|
||||||
);
|
);
|
||||||
@@ -155,7 +128,7 @@ export default class SubscriptionService {
|
|||||||
try {
|
try {
|
||||||
if (doc.sub_before) {
|
if (doc.sub_before) {
|
||||||
fs.appendFileSync(absolutePath, `\n## 执行before命令...\n\n`);
|
fs.appendFileSync(absolutePath, `\n## 执行before命令...\n\n`);
|
||||||
beforeStr = await this.promiseExec(doc.sub_before);
|
beforeStr = await promiseExec(doc.sub_before);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
beforeStr =
|
beforeStr =
|
||||||
@@ -175,14 +148,14 @@ export default class SubscriptionService {
|
|||||||
},
|
},
|
||||||
onEnd: async (cp, endTime, diff) => {
|
onEnd: async (cp, endTime, diff) => {
|
||||||
const sub = await this.getDb({ id: doc.id });
|
const sub = await this.getDb({ id: doc.id });
|
||||||
const absolutePath = await this.handleLogPath(sub.log_path as string);
|
const absolutePath = await handleLogPath(sub.log_path as string);
|
||||||
|
|
||||||
// 执行 sub_after
|
// 执行 sub_after
|
||||||
let afterStr = '';
|
let afterStr = '';
|
||||||
try {
|
try {
|
||||||
if (sub.sub_after) {
|
if (sub.sub_after) {
|
||||||
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n\n`);
|
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n\n`);
|
||||||
afterStr = await this.promiseExec(sub.sub_after);
|
afterStr = await promiseExec(sub.sub_after);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
afterStr =
|
afterStr =
|
||||||
@@ -212,12 +185,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 this.handleLogPath(sub.log_path as string);
|
const absolutePath = await handleLogPath(sub.log_path as string);
|
||||||
fs.appendFileSync(absolutePath, `\n${message}`);
|
fs.appendFileSync(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 this.handleLogPath(sub.log_path as string);
|
const absolutePath = await handleLogPath(sub.log_path as string);
|
||||||
fs.appendFileSync(absolutePath, `\n${message}`);
|
fs.appendFileSync(absolutePath, `\n${message}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -236,7 +209,7 @@ export default class SubscriptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async update(payload: Subscription): Promise<Subscription> {
|
public async update(payload: Subscription): Promise<Subscription> {
|
||||||
const doc = await this.getDb({ id: payload.id })
|
const doc = await this.getDb({ id: payload.id });
|
||||||
const tab = new Subscription({ ...doc, ...payload });
|
const tab = new Subscription({ ...doc, ...payload });
|
||||||
const newDoc = await this.updateDb(tab);
|
const newDoc = await this.updateDb(tab);
|
||||||
await this.handleTask(newDoc, !newDoc.is_disabled);
|
await this.handleTask(newDoc, !newDoc.is_disabled);
|
||||||
@@ -289,7 +262,9 @@ export default class SubscriptionService {
|
|||||||
await this.setSshConfig();
|
await this.setSshConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDb(query: FindOptions<Subscription>['where']): Promise<Subscription> {
|
public async getDb(
|
||||||
|
query: FindOptions<Subscription>['where'],
|
||||||
|
): Promise<Subscription> {
|
||||||
const doc: any = await SubscriptionModel.findOne({ where: { ...query } });
|
const doc: any = await SubscriptionModel.findOne({ where: { ...query } });
|
||||||
return doc && (doc.get({ plain: true }) as Subscription);
|
return doc && (doc.get({ plain: true }) as Subscription);
|
||||||
}
|
}
|
||||||
@@ -299,10 +274,9 @@ export default class SubscriptionService {
|
|||||||
{ status: SubscriptionStatus.queued },
|
{ status: SubscriptionStatus.queued },
|
||||||
{ where: { id: ids } },
|
{ where: { id: ids } },
|
||||||
);
|
);
|
||||||
concurrentRun(
|
ids.forEach((id) => {
|
||||||
ids.map((id) => async () => await this.runSingle(id)),
|
this.runSingle(id);
|
||||||
10,
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(ids: number[]) {
|
public async stop(ids: number[]) {
|
||||||
@@ -315,7 +289,7 @@ export default class SubscriptionService {
|
|||||||
this.logger.silly(error);
|
this.logger.silly(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const absolutePath = await this.handleLogPath(doc.log_path as string);
|
const absolutePath = await handleLogPath(doc.log_path as string);
|
||||||
|
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(
|
||||||
`${absolutePath}`,
|
`${absolutePath}`,
|
||||||
@@ -339,10 +313,7 @@ export default class SubscriptionService {
|
|||||||
|
|
||||||
const command = formatCommand(subscription);
|
const command = formatCommand(subscription);
|
||||||
|
|
||||||
await this.scheduleService.runTask(
|
this.scheduleService.runTask(command, this.taskCallbacks(subscription));
|
||||||
command,
|
|
||||||
this.taskCallbacks(subscription),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disabled(ids: number[]) {
|
public async disabled(ids: number[]) {
|
||||||
@@ -369,7 +340,7 @@ export default class SubscriptionService {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const absolutePath = await this.handleLogPath(doc.log_path as string);
|
const absolutePath = await handleLogPath(doc.log_path as string);
|
||||||
return getFileContentByName(absolutePath);
|
return getFileContentByName(absolutePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+61
-14
@@ -2,14 +2,21 @@ 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';
|
||||||
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
import { AuthDataType, AuthInfo, AuthModel, AuthModelInfo } from '../data/auth';
|
||||||
import { NotificationInfo } from '../data/notify';
|
import { NotificationInfo } from '../data/notify';
|
||||||
import NotificationService from './notify';
|
import NotificationService from './notify';
|
||||||
import ScheduleService from './schedule';
|
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import { parseContentVersion, parseVersion } from '../config/util';
|
import {
|
||||||
|
getPid,
|
||||||
|
killTask,
|
||||||
|
parseContentVersion,
|
||||||
|
parseVersion,
|
||||||
|
} from '../config/util';
|
||||||
|
import { TASK_COMMAND } from '../config/const';
|
||||||
|
import taskLimit from '../shared/pLimit'
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SystemService {
|
export default class SystemService {
|
||||||
@@ -22,8 +29,8 @@ export default class SystemService {
|
|||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getLogRemoveFrequency() {
|
public async getSystemConfig() {
|
||||||
const doc = await this.getDb({ type: AuthDataType.removeLogFrequency });
|
const doc = await this.getDb({ type: AuthDataType.systemConfig });
|
||||||
return doc || {};
|
return doc || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,25 +63,30 @@ export default class SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateLogRemoveFrequency(frequency: number) {
|
public async updateSystemConfig(info: AuthModelInfo) {
|
||||||
const oDoc = await this.getLogRemoveFrequency();
|
const oDoc = await this.getSystemConfig();
|
||||||
const result = await this.updateAuthDb({
|
const result = await this.updateAuthDb({
|
||||||
...oDoc,
|
...oDoc,
|
||||||
type: AuthDataType.removeLogFrequency,
|
type: AuthDataType.systemConfig,
|
||||||
info: { frequency },
|
info,
|
||||||
});
|
});
|
||||||
|
if (info.logRemoveFrequency) {
|
||||||
const cron = {
|
const cron = {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
name: '删除日志',
|
name: '删除日志',
|
||||||
command: `ql rmlog ${frequency}`,
|
command: `ql rmlog ${info.logRemoveFrequency}`,
|
||||||
};
|
};
|
||||||
await this.scheduleService.cancelIntervalTask(cron);
|
await this.scheduleService.cancelIntervalTask(cron);
|
||||||
if (frequency > 0) {
|
if (info.logRemoveFrequency > 0) {
|
||||||
this.scheduleService.createIntervalTask(cron, {
|
this.scheduleService.createIntervalTask(cron, {
|
||||||
days: frequency,
|
days: info.logRemoveFrequency,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { code: 200, data: { ...cron } };
|
}
|
||||||
|
if (info.cronConcurrency) {
|
||||||
|
await taskLimit.setCustomLimit(info.cronConcurrency);
|
||||||
|
}
|
||||||
|
return { code: 200, data: info };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkUpdate() {
|
public async checkUpdate() {
|
||||||
@@ -170,4 +182,39 @@ export default class SystemService {
|
|||||||
return { code: 400, message: '通知发送失败,请检查系统设置/通知配置' };
|
return { code: 400, message: '通知发送失败,请检查系统设置/通知配置' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async run(
|
||||||
|
{ command, logPath }: { command: string; logPath: string },
|
||||||
|
callback: TaskCallbacks,
|
||||||
|
) {
|
||||||
|
if (!command.startsWith(TASK_COMMAND)) {
|
||||||
|
command = `${TASK_COMMAND} ${command}`;
|
||||||
|
}
|
||||||
|
this.scheduleService.runTask(
|
||||||
|
`real_log_path=${logPath} real_time=true ${command}`,
|
||||||
|
callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop({ command, pid }: { command: string; pid: number }) {
|
||||||
|
if (!pid && !command) {
|
||||||
|
return { code: 400, message: '参数错误' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid) {
|
||||||
|
await killTask(pid);
|
||||||
|
return { code: 200 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!command.startsWith(TASK_COMMAND)) {
|
||||||
|
command = `${TASK_COMMAND} ${command}`;
|
||||||
|
}
|
||||||
|
const _pid = await getPid(command);
|
||||||
|
if (_pid) {
|
||||||
|
await killTask(_pid);
|
||||||
|
return { code: 200 };
|
||||||
|
} else {
|
||||||
|
return { code: 400, message: '任务未找到' };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-20
@@ -1,16 +1,20 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { createRandomString, getNetIp, getPlatform } from '../config/util';
|
import {
|
||||||
|
createRandomString,
|
||||||
|
fileExist,
|
||||||
|
getNetIp,
|
||||||
|
getPlatform,
|
||||||
|
} from '../config/util';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { authenticator } from '@otplib/preset-default';
|
import { authenticator } from '@otplib/preset-default';
|
||||||
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
import { AuthDataType, AuthInfo, AuthModel, AuthModelInfo, LoginStatus } from '../data/auth';
|
||||||
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 { spawn } from 'child_process';
|
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
@@ -23,7 +27,7 @@ export default class UserService {
|
|||||||
@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 login(
|
public async login(
|
||||||
payloads: {
|
payloads: {
|
||||||
@@ -115,8 +119,7 @@ export default class UserService {
|
|||||||
});
|
});
|
||||||
await this.notificationService.notify(
|
await 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.getLoginLog();
|
||||||
@@ -144,8 +147,7 @@ export default class UserService {
|
|||||||
});
|
});
|
||||||
await this.notificationService.notify(
|
await 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.getLoginLog();
|
||||||
@@ -183,12 +185,12 @@ export default class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getLoginLog(): Promise<AuthInfo[]> {
|
public async getLoginLog(): Promise<Array<AuthModelInfo | undefined>> {
|
||||||
const docs = await AuthModel.findAll({
|
const docs = await AuthModel.findAll({
|
||||||
where: { type: AuthDataType.loginLog },
|
where: { type: AuthDataType.loginLog },
|
||||||
});
|
});
|
||||||
if (docs && docs.length > 0) {
|
if (docs && docs.length > 0) {
|
||||||
const result = docs.sort((a, b) => b.info.timestamp - a.info.timestamp);
|
const result = docs.sort((a, b) => b.info!.timestamp! - a.info!.timestamp!);
|
||||||
if (result.length > 100) {
|
if (result.length > 100) {
|
||||||
await AuthModel.destroy({
|
await AuthModel.destroy({
|
||||||
where: { id: result[result.length - 1].id },
|
where: { id: result[result.length - 1].id },
|
||||||
@@ -205,17 +207,16 @@ export default class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initAuthInfo() {
|
private initAuthInfo() {
|
||||||
const newPassword = createRandomString(16, 22);
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
config.authConfigFile,
|
config.authConfigFile,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: newPassword,
|
password: 'admin',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
code: 100,
|
code: 100,
|
||||||
message: '已初始化密码,请前往auth.json查看并重新登录',
|
message: '未找到认证文件,重新初始化',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,13 +241,18 @@ export default class UserService {
|
|||||||
return { code: 200, data: avatar, message: '更新成功' };
|
return { code: 200, data: avatar, message: '更新成功' };
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUserInfo(): Promise<any> {
|
public async getUserInfo(): Promise<any> {
|
||||||
return new Promise((resolve) => {
|
const authFileExist = await fileExist(config.authConfigFile);
|
||||||
fs.readFile(config.authConfigFile, 'utf8', (err, data) => {
|
if (!authFileExist) {
|
||||||
if (err) console.log(err);
|
fs.writeFileSync(
|
||||||
resolve(JSON.parse(data));
|
config.authConfigFile,
|
||||||
});
|
JSON.stringify({
|
||||||
});
|
username: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.getAuthInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public initTwoFactor() {
|
public initTwoFactor() {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import pLimit from "p-limit";
|
||||||
|
import os from 'os';
|
||||||
|
import { AuthDataType, AuthModel } from "../data/auth";
|
||||||
|
|
||||||
|
class TaskLimit {
|
||||||
|
private oneLimit = pLimit(1);
|
||||||
|
private cpuLimit = pLimit(Math.max(os.cpus().length, 4));
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setCustomLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setCustomLimit(limit?: number) {
|
||||||
|
if (limit) {
|
||||||
|
this.cpuLimit = pLimit(limit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const doc = await AuthModel.findOne({ where: { type: AuthDataType.systemConfig } });
|
||||||
|
if (doc?.info?.cronConcurrency) {
|
||||||
|
this.cpuLimit = pLimit(doc?.info?.cronConcurrency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
|
return this.cpuLimit(() => {
|
||||||
|
return fn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public runOneByOne<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
|
return this.oneLimit(() => {
|
||||||
|
return fn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new TaskLimit();
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { spawn } from 'cross-spawn';
|
||||||
|
import taskLimit from "./pLimit";
|
||||||
|
import Logger from '../loaders/logger';
|
||||||
|
|
||||||
|
export function runCron(cmd: string): Promise<number> {
|
||||||
|
return taskLimit.runWithCpuLimit(() => {
|
||||||
|
return new Promise(async (resolve: any) => {
|
||||||
|
Logger.silly('运行命令: ' + cmd);
|
||||||
|
|
||||||
|
const cp = spawn(cmd, { shell: '/bin/bash' });
|
||||||
|
|
||||||
|
cp.stderr.on('data', (data) => {
|
||||||
|
Logger.info(
|
||||||
|
'[执行任务失败] %s,时间:%s, 错误信息:%j',
|
||||||
|
cmd,
|
||||||
|
new Date().toLocaleString(),
|
||||||
|
data.toString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
cp.on('error', (err) => {
|
||||||
|
Logger.error(
|
||||||
|
'[创建任务失败] %s,时间:%s, 错误信息:%j',
|
||||||
|
cmd,
|
||||||
|
new Date().toLocaleString(),
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('close', async (code) => {
|
||||||
|
Logger.info(
|
||||||
|
`[任务退出] ${cmd} 进程id: ${cp.pid} 退出,退出码 ${code}`,
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
+11
-6
@@ -3,9 +3,9 @@ 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 \
|
&& npm i -g pnpm@8.3.1 \
|
||||||
&& cd /tmp/build \
|
&& cd /tmp/build \
|
||||||
&& pnpm install --prod
|
&& pnpm --registry https://registry.npmmirror.com install --prod
|
||||||
|
|
||||||
FROM python:3.10-alpine
|
FROM python:3.10-alpine
|
||||||
|
|
||||||
@@ -29,7 +29,6 @@ RUN set -x \
|
|||||||
&& apk upgrade \
|
&& apk upgrade \
|
||||||
&& apk --no-cache add -f bash \
|
&& apk --no-cache add -f bash \
|
||||||
coreutils \
|
coreutils \
|
||||||
moreutils \
|
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
wget \
|
wget \
|
||||||
@@ -40,6 +39,8 @@ RUN set -x \
|
|||||||
nodejs \
|
nodejs \
|
||||||
jq \
|
jq \
|
||||||
openssh \
|
openssh \
|
||||||
|
procps \
|
||||||
|
netcat-openbsd \
|
||||||
npm \
|
npm \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& apk update \
|
&& apk update \
|
||||||
@@ -48,7 +49,8 @@ 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 \
|
&& npm install -g pnpm@8.3.1 \
|
||||||
|
&& cd && pnpm config set registry https://registry.npmmirror.com \
|
||||||
&& pnpm add -g pm2 tsx \
|
&& 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 \
|
||||||
@@ -56,12 +58,12 @@ RUN set -x \
|
|||||||
&& rm -rf /root/.npm
|
&& rm -rf /root/.npm
|
||||||
|
|
||||||
ARG SOURCE_COMMIT
|
ARG SOURCE_COMMIT
|
||||||
RUN git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||||
&& cd ${QL_DIR} \
|
&& cd ${QL_DIR} \
|
||||||
&& cp -f .env.example .env \
|
&& cp -f .env.example .env \
|
||||||
&& chmod 777 ${QL_DIR}/shell/*.sh \
|
&& chmod 777 ${QL_DIR}/shell/*.sh \
|
||||||
&& chmod 777 ${QL_DIR}/docker/*.sh \
|
&& chmod 777 ${QL_DIR}/docker/*.sh \
|
||||||
&& git clone -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
|
&& git clone --depth=1 -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
|
||||||
&& mkdir -p ${QL_DIR}/static \
|
&& mkdir -p ${QL_DIR}/static \
|
||||||
&& cp -rf /static/* ${QL_DIR}/static \
|
&& cp -rf /static/* ${QL_DIR}/static \
|
||||||
&& rm -rf /static
|
&& rm -rf /static
|
||||||
@@ -70,4 +72,7 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
|
|||||||
|
|
||||||
WORKDIR ${QL_DIR}
|
WORKDIR ${QL_DIR}
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
|
||||||
|
CMD curl -sf http://127.0.0.1:5400/api/health || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
version: '2'
|
version: '2'
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
|
# alpine 基础镜像版本
|
||||||
image: whyour/qinglong:latest
|
image: whyour/qinglong:latest
|
||||||
|
# debian-slim 基础镜像版本
|
||||||
|
# image: whyour/qinglong:debian
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/ql/data
|
- ./data:/ql/data
|
||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:5700:5700"
|
- "0.0.0.0:5700:5700"
|
||||||
|
environment:
|
||||||
|
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
|
||||||
|
QlBaseUrl: '/'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
+14
-17
@@ -10,11 +10,21 @@ echo -e "======================1. 检测配置文件========================\n"
|
|||||||
make_dir /etc/nginx/conf.d
|
make_dir /etc/nginx/conf.d
|
||||||
make_dir /run/nginx
|
make_dir /run/nginx
|
||||||
init_nginx
|
init_nginx
|
||||||
|
fix_config
|
||||||
|
|
||||||
pm2 l &>/dev/null
|
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
|
||||||
update_depend
|
update_depend
|
||||||
echo
|
echo
|
||||||
|
|
||||||
@@ -22,36 +32,23 @@ 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
|
||||||
echo -e "nginx启动成功...\n"
|
echo -e "nginx启动成功...\n"
|
||||||
|
|
||||||
echo -e "======================4. 启动面板监控========================\n"
|
echo -e "======================4. 启动pm2服务========================\n"
|
||||||
pm2 delete public &>/dev/null
|
reload_pm2
|
||||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time
|
|
||||||
echo -e "监控服务启动成功...\n"
|
|
||||||
|
|
||||||
echo -e "======================5. 启动主服务========================\n"
|
|
||||||
pm2 delete panel &>/dev/null
|
|
||||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time
|
|
||||||
echo -e "主服务启动成功...\n"
|
|
||||||
|
|
||||||
echo -e "======================6. 启动定时服务========================\n"
|
|
||||||
pm2 delete schedule &>/dev/null
|
|
||||||
pm2 start $dir_static/build/schedule/index.js -n schedule --source-map-support --time
|
|
||||||
echo -e "定时任务启动成功...\n"
|
|
||||||
|
|
||||||
if [[ $AutoStartBot == true ]]; then
|
if [[ $AutoStartBot == true ]]; then
|
||||||
echo -e "======================7. 启动bot========================\n"
|
echo -e "======================5. 启动bot========================\n"
|
||||||
nohup ql -l bot >$dir_log/bot.log 2>&1 &
|
nohup ql -l 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 "======================8. 执行自定义脚本========================\n"
|
echo -e "====================6. 执行自定义脚本========================\n"
|
||||||
nohup ql -l extra >$dir_log/extra.log 2>&1 &
|
nohup ql -l extra >$dir_log/extra.log 2>&1 &
|
||||||
echo -e "自定义脚本后台执行中...\n"
|
echo -e "自定义脚本后台执行中...\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "############################################################\n"
|
echo -e "############################################################\n"
|
||||||
echo -e "容器启动成功..."
|
echo -e "容器启动成功..."
|
||||||
echo -e "\n请先访问5700端口,登录成功面板之后再执行添加定时任务..."
|
|
||||||
echo -e "############################################################\n"
|
echo -e "############################################################\n"
|
||||||
|
|
||||||
crond -f >/dev/null
|
crond -f >/dev/null
|
||||||
|
|||||||
+11
-7
@@ -14,31 +14,33 @@ map $http_upgrade $connection_upgrade {
|
|||||||
server {
|
server {
|
||||||
listen 5700;
|
listen 5700;
|
||||||
IPV6_CONFIG
|
IPV6_CONFIG
|
||||||
root /ql/static/dist;
|
|
||||||
ssl_session_timeout 5m;
|
ssl_session_timeout 5m;
|
||||||
|
|
||||||
location QL_BASE_URL/api/public/ {
|
location QL_BASE_URLapi/public/ {
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
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/public/;
|
proxy_pass http://publicApi/api/;
|
||||||
|
proxy_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
location QL_BASE_URL/api/ {
|
location QL_BASE_URLapi/ {
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
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_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
location QL_BASE_URL/open/ {
|
location QL_BASE_URLopen/ {
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
@@ -49,10 +51,12 @@ server {
|
|||||||
gzip_comp_level 6;
|
gzip_comp_level 6;
|
||||||
gzip_buffers 16 8k;
|
gzip_buffers 16 8k;
|
||||||
gzip_http_version 1.0;
|
gzip_http_version 1.0;
|
||||||
|
QL_ROOT_CONFIG
|
||||||
|
|
||||||
location QL_BASE_URL/ {
|
location QL_BASE_URL_LOCATION {
|
||||||
|
QL_ALIAS_CONFIG
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
try_files $uri $uri/ QL_BASE_URL/index.html;
|
try_files $uri QL_BASE_URLindex.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ .*\.(html)$ {
|
location ~ .*\.(html)$ {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'schedule',
|
||||||
|
max_restarts: 10,
|
||||||
|
kill_timeout: 15000,
|
||||||
|
wait_ready: true,
|
||||||
|
listen_timeout: 10000,
|
||||||
|
source_map_support: true,
|
||||||
|
time: true,
|
||||||
|
script: 'static/build/schedule/index.js',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'public',
|
||||||
|
max_restarts: 10,
|
||||||
|
kill_timeout: 15000,
|
||||||
|
wait_ready: true,
|
||||||
|
listen_timeout: 10000,
|
||||||
|
source_map_support: true,
|
||||||
|
time: true,
|
||||||
|
script: 'static/build/public.js',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'panel',
|
||||||
|
max_restarts: 10,
|
||||||
|
kill_timeout: 15000,
|
||||||
|
wait_ready: true,
|
||||||
|
listen_timeout: 10000,
|
||||||
|
source_map_support: true,
|
||||||
|
time: true,
|
||||||
|
script: 'static/build/app.js',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
+7
-2
@@ -11,7 +11,7 @@
|
|||||||
"panel": "npm run build:back && node static/build/app.js",
|
"panel": "npm run build:back && node static/build/app.js",
|
||||||
"schedule": "npm run build:back && node static/build/schedule/index.js",
|
"schedule": "npm run build:back && node static/build/schedule/index.js",
|
||||||
"public": "npm run build:back && node static/build/public.js",
|
"public": "npm run build:back && node static/build/public.js",
|
||||||
"gen:proto": "protoc --experimental_allow_proto3_optional --plugin=./node_modules/.bin/protoc-gen-ts_proto ./back/protos/cron.proto --ts_proto_out=./ --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true",
|
"gen:proto": "protoc --experimental_allow_proto3_optional --plugin=./node_modules/.bin/protoc-gen-ts_proto ./back/protos/*.proto --ts_proto_out=./ --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true",
|
||||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||||
"postinstall": "max setup 2>/dev/null || true",
|
"postinstall": "max setup 2>/dev/null || true",
|
||||||
"test": "umi-test",
|
"test": "umi-test",
|
||||||
@@ -64,14 +64,17 @@
|
|||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cron-parser": "^4.2.1",
|
"cron-parser": "^4.2.1",
|
||||||
|
"cross-spawn": "^7.0.3",
|
||||||
"dayjs": "^1.11.2",
|
"dayjs": "^1.11.2",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express": "^4.17.3",
|
"express": "^4.17.3",
|
||||||
"express-jwt": "^6.1.1",
|
"express-jwt": "^6.1.1",
|
||||||
|
"express-rate-limit": "^6.7.0",
|
||||||
"express-urlrewrite": "^1.4.0",
|
"express-urlrewrite": "^1.4.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"hpagent": "^0.1.2",
|
"hpagent": "^0.1.2",
|
||||||
|
"http-proxy-middleware": "^2.0.6",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
@@ -80,13 +83,14 @@
|
|||||||
"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",
|
||||||
|
"p-limit": "3.1.0",
|
||||||
"protobufjs": "^7.2.3",
|
"protobufjs": "^7.2.3",
|
||||||
"pstree.remy": "^1.1.8",
|
"pstree.remy": "^1.1.8",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sequelize": "^6.25.5",
|
"sequelize": "^6.25.5",
|
||||||
"serve-handler": "^6.1.3",
|
"serve-handler": "^6.1.3",
|
||||||
"sockjs": "^0.3.24",
|
"sockjs": "^0.3.24",
|
||||||
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.2",
|
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3",
|
||||||
"toad-scheduler": "^1.6.0",
|
"toad-scheduler": "^1.6.0",
|
||||||
"typedi": "^0.10.0",
|
"typedi": "^0.10.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
@@ -101,6 +105,7 @@
|
|||||||
"@sentry/react": "^7.12.1",
|
"@sentry/react": "^7.12.1",
|
||||||
"@types/body-parser": "^1.19.2",
|
"@types/body-parser": "^1.19.2",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
|
"@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/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
|
|||||||
Generated
+3316
-3082
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 6.8 KiB |
+6
-11
@@ -1,9 +1,3 @@
|
|||||||
## Version: v2.8.0
|
|
||||||
## Date: 2021-06-20
|
|
||||||
## Update Content: 可持续发展纲要\n1. session管理破坏性修改\n2. 配置管理可编辑config下文件\n3. 自定义脚本改为查看脚本\n4. 移除互助相关
|
|
||||||
|
|
||||||
## 上面版本号中,如果第2位数字有变化,那么代表增加了新的参数,如果只有第3位数字有变化,仅代表更新了注释,没有增加新的参数,可更新可不更新
|
|
||||||
|
|
||||||
## 在运行 ql repo 命令时,是否自动删除失效的脚本与定时任务
|
## 在运行 ql repo 命令时,是否自动删除失效的脚本与定时任务
|
||||||
AutoDelCron="true"
|
AutoDelCron="true"
|
||||||
|
|
||||||
@@ -24,11 +18,8 @@ CpuWarn=80
|
|||||||
MemoryWarn=80
|
MemoryWarn=80
|
||||||
DiskWarn=90
|
DiskWarn=90
|
||||||
|
|
||||||
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
## 设置定时任务执行的超时时间,例如1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||||
CommandTimeoutTime="1h"
|
CommandTimeoutTime=""
|
||||||
|
|
||||||
## 设置批量执行任务时的并发数,默认同时执行5个任务
|
|
||||||
MaxConcurrentNum="5"
|
|
||||||
|
|
||||||
## 在运行 task 命令时,随机延迟启动任务的最大延迟时间
|
## 在运行 task 命令时,随机延迟启动任务的最大延迟时间
|
||||||
## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空
|
## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空
|
||||||
@@ -100,6 +91,10 @@ export TG_API_HOST=""
|
|||||||
export DD_BOT_TOKEN=""
|
export DD_BOT_TOKEN=""
|
||||||
export DD_BOT_SECRET=""
|
export DD_BOT_SECRET=""
|
||||||
|
|
||||||
|
## 企业微信反向代理地址
|
||||||
|
## (环境变量名 QYWX_ORIGIN)
|
||||||
|
export QYWX_ORIGIN=""
|
||||||
|
|
||||||
## 5. 企业微信机器人
|
## 5. 企业微信机器人
|
||||||
## 官方说明文档:https://work.weixin.qq.com/api/doc/90000/90136/91770
|
## 官方说明文档:https://work.weixin.qq.com/api/doc/90000/90136/91770
|
||||||
## 下方填写密钥,企业微信推送 webhook 后面的 key
|
## 下方填写密钥,企业微信推送 webhook 后面的 key
|
||||||
|
|||||||
+19
-9
File diff suppressed because one or more lines are too long
+89
-47
@@ -76,6 +76,8 @@ push_config = {
|
|||||||
'QMSG_KEY': '', # qmsg 酱的 QMSG_KEY
|
'QMSG_KEY': '', # qmsg 酱的 QMSG_KEY
|
||||||
'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE
|
'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE
|
||||||
|
|
||||||
|
'QYWX_ORIGIN': '', # 企业微信代理地址
|
||||||
|
|
||||||
'QYWX_AM': '', # 企业微信应用
|
'QYWX_AM': '', # 企业微信应用
|
||||||
|
|
||||||
'QYWX_KEY': '', # 企业微信机器人
|
'QYWX_KEY': '', # 企业微信机器人
|
||||||
@@ -164,8 +166,7 @@ def dingding_bot(title: str, content: str) -> None:
|
|||||||
|
|
||||||
timestamp = str(round(time.time() * 1000))
|
timestamp = str(round(time.time() * 1000))
|
||||||
secret_enc = push_config.get("DD_BOT_SECRET").encode("utf-8")
|
secret_enc = push_config.get("DD_BOT_SECRET").encode("utf-8")
|
||||||
string_to_sign = "{}\n{}".format(
|
string_to_sign = "{}\n{}".format(timestamp, push_config.get("DD_BOT_SECRET"))
|
||||||
timestamp, push_config.get("DD_BOT_SECRET"))
|
|
||||||
string_to_sign_enc = string_to_sign.encode("utf-8")
|
string_to_sign_enc = string_to_sign.encode("utf-8")
|
||||||
hmac_code = hmac.new(
|
hmac_code = hmac.new(
|
||||||
secret_enc, string_to_sign_enc, digestmod=hashlib.sha256
|
secret_enc, string_to_sign_enc, digestmod=hashlib.sha256
|
||||||
@@ -231,8 +232,11 @@ def gotify(title: str, content: str) -> None:
|
|||||||
print("gotify 服务启动")
|
print("gotify 服务启动")
|
||||||
|
|
||||||
url = f'{push_config.get("GOTIFY_URL")}/message?token={push_config.get("GOTIFY_TOKEN")}'
|
url = f'{push_config.get("GOTIFY_URL")}/message?token={push_config.get("GOTIFY_TOKEN")}'
|
||||||
data = {"title": title, "message": content,
|
data = {
|
||||||
"priority": push_config.get("GOTIFY_PRIORITY")}
|
"title": title,
|
||||||
|
"message": content,
|
||||||
|
"priority": push_config.get("GOTIFY_PRIORITY"),
|
||||||
|
}
|
||||||
response = requests.post(url, data=data).json()
|
response = requests.post(url, data=data).json()
|
||||||
|
|
||||||
if response.get("id"):
|
if response.get("id"):
|
||||||
@@ -291,9 +295,13 @@ def pushdeer(title: str, content: str) -> None:
|
|||||||
print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送")
|
print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送")
|
||||||
return
|
return
|
||||||
print("PushDeer 服务启动")
|
print("PushDeer 服务启动")
|
||||||
data = {"text": title, "desp": content, "type": "markdown",
|
data = {
|
||||||
"pushkey": push_config.get("DEER_KEY")}
|
"text": title,
|
||||||
url = 'https://api2.pushdeer.com/message/push'
|
"desp": content,
|
||||||
|
"type": "markdown",
|
||||||
|
"pushkey": push_config.get("DEER_KEY"),
|
||||||
|
}
|
||||||
|
url = "https://api2.pushdeer.com/message/push"
|
||||||
if push_config.get("DEER_URL"):
|
if push_config.get("DEER_URL"):
|
||||||
url = push_config.get("DEER_URL")
|
url = push_config.get("DEER_URL")
|
||||||
|
|
||||||
@@ -313,7 +321,7 @@ def chat(title: str, content: str) -> None:
|
|||||||
print("chat 服务的 CHAT_URL或CHAT_TOKEN 未设置!!\n取消推送")
|
print("chat 服务的 CHAT_URL或CHAT_TOKEN 未设置!!\n取消推送")
|
||||||
return
|
return
|
||||||
print("chat 服务启动")
|
print("chat 服务启动")
|
||||||
data = 'payload=' + json.dumps({'text': title + '\n' + content})
|
data = "payload=" + json.dumps({"text": title + "\n" + content})
|
||||||
url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN")
|
url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN")
|
||||||
response = requests.post(url, data=data)
|
response = requests.post(url, data=data)
|
||||||
|
|
||||||
@@ -347,11 +355,9 @@ def pushplus_bot(title: str, content: str) -> None:
|
|||||||
print("PUSHPLUS 推送成功!")
|
print("PUSHPLUS 推送成功!")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
url_old = "http://pushplus.hxtrip.com/send"
|
url_old = "http://pushplus.hxtrip.com/send"
|
||||||
headers["Accept"] = "application/json"
|
headers["Accept"] = "application/json"
|
||||||
response = requests.post(
|
response = requests.post(url=url_old, data=body, headers=headers).json()
|
||||||
url=url_old, data=body, headers=headers).json()
|
|
||||||
|
|
||||||
if response["code"] == 200:
|
if response["code"] == 200:
|
||||||
print("PUSHPLUS(hxtrip) 推送成功!")
|
print("PUSHPLUS(hxtrip) 推送成功!")
|
||||||
@@ -370,8 +376,7 @@ def qmsg_bot(title: str, content: str) -> None:
|
|||||||
print("qmsg 服务启动")
|
print("qmsg 服务启动")
|
||||||
|
|
||||||
url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}'
|
url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}'
|
||||||
payload = {
|
payload = {"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")}
|
||||||
"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")}
|
|
||||||
response = requests.post(url=url, params=payload).json()
|
response = requests.post(url=url, params=payload).json()
|
||||||
|
|
||||||
if response["code"] == 0:
|
if response["code"] == 0:
|
||||||
@@ -420,9 +425,12 @@ class WeCom:
|
|||||||
self.CORPID = corpid
|
self.CORPID = corpid
|
||||||
self.CORPSECRET = corpsecret
|
self.CORPSECRET = corpsecret
|
||||||
self.AGENTID = agentid
|
self.AGENTID = agentid
|
||||||
|
self.ORIGIN = "https://qyapi.weixin.qq.com"
|
||||||
|
if push_config.get("QYWX_ORIGIN"):
|
||||||
|
self.ORIGIN = push_config.get("QYWX_ORIGIN")
|
||||||
|
|
||||||
def get_access_token(self):
|
def get_access_token(self):
|
||||||
url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
|
url = f"{self.ORIGIN}/cgi-bin/gettoken"
|
||||||
values = {
|
values = {
|
||||||
"corpid": self.CORPID,
|
"corpid": self.CORPID,
|
||||||
"corpsecret": self.CORPSECRET,
|
"corpsecret": self.CORPSECRET,
|
||||||
@@ -432,10 +440,7 @@ class WeCom:
|
|||||||
return data["access_token"]
|
return data["access_token"]
|
||||||
|
|
||||||
def send_text(self, message, touser="@all"):
|
def send_text(self, message, touser="@all"):
|
||||||
send_url = (
|
send_url = f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}"
|
||||||
"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="
|
|
||||||
+ self.get_access_token()
|
|
||||||
)
|
|
||||||
send_values = {
|
send_values = {
|
||||||
"touser": touser,
|
"touser": touser,
|
||||||
"msgtype": "text",
|
"msgtype": "text",
|
||||||
@@ -449,10 +454,7 @@ class WeCom:
|
|||||||
return respone["errmsg"]
|
return respone["errmsg"]
|
||||||
|
|
||||||
def send_mpnews(self, title, message, media_id, touser="@all"):
|
def send_mpnews(self, title, message, media_id, touser="@all"):
|
||||||
send_url = (
|
send_url = f"https://{self.HOST}/cgi-bin/message/send?access_token={self.get_access_token()}"
|
||||||
"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="
|
|
||||||
+ self.get_access_token()
|
|
||||||
)
|
|
||||||
send_values = {
|
send_values = {
|
||||||
"touser": touser,
|
"touser": touser,
|
||||||
"msgtype": "mpnews",
|
"msgtype": "mpnews",
|
||||||
@@ -485,7 +487,11 @@ def wecom_bot(title: str, content: str) -> None:
|
|||||||
return
|
return
|
||||||
print("企业微信机器人服务启动")
|
print("企业微信机器人服务启动")
|
||||||
|
|
||||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={push_config.get('QYWX_KEY')}"
|
origin = "https://qyapi.weixin.qq.com"
|
||||||
|
if push_config.get("QYWX_ORIGIN"):
|
||||||
|
origin = push_config.get("QYWX_ORIGIN")
|
||||||
|
|
||||||
|
url = f"{origin}/cgi-bin/webhook/send?key={push_config.get('QYWX_KEY')}"
|
||||||
headers = {"Content-Type": "application/json;charset=utf-8"}
|
headers = {"Content-Type": "application/json;charset=utf-8"}
|
||||||
data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}}
|
data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}}
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
@@ -547,24 +553,28 @@ def aibotk(title: str, content: str) -> None:
|
|||||||
"""
|
"""
|
||||||
使用 智能微秘书 推送消息。
|
使用 智能微秘书 推送消息。
|
||||||
"""
|
"""
|
||||||
if not push_config.get("AIBOTK_KEY") or not push_config.get("AIBOTK_TYPE") or not push_config.get("AIBOTK_NAME"):
|
if (
|
||||||
|
not push_config.get("AIBOTK_KEY")
|
||||||
|
or not push_config.get("AIBOTK_TYPE")
|
||||||
|
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("智能微秘书 服务启动")
|
||||||
|
|
||||||
if push_config.get("AIBOTK_TYPE") == 'room':
|
if push_config.get("AIBOTK_TYPE") == "room":
|
||||||
url = "https://api-bot.aibotk.com/openapi/v1/chat/room"
|
url = "https://api-bot.aibotk.com/openapi/v1/chat/room"
|
||||||
data = {
|
data = {
|
||||||
"apiKey": push_config.get("AIBOTK_KEY"),
|
"apiKey": push_config.get("AIBOTK_KEY"),
|
||||||
"roomName": push_config.get("AIBOTK_NAME"),
|
"roomName": push_config.get("AIBOTK_NAME"),
|
||||||
"message": {"type": 1, "content": f'【青龙快讯】\n\n{title}\n{content}'}
|
"message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"},
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
url = "https://api-bot.aibotk.com/openapi/v1/chat/contact"
|
url = "https://api-bot.aibotk.com/openapi/v1/chat/contact"
|
||||||
data = {
|
data = {
|
||||||
"apiKey": push_config.get("AIBOTK_KEY"),
|
"apiKey": push_config.get("AIBOTK_KEY"),
|
||||||
"name": push_config.get("AIBOTK_NAME"),
|
"name": push_config.get("AIBOTK_NAME"),
|
||||||
"message": {"type": 1, "content": f'【青龙快讯】\n\n{title}\n{content}'}
|
"message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"},
|
||||||
}
|
}
|
||||||
body = json.dumps(data).encode(encoding="utf-8")
|
body = json.dumps(data).encode(encoding="utf-8")
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
@@ -580,29 +590,52 @@ def smtp(title: str, content: str) -> None:
|
|||||||
"""
|
"""
|
||||||
使用 SMTP 邮件 推送消息。
|
使用 SMTP 邮件 推送消息。
|
||||||
"""
|
"""
|
||||||
if not push_config.get("SMTP_SERVER") or not push_config.get("SMTP_SSL") or not push_config.get("SMTP_EMAIL") or not push_config.get("SMTP_PASSWORD") or not push_config.get("SMTP_NAME"):
|
if (
|
||||||
print("SMTP 邮件 的 SMTP_SERVER 或者 SMTP_SSL 或者 SMTP_EMAIL 或者 SMTP_PASSWORD 或者 SMTP_NAME 未设置!!\n取消推送")
|
not push_config.get("SMTP_SERVER")
|
||||||
|
or not push_config.get("SMTP_SSL")
|
||||||
|
or not push_config.get("SMTP_EMAIL")
|
||||||
|
or not push_config.get("SMTP_PASSWORD")
|
||||||
|
or not push_config.get("SMTP_NAME")
|
||||||
|
):
|
||||||
|
print(
|
||||||
|
"SMTP 邮件 的 SMTP_SERVER 或者 SMTP_SSL 或者 SMTP_EMAIL 或者 SMTP_PASSWORD 或者 SMTP_NAME 未设置!!\n取消推送"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
print("SMTP 邮件 服务启动")
|
print("SMTP 邮件 服务启动")
|
||||||
|
|
||||||
message = MIMEText(content, 'plain', 'utf-8')
|
message = MIMEText(content, "plain", "utf-8")
|
||||||
message['From'] = formataddr((Header(push_config.get(
|
message["From"] = formataddr(
|
||||||
"SMTP_NAME"), 'utf-8').encode(), push_config.get("SMTP_EMAIL")))
|
(
|
||||||
message['To'] = formataddr((Header(push_config.get(
|
Header(push_config.get("SMTP_NAME"), "utf-8").encode(),
|
||||||
"SMTP_NAME"), 'utf-8').encode(), push_config.get("SMTP_EMAIL")))
|
push_config.get("SMTP_EMAIL"),
|
||||||
message['Subject'] = Header(title, 'utf-8')
|
)
|
||||||
|
)
|
||||||
|
message["To"] = formataddr(
|
||||||
|
(
|
||||||
|
Header(push_config.get("SMTP_NAME"), "utf-8").encode(),
|
||||||
|
push_config.get("SMTP_EMAIL"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
message["Subject"] = Header(title, "utf-8")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
smtp_server = smtplib.SMTP_SSL(push_config.get("SMTP_SERVER")) if push_config.get(
|
smtp_server = (
|
||||||
"SMTP_SSL") == 'true' else smtplib.SMTP(push_config.get("SMTP_SERVER"))
|
smtplib.SMTP_SSL(push_config.get("SMTP_SERVER"))
|
||||||
smtp_server.login(push_config.get("SMTP_EMAIL"),
|
if push_config.get("SMTP_SSL") == "true"
|
||||||
push_config.get("SMTP_PASSWORD"))
|
else smtplib.SMTP(push_config.get("SMTP_SERVER"))
|
||||||
smtp_server.sendmail(push_config.get("SMTP_EMAIL"),
|
)
|
||||||
push_config.get("SMTP_EMAIL"), message.as_bytes())
|
smtp_server.login(
|
||||||
|
push_config.get("SMTP_EMAIL"), push_config.get("SMTP_PASSWORD")
|
||||||
|
)
|
||||||
|
smtp_server.sendmail(
|
||||||
|
push_config.get("SMTP_EMAIL"),
|
||||||
|
push_config.get("SMTP_EMAIL"),
|
||||||
|
message.as_bytes(),
|
||||||
|
)
|
||||||
smtp_server.close()
|
smtp_server.close()
|
||||||
print("SMTP 邮件 推送成功!")
|
print("SMTP 邮件 推送成功!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'SMTP 邮件 推送失败!{e}')
|
print(f"SMTP 邮件 推送失败!{e}")
|
||||||
|
|
||||||
|
|
||||||
def one() -> str:
|
def one() -> str:
|
||||||
@@ -645,9 +678,19 @@ if push_config.get("QYWX_KEY"):
|
|||||||
notify_function.append(wecom_bot)
|
notify_function.append(wecom_bot)
|
||||||
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
|
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
|
||||||
notify_function.append(telegram_bot)
|
notify_function.append(telegram_bot)
|
||||||
if push_config.get("AIBOTK_KEY") and push_config.get("AIBOTK_TYPE") and push_config.get("AIBOTK_NAME"):
|
if (
|
||||||
|
push_config.get("AIBOTK_KEY")
|
||||||
|
and push_config.get("AIBOTK_TYPE")
|
||||||
|
and push_config.get("AIBOTK_NAME")
|
||||||
|
):
|
||||||
notify_function.append(aibotk)
|
notify_function.append(aibotk)
|
||||||
if push_config.get("SMTP_SERVER") and push_config.get("SMTP_SSL") and push_config.get("SMTP_EMAIL") and push_config.get("SMTP_PASSWORD") and push_config.get("SMTP_NAME"):
|
if (
|
||||||
|
push_config.get("SMTP_SERVER")
|
||||||
|
and push_config.get("SMTP_SSL")
|
||||||
|
and push_config.get("SMTP_EMAIL")
|
||||||
|
and push_config.get("SMTP_PASSWORD")
|
||||||
|
and push_config.get("SMTP_NAME")
|
||||||
|
):
|
||||||
notify_function.append(smtp)
|
notify_function.append(smtp)
|
||||||
|
|
||||||
|
|
||||||
@@ -659,7 +702,7 @@ def send(title: str, content: str) -> None:
|
|||||||
# 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔
|
# 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔
|
||||||
skipTitle = os.getenv("SKIP_PUSH_TITLE")
|
skipTitle = os.getenv("SKIP_PUSH_TITLE")
|
||||||
if skipTitle:
|
if skipTitle:
|
||||||
if (title in re.split("\n", skipTitle)):
|
if title in re.split("\n", skipTitle):
|
||||||
print(f"{title} 在SKIP_PUSH_TITLE环境变量内,跳过推送!")
|
print(f"{title} 在SKIP_PUSH_TITLE环境变量内,跳过推送!")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -669,8 +712,7 @@ def send(title: str, content: str) -> None:
|
|||||||
content += "\n\n" + text
|
content += "\n\n" + text
|
||||||
|
|
||||||
ts = [
|
ts = [
|
||||||
threading.Thread(target=mode, args=(
|
threading.Thread(target=mode, args=(title, content), name=mode.__name__)
|
||||||
title, content), name=mode.__name__)
|
|
||||||
for mode in notify_function
|
for mode in notify_function
|
||||||
]
|
]
|
||||||
[t.start() for t in ts]
|
[t.start() for t in ts]
|
||||||
|
|||||||
+17
-17
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
create_token() {
|
create_token() {
|
||||||
local token_command="tsx ${dir_root}/back/token.ts"
|
local token_command="tsx ${dir_root}/back/token.ts"
|
||||||
local token_file="${dir_root}static/build/token.js"
|
local token_file="${dir_root}/static/build/token.js"
|
||||||
if [[ -f $token_file ]]; then
|
if [[ -f $token_file ]]; then
|
||||||
token_command="node ${token_file}"
|
token_command="node ${token_file}"
|
||||||
fi
|
fi
|
||||||
@@ -30,10 +30,10 @@ add_cron_api() {
|
|||||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||||
local sub_id=$(echo "$1" | awk -F ":" '{print $4}')
|
local sub_id=$(echo "$1" | awk -F ":" '{print $4}')
|
||||||
else
|
else
|
||||||
local schedule=$1
|
local schedule="$1"
|
||||||
local command=$2
|
local command="$2"
|
||||||
local name=$3
|
local name="$3"
|
||||||
local sub_id=$4
|
local sub_id="$4"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! $sub_id ]];then
|
if [[ ! $sub_id ]];then
|
||||||
@@ -69,10 +69,10 @@ update_cron_api() {
|
|||||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||||
local id=$(echo "$1" | awk -F ":" '{print $4}')
|
local id=$(echo "$1" | awk -F ":" '{print $4}')
|
||||||
else
|
else
|
||||||
local schedule=$1
|
local schedule="$1"
|
||||||
local command=$2
|
local command="$2"
|
||||||
local name=$3
|
local name="$3"
|
||||||
local id=$4
|
local id="$4"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
@@ -103,8 +103,8 @@ update_cron_command_api() {
|
|||||||
local command=$(echo "$1" | awk -F ":" '{print $1}')
|
local command=$(echo "$1" | awk -F ":" '{print $1}')
|
||||||
local id=$(echo "$1" | awk -F ":" '{print $2}')
|
local id=$(echo "$1" | awk -F ":" '{print $2}')
|
||||||
else
|
else
|
||||||
local command=$1
|
local command="$1"
|
||||||
local id=$2
|
local id="$2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
@@ -130,7 +130,7 @@ update_cron_command_api() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
del_cron_api() {
|
del_cron_api() {
|
||||||
local ids=$1
|
local ids="$1"
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
@@ -178,13 +178,13 @@ update_cron() {
|
|||||||
code=$(echo "$api" | jq -r .code)
|
code=$(echo "$api" | jq -r .code)
|
||||||
message=$(echo "$api" | jq -r .message)
|
message=$(echo "$api" | jq -r .message)
|
||||||
if [[ $code != 200 ]]; then
|
if [[ $code != 200 ]]; then
|
||||||
echo -e "\n## 更新任务状态失败(${message})\n" >>$dir_log/$log_path
|
echo -e "\n## 更新任务状态失败(${message})\n"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_api() {
|
notify_api() {
|
||||||
local title=$1
|
local title="$1"
|
||||||
local content=$2
|
local content="$2"
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
||||||
@@ -202,14 +202,14 @@ notify_api() {
|
|||||||
code=$(echo "$api" | jq -r .code)
|
code=$(echo "$api" | jq -r .code)
|
||||||
message=$(echo "$api" | jq -r .message)
|
message=$(echo "$api" | jq -r .message)
|
||||||
if [[ $code == 200 ]]; then
|
if [[ $code == 200 ]]; then
|
||||||
echo -e "通知发送成功"
|
echo -e "通知发送成功🎉"
|
||||||
else
|
else
|
||||||
echo -e "通知失败(${message})"
|
echo -e "通知失败(${message})"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
find_cron_api() {
|
find_cron_api() {
|
||||||
local params=$1
|
local params="$1"
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/detail?$params&t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/detail?$params&t=$currentTimeStamp" \
|
||||||
|
|||||||
+1
-1
@@ -42,6 +42,6 @@ echo -e "\npython3依赖安装成功...\n"
|
|||||||
echo -e "4、启动bot程序...\n"
|
echo -e "4、启动bot程序...\n"
|
||||||
make_dir $dir_log/bot
|
make_dir $dir_log/bot
|
||||||
cd $dir_data
|
cd $dir_data
|
||||||
ps -ef | grep "python3 -m jbot" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null
|
ps -eo pid,command | grep "python3 -m jbot" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null
|
||||||
nohup python3 -m jbot >$dir_log/bot/nohup.log 2>&1 &
|
nohup python3 -m jbot >$dir_log/bot/nohup.log 2>&1 &
|
||||||
echo -e "bot启动成功...\n"
|
echo -e "bot启动成功...\n"
|
||||||
|
|||||||
+16
-18
@@ -33,12 +33,12 @@ pm2_log() {
|
|||||||
echo -e "---> pm2日志"
|
echo -e "---> pm2日志"
|
||||||
local panelOut="/root/.pm2/logs/panel-out.log"
|
local panelOut="/root/.pm2/logs/panel-out.log"
|
||||||
local panelError="/root/.pm2/logs/panel-error.log"
|
local panelError="/root/.pm2/logs/panel-error.log"
|
||||||
tail -n 100 "$panelOut"
|
tail -n 300 "$panelOut"
|
||||||
tail -n 100 "$panelError"
|
tail -n 300 "$panelError"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_nginx() {
|
check_nginx() {
|
||||||
local nginxPid=$(ps -ef | grep nginx | grep -v grep)
|
local nginxPid=$(ps -eo pid,command | grep nginx | grep -v grep)
|
||||||
echo -e "=====> 检测nginx服务\n$nginxPid"
|
echo -e "=====> 检测nginx服务\n$nginxPid"
|
||||||
if [[ $nginxPid ]]; then
|
if [[ $nginxPid ]]; then
|
||||||
echo -e "\n=====> nginx服务正常\n"
|
echo -e "\n=====> nginx服务正常\n"
|
||||||
@@ -54,9 +54,6 @@ check_ql() {
|
|||||||
echo -e "\n=====> 检测面板\n\n$api\n"
|
echo -e "\n=====> 检测面板\n\n$api\n"
|
||||||
if [[ $api =~ "<div id=\"root\"></div>" ]]; then
|
if [[ $api =~ "<div id=\"root\"></div>" ]]; then
|
||||||
echo -e "=====> 面板服务启动正常\n"
|
echo -e "=====> 面板服务启动正常\n"
|
||||||
else
|
|
||||||
echo -e "=====> 面板服务异常,重置基础环境\n"
|
|
||||||
reset_env
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,25 +71,26 @@ check_pm2() {
|
|||||||
echo -e "\n=====> 检测后台\n\n$api\n"
|
echo -e "\n=====> 检测后台\n\n$api\n"
|
||||||
if [[ $api =~ "{\"code\"" ]]; then
|
if [[ $api =~ "{\"code\"" ]]; then
|
||||||
echo -e "=====> 后台服务启动正常\n"
|
echo -e "=====> 后台服务启动正常\n"
|
||||||
else
|
|
||||||
echo -e "=====> 后台服务异常,重置基础环境并重启后台\n"
|
|
||||||
reset_env
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
start_public() {
|
|
||||||
echo -e "=====> 启动公开服务\n"
|
|
||||||
pm2 delete public --source-map-support --time &>/dev/null
|
|
||||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
echo -e "=====> 开始检测"
|
echo -e "=====> 开始检测"
|
||||||
npm i -g pnpm
|
npm i -g pnpm@8.3.1
|
||||||
patch_version
|
patch_version
|
||||||
|
|
||||||
|
apk add procps netcat-openbsd
|
||||||
|
|
||||||
|
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
|
pnpm add -g pm2 tsx
|
||||||
update_depend
|
reset_env
|
||||||
start_public
|
|
||||||
copy_dep
|
copy_dep
|
||||||
check_ql
|
check_ql
|
||||||
check_nginx
|
check_nginx
|
||||||
|
|||||||
+3
-43
@@ -1,11 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
trap "single_hanle" 2 20 15 14
|
|
||||||
single_hanle() {
|
|
||||||
handle_task_after "$@"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
random_delay() {
|
random_delay() {
|
||||||
local random_delay_max=$RandomDelay
|
local random_delay_max=$RandomDelay
|
||||||
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
|
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
|
||||||
@@ -44,7 +38,7 @@ gen_array_scripts() {
|
|||||||
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
|
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
|
||||||
let i++
|
let i++
|
||||||
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
|
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
|
||||||
array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1)
|
array_scripts_name[i]=$(grep "new Env" $file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:.*\('\''\|"\)\([^"'\'']*\)\('\''\|"\).*:\2:' | sed 's:"::g' | sed "s:'::g" | head -1)
|
||||||
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
|
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -92,44 +86,10 @@ check_server() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_task_before() {
|
|
||||||
begin_time=$(format_time "$time_format" "$time")
|
|
||||||
begin_timestamp=$(format_timestamp "$time_format" "$time")
|
|
||||||
|
|
||||||
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
|
|
||||||
|
|
||||||
echo -e "## 开始执行... $begin_time\n"
|
|
||||||
|
|
||||||
[[ $is_macos -eq 0 ]] && check_server
|
|
||||||
|
|
||||||
if [[ -s $task_error_log_path ]]; then
|
|
||||||
eval cat $task_error_log_path $cmd
|
|
||||||
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
|
|
||||||
eval echo $cmd
|
|
||||||
fi
|
|
||||||
|
|
||||||
. $file_task_before "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_task_after() {
|
|
||||||
. $file_task_after "$@"
|
|
||||||
|
|
||||||
local etime=$(date "+$time_format")
|
|
||||||
local end_time=$(format_time "$time_format" "$etime")
|
|
||||||
local end_timestamp=$(format_timestamp "$time_format" "$etime")
|
|
||||||
local diff_time=$(($end_timestamp - $begin_timestamp))
|
|
||||||
|
|
||||||
[[ "$diff_time" == 0 ]] && diff_time=1
|
|
||||||
|
|
||||||
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time 秒 "
|
|
||||||
|
|
||||||
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
|
||||||
}
|
|
||||||
|
|
||||||
## 正常运行单个脚本,$1:传入参数
|
## 正常运行单个脚本,$1:传入参数
|
||||||
run_normal() {
|
run_normal() {
|
||||||
local file_param=$1
|
local file_param=$1
|
||||||
if [[ $# -eq 1 ]]; then
|
if [[ $# -eq 1 ]] && [[ "$real_time" != "true" ]]; then
|
||||||
random_delay "$file_param"
|
random_delay "$file_param"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -233,7 +193,7 @@ run_else() {
|
|||||||
local relative_path="${file_param%/*}"
|
local relative_path="${file_param%/*}"
|
||||||
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
file_param=${file_param/$relative_path\//}
|
file_param=${file_param/$relative_path\//.\/}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
shift
|
shift
|
||||||
|
|||||||
+78
-57
@@ -18,6 +18,7 @@ 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
|
||||||
@@ -34,7 +35,6 @@ file_notify_js_sample=$dir_sample/notify.js
|
|||||||
file_notify_py_sample=$dir_sample/notify.py
|
file_notify_py_sample=$dir_sample/notify.py
|
||||||
file_notify_py=$dir_scripts/notify.py
|
file_notify_py=$dir_scripts/notify.py
|
||||||
file_notify_js=$dir_scripts/sendNotify.js
|
file_notify_js=$dir_scripts/sendNotify.js
|
||||||
task_error_log_path=$dir_log/task_error.log
|
|
||||||
nginx_app_conf=$dir_root/docker/front.conf
|
nginx_app_conf=$dir_root/docker/front.conf
|
||||||
nginx_conf=$dir_root/docker/nginx.conf
|
nginx_conf=$dir_root/docker/nginx.conf
|
||||||
dep_notify_py=$dir_dep/notify.py
|
dep_notify_py=$dir_dep/notify.py
|
||||||
@@ -67,8 +67,8 @@ import_config() {
|
|||||||
[[ -f $file_config_user ]] && . $file_config_user
|
[[ -f $file_config_user ]] && . $file_config_user
|
||||||
[[ -f $file_env ]] && . $file_env
|
[[ -f $file_env ]] && . $file_env
|
||||||
|
|
||||||
ql_base_url=${QlBaseUrl:-""}
|
ql_base_url=${QlBaseUrl:-"/"}
|
||||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
command_timeout_time=${CommandTimeoutTime:-""}
|
||||||
proxy_url=${ProxyUrl:-""}
|
proxy_url=${ProxyUrl:-""}
|
||||||
file_extensions=${RepoFileExtensions:-"js py"}
|
file_extensions=${RepoFileExtensions:-"js py"}
|
||||||
current_branch=${QL_BRANCH}
|
current_branch=${QL_BRANCH}
|
||||||
@@ -98,6 +98,9 @@ set_proxy() {
|
|||||||
unset_proxy() {
|
unset_proxy() {
|
||||||
unset http_proxy
|
unset http_proxy
|
||||||
unset https_proxy
|
unset https_proxy
|
||||||
|
unset ftp_proxy
|
||||||
|
unset all_proxy
|
||||||
|
unset no_proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
make_dir() {
|
make_dir() {
|
||||||
@@ -262,28 +265,13 @@ npm_install_sub() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
npm_install_1() {
|
|
||||||
local dir_current=$(pwd)
|
|
||||||
local dir_work=$1
|
|
||||||
|
|
||||||
cd $dir_work
|
|
||||||
echo -e "运行 npm install...\n"
|
|
||||||
npm_install_sub
|
|
||||||
[[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 npm install...\n"
|
|
||||||
cd $dir_current
|
|
||||||
}
|
|
||||||
|
|
||||||
npm_install_2() {
|
npm_install_2() {
|
||||||
local dir_current=$(pwd)
|
local dir_current=$(pwd)
|
||||||
local dir_work=$1
|
local dir_work=$1
|
||||||
|
|
||||||
cd $dir_work
|
cd $dir_work
|
||||||
echo -e "检测到 $dir_work 的依赖包有变化,运行 npm install...\n"
|
echo -e "安装 $dir_work 依赖包...\n"
|
||||||
npm_install_sub
|
npm_install_sub
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n"
|
|
||||||
npm_install_1 $dir_work
|
|
||||||
fi
|
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,11 +300,15 @@ git_clone_scripts() {
|
|||||||
local branch="$3"
|
local branch="$3"
|
||||||
local proxy="$4"
|
local proxy="$4"
|
||||||
[[ $branch ]] && local part_cmd="-b $branch "
|
[[ $branch ]] && local part_cmd="-b $branch "
|
||||||
echo -e "开始克隆仓库 $url 到 $dir\n"
|
echo -e "开始拉取 $url 到 $dir\n"
|
||||||
|
|
||||||
set_proxy "$proxy"
|
set_proxy "$proxy"
|
||||||
git clone $part_cmd $url $dir
|
|
||||||
|
git clone --depth=1 $part_cmd $url $dir
|
||||||
exit_status=$?
|
exit_status=$?
|
||||||
|
|
||||||
|
reset_branch "$branch" "$dir"
|
||||||
|
|
||||||
unset_proxy
|
unset_proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,13 +319,25 @@ git_pull_scripts() {
|
|||||||
local proxy="$3"
|
local proxy="$3"
|
||||||
cd $dir_work
|
cd $dir_work
|
||||||
echo -e "开始更新仓库:$dir_work"
|
echo -e "开始更新仓库:$dir_work"
|
||||||
|
|
||||||
set_proxy "$proxy"
|
set_proxy "$proxy"
|
||||||
git fetch --all
|
|
||||||
git pull 1>/dev/null
|
|
||||||
exit_status=$?
|
|
||||||
unset_proxy
|
|
||||||
|
|
||||||
|
if [[ ! $branch ]]; then
|
||||||
|
branch=$(cd $dir_work && git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pre_commit_id=$(git rev-parse --short HEAD)
|
||||||
|
reset_branch "$branch" "$dir_work"
|
||||||
|
|
||||||
|
git fetch --depth 1 origin $branch
|
||||||
|
exit_status=$?
|
||||||
|
|
||||||
|
reset_branch "$branch" "$dir_work"
|
||||||
|
local cur_commit_id=$(git rev-parse --short HEAD)
|
||||||
|
if [[ $cur_commit_id != $pre_commit_id ]]; then
|
||||||
|
exit_status=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset_proxy
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,19 +355,16 @@ reset_romote_url() {
|
|||||||
git init
|
git init
|
||||||
git remote add origin $url &>/dev/null
|
git remote add origin $url &>/dev/null
|
||||||
fi
|
fi
|
||||||
reset_branch "$branch"
|
|
||||||
cd $dir_current
|
cd $dir_current
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_branch() {
|
reset_branch() {
|
||||||
local branch="$1"
|
local branch="$1"
|
||||||
local part_cmd="HEAD"
|
local part_cmd="origin/${branch}"
|
||||||
if [[ $branch ]]; then
|
git remote set-branches origin $branch
|
||||||
part_cmd="origin/${branch}"
|
|
||||||
git checkout -B "$branch" &>/dev/null
|
|
||||||
git branch --set-upstream-to=$part_cmd $branch &>/dev/null
|
|
||||||
fi
|
|
||||||
git reset --hard $part_cmd &>/dev/null
|
git reset --hard $part_cmd &>/dev/null
|
||||||
|
git checkout -b $branch $part_cmd &>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
random_range() {
|
random_range() {
|
||||||
@@ -373,15 +374,11 @@ random_range() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reload_pm2() {
|
reload_pm2() {
|
||||||
pm2 l &>/dev/null
|
cd $dir_root
|
||||||
|
# 代理会影响 grpc 服务
|
||||||
echo -e "启动面板服务\n"
|
unset_proxy
|
||||||
pm2 delete panel --source-map-support --time &>/dev/null
|
pm2 flush &>/dev/null
|
||||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null
|
pm2 startOrGracefulReload $file_ecosystem_js
|
||||||
|
|
||||||
echo -e "启动定时服务\n"
|
|
||||||
pm2 delete schedule --source-map-support --time &>/dev/null
|
|
||||||
pm2 start $dir_static/build/schedule/index.js -n schedule --source-map-support --time &>/dev/null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
diff_time() {
|
diff_time() {
|
||||||
@@ -431,18 +428,6 @@ format_timestamp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
patch_version() {
|
patch_version() {
|
||||||
# 兼容pnpm@7
|
|
||||||
pnpm setup &>/dev/null
|
|
||||||
source ~/.bashrc
|
|
||||||
|
|
||||||
if [[ $PipMirror ]]; then
|
|
||||||
pip3 config set global.index-url $PipMirror
|
|
||||||
fi
|
|
||||||
if [[ $NpmMirror ]]; then
|
|
||||||
cd && pnpm config set registry $NpmMirror
|
|
||||||
pnpm install -g
|
|
||||||
fi
|
|
||||||
|
|
||||||
git config --global pull.rebase false
|
git config --global pull.rebase false
|
||||||
|
|
||||||
cp -f $dir_root/.env.example $dir_root/.env
|
cp -f $dir_root/.env.example $dir_root/.env
|
||||||
@@ -482,7 +467,19 @@ patch_version() {
|
|||||||
init_nginx() {
|
init_nginx() {
|
||||||
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||||
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
local location_url="/"
|
||||||
|
local aliasStr=""
|
||||||
|
local rootStr=""
|
||||||
|
if [[ $ql_base_url != "/" ]]; then
|
||||||
|
location_url="^~${ql_base_url%*/}"
|
||||||
|
aliasStr="alias ${dir_static}/dist;"
|
||||||
|
else
|
||||||
|
rootStr="root ${dir_static}/dist;"
|
||||||
|
fi
|
||||||
|
sed -i "s,QL_ALIAS_CONFIG,${aliasStr},g" /etc/nginx/conf.d/front.conf
|
||||||
|
sed -i "s,QL_ROOT_CONFIG,${rootStr},g" /etc/nginx/conf.d/front.conf
|
||||||
|
sed -i "s,QL_BASE_URL_LOCATION,${location_url},g" /etc/nginx/conf.d/front.conf
|
||||||
|
sed -i "s,QL_BASE_URL,${ql_base_url},g" /etc/nginx/conf.d/front.conf
|
||||||
|
|
||||||
ipv6=$(ip a | grep inet6)
|
ipv6=$(ip a | grep inet6)
|
||||||
ipv6Str=""
|
ipv6Str=""
|
||||||
@@ -492,10 +489,34 @@ init_nginx() {
|
|||||||
sed -i "s,IPV6_CONFIG,${ipv6Str},g" /etc/nginx/conf.d/front.conf
|
sed -i "s,IPV6_CONFIG,${ipv6Str},g" /etc/nginx/conf.d/front.conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle_task_before() {
|
||||||
|
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||||
|
|
||||||
|
echo -e "## 开始执行... $begin_time\n"
|
||||||
|
|
||||||
|
[[ $is_macos -eq 0 ]] && check_server
|
||||||
|
|
||||||
|
. $file_task_before "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_task_after() {
|
||||||
|
. $file_task_after "$@"
|
||||||
|
|
||||||
|
local etime=$(date "+$time_format")
|
||||||
|
local end_time=$(format_time "$time_format" "$etime")
|
||||||
|
local end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||||
|
local diff_time=$(($end_timestamp - $begin_timestamp))
|
||||||
|
|
||||||
|
[[ "$diff_time" == 0 ]] && diff_time=1
|
||||||
|
|
||||||
|
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time 秒 "
|
||||||
|
|
||||||
|
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
|
}
|
||||||
|
|
||||||
init_env
|
init_env
|
||||||
detect_termux
|
detect_termux
|
||||||
detect_macos
|
detect_macos
|
||||||
define_cmd
|
define_cmd
|
||||||
fix_config
|
|
||||||
|
|
||||||
import_config $1 2>$task_error_log_path
|
import_config $1
|
||||||
|
|||||||
+36
-6
@@ -5,6 +5,12 @@ dir_shell=$QL_DIR/shell
|
|||||||
. $dir_shell/share.sh
|
. $dir_shell/share.sh
|
||||||
. $dir_shell/api.sh
|
. $dir_shell/api.sh
|
||||||
|
|
||||||
|
trap "single_hanle" 2 3 20 15 14
|
||||||
|
single_hanle() {
|
||||||
|
eval handle_task_after "$@" "$cmd"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
## 选择python3还是node
|
## 选择python3还是node
|
||||||
define_program() {
|
define_program() {
|
||||||
local file_param=$1
|
local file_param=$1
|
||||||
@@ -28,14 +34,22 @@ define_program() {
|
|||||||
handle_log_path() {
|
handle_log_path() {
|
||||||
local file_param=$1
|
local file_param=$1
|
||||||
|
|
||||||
if [[ -z $file_param ]];then
|
if [[ -z $file_param ]]; then
|
||||||
file_param="task"
|
file_param="task"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -z $ID ]]; then
|
||||||
|
ID=$(cat $list_crontab_user | grep -E "$cmd_task.* $file_param" | perl -pe "s|.*ID=(.*) $cmd_task.* $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||||
|
fi
|
||||||
local suffix=""
|
local suffix=""
|
||||||
if [[ ! -z $ID ]]; then
|
if [[ ! -z $ID ]]; then
|
||||||
|
if [[ "$ID" -gt 0 ]] 2>/dev/null; then
|
||||||
suffix="_${ID}"
|
suffix="_${ID}"
|
||||||
|
else
|
||||||
|
ID=""
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
time=$(date "+$mtime_format")
|
time=$(date "+$mtime_format")
|
||||||
log_time=$(format_log_time "$mtime_format" "$time")
|
log_time=$(format_log_time "$mtime_format" "$time")
|
||||||
log_dir_tmp="${file_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
@@ -51,11 +65,19 @@ handle_log_path() {
|
|||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
log_dir="${log_dir_tmp%.*}${suffix}"
|
log_dir="${log_dir_tmp%.*}${suffix}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
|
|
||||||
|
if [[ $real_log_path ]]; then
|
||||||
|
log_path="$real_log_path"
|
||||||
|
fi
|
||||||
|
|
||||||
cmd=">> $dir_log/$log_path 2>&1"
|
cmd=">> $dir_log/$log_path 2>&1"
|
||||||
if [[ "$show_log" == "true" ]]; then
|
|
||||||
cmd=""
|
|
||||||
else
|
|
||||||
make_dir "$dir_log/$log_dir"
|
make_dir "$dir_log/$log_dir"
|
||||||
|
if [[ "$show_log" == "true" ]]; then
|
||||||
|
cmd="2>&1 | tee -a $dir_log/$log_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$real_time" == "true" ]]; then
|
||||||
|
cmd=""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +89,19 @@ format_params() {
|
|||||||
mtime_format="%Y-%m-%d %H:%M:%S.%3N"
|
mtime_format="%Y-%m-%d %H:%M:%S.%3N"
|
||||||
fi
|
fi
|
||||||
timeoutCmd=""
|
timeoutCmd=""
|
||||||
|
if [[ $command_timeout_time ]]; then
|
||||||
if type timeout &>/dev/null; then
|
if type timeout &>/dev/null; then
|
||||||
timeoutCmd="timeout --foreground -s 14 -k 10s $command_timeout_time "
|
timeoutCmd="timeout --foreground -s 2 -k 10s $command_timeout_time "
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
# params=$(echo "$@" | sed -E 's/([^ ])&([^ ])/\1\\\&\2/g')
|
# params=$(echo "$@" | sed -E 's/([^ ])&([^ ])/\1\\\&\2/g')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_begin_time() {
|
||||||
|
begin_time=$(format_time "$time_format" "$time")
|
||||||
|
begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
}
|
||||||
|
|
||||||
while getopts ":lm:" opt; do
|
while getopts ":lm:" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
l)
|
l)
|
||||||
@@ -92,8 +121,9 @@ fi
|
|||||||
format_params "$@"
|
format_params "$@"
|
||||||
define_program "$@"
|
define_program "$@"
|
||||||
handle_log_path "$@"
|
handle_log_path "$@"
|
||||||
|
init_begin_time
|
||||||
|
|
||||||
eval . $dir_shell/otask.sh "$cmd"
|
eval . $dir_shell/otask.sh "$cmd"
|
||||||
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
|
[[ -f "$dir_log/$log_path" ]] && [[ ! $show_log ]] && [[ "$real_time" != "true" ]] && cat "$dir_log/$log_path"
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
+16
-32
@@ -31,7 +31,7 @@ output_list_add_drop() {
|
|||||||
local list=$1
|
local list=$1
|
||||||
local type=$2
|
local type=$2
|
||||||
if [[ -s $list ]]; then
|
if [[ -s $list ]]; then
|
||||||
echo -e "检测到有$type的定时任务:\n"
|
echo -e "检测到有${type}的定时任务:\n"
|
||||||
cat $list
|
cat $list
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
@@ -44,9 +44,9 @@ del_cron() {
|
|||||||
local path=$2
|
local path=$2
|
||||||
local detail=""
|
local detail=""
|
||||||
local ids=""
|
local ids=""
|
||||||
echo -e "开始尝试自动删除失效的定时任务...\n"
|
echo -e "开始尝试自动删除失效的定时任务..."
|
||||||
for cron in $(cat $list_drop); do
|
for cron in $(cat $list_drop); do
|
||||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $cron" | perl -pe "s|.*ID=(.*) $cmd_task $cron\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
local id=$(cat $list_crontab_user | grep -E "$cmd_task.* $cron" | perl -pe "s|.*ID=(.*) $cmd_task.* $cron\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||||
if [[ $ids ]]; then
|
if [[ $ids ]]; then
|
||||||
ids="$ids,\"$id\""
|
ids="$ids,\"$id\""
|
||||||
else
|
else
|
||||||
@@ -54,7 +54,7 @@ del_cron() {
|
|||||||
fi
|
fi
|
||||||
cron_file="$dir_scripts/${cron}"
|
cron_file="$dir_scripts/${cron}"
|
||||||
if [[ -f $cron_file ]]; then
|
if [[ -f $cron_file ]]; then
|
||||||
cron_name=$(grep "new Env" $cron_file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
cron_name=$(grep "new Env" $cron_file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:.*\('\''\|"\)\([^"'\'']*\)\('\''\|"\).*:\2:' | sed 's:"::g' | sed "s:'::g" | head -1)
|
||||||
rm -f $cron_file
|
rm -f $cron_file
|
||||||
fi
|
fi
|
||||||
[[ -z $cron_name ]] && cron_name="$cron"
|
[[ -z $cron_name ]] && cron_name="$cron"
|
||||||
@@ -75,7 +75,7 @@ del_cron() {
|
|||||||
add_cron() {
|
add_cron() {
|
||||||
local list_add=$1
|
local list_add=$1
|
||||||
local path=$2
|
local path=$2
|
||||||
echo -e "开始尝试自动添加定时任务...\n"
|
echo -e "开始尝试自动添加定时任务..."
|
||||||
local detail=""
|
local detail=""
|
||||||
cd $dir_scripts
|
cd $dir_scripts
|
||||||
for file in $(cat $list_add); do
|
for file in $(cat $list_add); do
|
||||||
@@ -92,7 +92,7 @@ add_cron() {
|
|||||||
s| | |g;
|
s| | |g;
|
||||||
}" | sort -u | head -1
|
}" | sort -u | head -1
|
||||||
)
|
)
|
||||||
cron_name=$(grep "new Env" $file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
cron_name=$(grep "new Env" $file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:.*\('\''\|"\)\([^"'\'']*\)\('\''\|"\).*:\2:' | sed 's:"::g' | sed "s:'::g" | head -1)
|
||||||
[[ -z $cron_name ]] && cron_name="$file_name"
|
[[ -z $cron_name ]] && cron_name="$file_name"
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $file | awk -F ":" '{print $2}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $file | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $file | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron " $file | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||||
@@ -130,13 +130,9 @@ update_repo() {
|
|||||||
make_dir "${dir_scripts}/${uniq_path}"
|
make_dir "${dir_scripts}/${uniq_path}"
|
||||||
|
|
||||||
local formatUrl="$url"
|
local formatUrl="$url"
|
||||||
if [[ -d ${repo_path}/.git ]]; then
|
|
||||||
reset_romote_url ${repo_path} "${formatUrl}" "${branch}"
|
|
||||||
git_pull_scripts ${repo_path} "${branch}" "${proxy}"
|
|
||||||
else
|
|
||||||
rm -rf ${repo_path} &>/dev/null
|
rm -rf ${repo_path} &>/dev/null
|
||||||
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}"
|
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}"
|
||||||
fi
|
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
echo -e "\n更新${repo_path}成功...\n"
|
echo -e "\n更新${repo_path}成功...\n"
|
||||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
|
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
|
||||||
@@ -193,7 +189,7 @@ update_raw() {
|
|||||||
s| | |g;
|
s| | |g;
|
||||||
}" | sort -u | head -1
|
}" | sort -u | head -1
|
||||||
)
|
)
|
||||||
cron_name=$(grep "new Env" $raw_file_name | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
cron_name=$(grep "new Env" $raw_file_name | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:.*\('\''\|"\)\([^"'\'']*\)\('\''\|"\).*:\2:' | sed 's:"::g' | sed "s:'::g" | head -1)
|
||||||
[[ -z $cron_name ]] && cron_name="$raw_file_name"
|
[[ -z $cron_name ]] && cron_name="$raw_file_name"
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $raw_file_name | awk -F ":" '{print $2}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $raw_file_name | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $raw_file_name | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
[[ -z $cron_line ]] && cron_line=$(grep "cron " $raw_file_name | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||||
@@ -243,10 +239,9 @@ update_qinglong() {
|
|||||||
echo -e "使用 ${mirror} 源更新...\n"
|
echo -e "使用 ${mirror} 源更新...\n"
|
||||||
export isFirstStartServer=false
|
export isFirstStartServer=false
|
||||||
|
|
||||||
local all_branch=$(cd ${dir_root} && git branch -a)
|
|
||||||
local primary_branch="master"
|
local primary_branch="master"
|
||||||
if [[ "${all_branch}" =~ "${current_branch}" ]]; then
|
if [[ "${QL_BRANCH}" == "develop" ]]; then
|
||||||
primary_branch="${current_branch}"
|
primary_branch="develop"
|
||||||
fi
|
fi
|
||||||
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
||||||
reset_romote_url ${dir_root} "https://${mirror}.com/whyour/qinglong.git" ${primary_branch}
|
reset_romote_url ${dir_root} "https://${mirror}.com/whyour/qinglong.git" ${primary_branch}
|
||||||
@@ -254,7 +249,6 @@ update_qinglong() {
|
|||||||
|
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
echo -e "\n更新青龙源文件成功...\n"
|
echo -e "\n更新青龙源文件成功...\n"
|
||||||
reset_romote_url ${dir_root} "https://${mirror}.com/whyour/qinglong.git" ${primary_branch}
|
|
||||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
cp -f $file_config_sample $dir_config/config.sample.sh
|
||||||
update_depend
|
update_depend
|
||||||
|
|
||||||
@@ -271,16 +265,11 @@ update_qinglong_static() {
|
|||||||
local no_restart="$1"
|
local no_restart="$1"
|
||||||
local primary_branch="$2"
|
local primary_branch="$2"
|
||||||
local url="https://${mirror}.com/whyour/qinglong-static.git"
|
local url="https://${mirror}.com/whyour/qinglong-static.git"
|
||||||
if [[ -d ${ql_static_repo}/.git ]]; then
|
|
||||||
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
|
||||||
git_pull_scripts ${ql_static_repo} ${primary_branch}
|
|
||||||
else
|
|
||||||
rm -rf ${ql_static_repo} &>/dev/null
|
rm -rf ${ql_static_repo} &>/dev/null
|
||||||
git_clone_scripts ${url} ${ql_static_repo} ${primary_branch}
|
git_clone_scripts ${url} ${ql_static_repo} ${primary_branch}
|
||||||
fi
|
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
echo -e "\n更新青龙静态资源成功...\n"
|
echo -e "\n更新青龙静态资源成功...\n"
|
||||||
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
|
||||||
|
|
||||||
rm -rf $dir_static/*
|
rm -rf $dir_static/*
|
||||||
cp -rf $ql_static_repo/* $dir_static
|
cp -rf $ql_static_repo/* $dir_static
|
||||||
@@ -366,10 +355,10 @@ gen_list_repo() {
|
|||||||
done
|
done
|
||||||
files=$(eval $cmd | sed 's/^..//')
|
files=$(eval $cmd | sed 's/^..//')
|
||||||
if [[ $path ]]; then
|
if [[ $path ]]; then
|
||||||
files=$(echo "$files" | egrep $path)
|
files=$(echo "$files" | egrep "$path")
|
||||||
fi
|
fi
|
||||||
if [[ $blackword ]]; then
|
if [[ $blackword ]]; then
|
||||||
files=$(echo "$files" | egrep -v $blackword)
|
files=$(echo "$files" | egrep -v "$blackword")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp -f $file_notify_js "${dir_scripts}/${uniq_path}"
|
cp -f $file_notify_js "${dir_scripts}/${uniq_path}"
|
||||||
@@ -377,7 +366,7 @@ gen_list_repo() {
|
|||||||
|
|
||||||
if [[ $dependence ]]; then
|
if [[ $dependence ]]; then
|
||||||
cd ${repo_path}
|
cd ${repo_path}
|
||||||
results=$(eval $cmd | sed 's/^..//' | egrep $dependence)
|
results=$(eval $cmd | sed 's/^..//' | egrep "$dependence")
|
||||||
for _file in ${results}; do
|
for _file in ${results}; do
|
||||||
file_path=$(dirname $_file)
|
file_path=$(dirname $_file)
|
||||||
make_dir "${dir_scripts}/${uniq_path}/${file_path}"
|
make_dir "${dir_scripts}/${uniq_path}/${file_path}"
|
||||||
@@ -390,7 +379,7 @@ gen_list_repo() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for file in ${files}; do
|
for file in ${files}; do
|
||||||
filename=$(basename $file)
|
filename=$(basename "$file")
|
||||||
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
||||||
echo "${uniq_path}/${filename}" >>"$dir_list_tmp/${uniq_path}_scripts.list"
|
echo "${uniq_path}/${filename}" >>"$dir_list_tmp/${uniq_path}_scripts.list"
|
||||||
# cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task.* ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
# cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task.* ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||||
@@ -459,12 +448,6 @@ main() {
|
|||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -s $task_error_log_path ]]; then
|
|
||||||
eval cat $task_error_log_path $cmd
|
|
||||||
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
|
|
||||||
eval echo $cmd
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
|
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
|
||||||
eval echo -e "请移除 -l 参数" $cmd
|
eval echo -e "请移除 -l 参数" $cmd
|
||||||
exit 1
|
exit 1
|
||||||
@@ -472,6 +455,7 @@ main() {
|
|||||||
|
|
||||||
case $p1 in
|
case $p1 in
|
||||||
update)
|
update)
|
||||||
|
fix_config
|
||||||
eval update_qinglong "$2" $cmd
|
eval update_qinglong "$2" $cmd
|
||||||
;;
|
;;
|
||||||
extra)
|
extra)
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
const baseUrl = window.__ENV__QlBaseUrl || '/';
|
||||||
|
|
||||||
|
export function modifyClientRenderOpts(memo: any) {
|
||||||
|
return {
|
||||||
|
...memo,
|
||||||
|
publicPath: baseUrl,
|
||||||
|
basename: baseUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyContextOpts(memo: any) {
|
||||||
|
return {
|
||||||
|
...memo,
|
||||||
|
basename: baseUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
+34
-2
@@ -332,8 +332,20 @@ select:-webkit-autofill:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider-logo {
|
.ant-pro-sider-logo {
|
||||||
h1 {
|
.title {
|
||||||
margin-left: 5px !important;
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
margin: 0 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 32px;
|
||||||
|
vertical-align: middle;
|
||||||
|
animation: pro-layout-title-hide 0.3s;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -347,6 +359,10 @@ select:-webkit-autofill:focus {
|
|||||||
// 移动端logo被拉伸
|
// 移动端logo被拉伸
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-image {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
@@ -386,3 +402,19 @@ pre {
|
|||||||
.ant-table-filter-column {
|
.ant-table-filter-column {
|
||||||
justify-content: unset;
|
justify-content: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea.ant-input {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-sider-collapsed,
|
||||||
|
body[data-mode='phone'] header {
|
||||||
|
.title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-design-pro .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
|
||||||
|
box-shadow: none;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|||||||
+25
-10
@@ -98,8 +98,7 @@ export default function () {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
})
|
});
|
||||||
.finally(() => setInitLoading(false));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUser = (needLoading = true) => {
|
const getUser = (needLoading = true) => {
|
||||||
@@ -120,6 +119,22 @@ export default function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getHealthStatus = () => {
|
||||||
|
request
|
||||||
|
.get(`${config.apiPrefix}public/health`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res?.data?.status === 1) {
|
||||||
|
getSystemInfo();
|
||||||
|
} else {
|
||||||
|
history.push('/error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
history.push('/error');
|
||||||
|
})
|
||||||
|
.finally(() => setInitLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
const reloadUser = (needLoading = false) => {
|
const reloadUser = (needLoading = false) => {
|
||||||
getUser(needLoading);
|
getUser(needLoading);
|
||||||
};
|
};
|
||||||
@@ -131,10 +146,8 @@ export default function () {
|
|||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!systemInfo) {
|
getHealthStatus();
|
||||||
getSystemInfo();
|
}, []);
|
||||||
}
|
|
||||||
}, [systemInfo]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (theme === 'vs-dark') {
|
if (theme === 'vs-dark') {
|
||||||
@@ -267,11 +280,11 @@ export default function () {
|
|||||||
selectedKeys={[location.pathname]}
|
selectedKeys={[location.pathname]}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
ErrorBoundary={Sentry.ErrorBoundary}
|
ErrorBoundary={Sentry.ErrorBoundary}
|
||||||
logo={<Image preview={false} src="https://qn.whyour.cn/logo.png" />}
|
logo={
|
||||||
// @ts-ignore
|
|
||||||
title={
|
|
||||||
<>
|
<>
|
||||||
<span style={{ fontSize: 16, marginRight: 5 }}>青龙</span>
|
<Image preview={false} src="https://qn.whyour.cn/logo.png" />
|
||||||
|
<div className="title">
|
||||||
|
<span className="title">青龙</span>
|
||||||
<a
|
<a
|
||||||
href={systemInfo?.changeLogLink}
|
href={systemInfo?.changeLogLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -298,8 +311,10 @@ export default function () {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
title={false}
|
||||||
menuItemRender={(menuItemProps: any, defaultDom: any) => {
|
menuItemRender={(menuItemProps: any, defaultDom: any) => {
|
||||||
if (
|
if (
|
||||||
menuItemProps.isUrl ||
|
menuItemProps.isUrl ||
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
FullscreenOutlined,
|
FullscreenOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { CrontabStatus } from './index';
|
import { CrontabStatus } from './type';
|
||||||
import { diffTime } from '@/utils/date';
|
import { diffTime } from '@/utils/date';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
|
|||||||
+16
-52
@@ -54,51 +54,11 @@ import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
|||||||
import { getCommandScript, parseCrontab } from '@/utils';
|
import { getCommandScript, parseCrontab } from '@/utils';
|
||||||
import { ColumnProps } from 'antd/lib/table';
|
import { ColumnProps } from 'antd/lib/table';
|
||||||
import { useVT } from 'virtualizedtableforantd4';
|
import { useVT } from 'virtualizedtableforantd4';
|
||||||
|
import { ICrontab, OperationName, OperationPath, CrontabStatus } from './type';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
|
|
||||||
export enum CrontabStatus {
|
|
||||||
'running',
|
|
||||||
'idle',
|
|
||||||
'disabled',
|
|
||||||
'queued',
|
|
||||||
}
|
|
||||||
|
|
||||||
const CrontabSort: any = { 0: 0, 5: 1, 3: 2, 1: 3, 4: 4 };
|
|
||||||
|
|
||||||
enum OperationName {
|
|
||||||
'启用',
|
|
||||||
'禁用',
|
|
||||||
'运行',
|
|
||||||
'停止',
|
|
||||||
'置顶',
|
|
||||||
'取消置顶',
|
|
||||||
}
|
|
||||||
|
|
||||||
enum OperationPath {
|
|
||||||
'enable',
|
|
||||||
'disable',
|
|
||||||
'run',
|
|
||||||
'stop',
|
|
||||||
'pin',
|
|
||||||
'unpin',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICrontab {
|
|
||||||
name: string;
|
|
||||||
command: string;
|
|
||||||
schedule: string;
|
|
||||||
id: number;
|
|
||||||
status: number;
|
|
||||||
isDisabled?: 1 | 0;
|
|
||||||
isPinned?: 1 | 0;
|
|
||||||
labels?: string[];
|
|
||||||
last_running_time?: number;
|
|
||||||
last_execution_time?: number;
|
|
||||||
nextRunTime: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Crontab = () => {
|
const Crontab = () => {
|
||||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const columns: ColumnProps<ICrontab>[] = [
|
const columns: ColumnProps<ICrontab>[] = [
|
||||||
@@ -264,19 +224,19 @@ const Crontab = () => {
|
|||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
text: '运行中',
|
text: '运行中',
|
||||||
value: 0,
|
value: CrontabStatus.running,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '空闲中',
|
text: '空闲中',
|
||||||
value: 1,
|
value: CrontabStatus.idle,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '已禁用',
|
text: '已禁用',
|
||||||
value: 2,
|
value: CrontabStatus.disabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '队列中',
|
text: '队列中',
|
||||||
value: 3,
|
value: CrontabStatus.queued,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
@@ -824,12 +784,6 @@ const Crontab = () => {
|
|||||||
}, [viewConf, enabledCronViews]);
|
}, [viewConf, enabledCronViews]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPageConf({
|
|
||||||
page: 1,
|
|
||||||
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
|
||||||
sorter: {},
|
|
||||||
filters: {},
|
|
||||||
});
|
|
||||||
getCronViews();
|
getCronViews();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -889,7 +843,17 @@ const Crontab = () => {
|
|||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setCronViews(data);
|
setCronViews(data);
|
||||||
setEnabledCronViews(data.filter((x) => !x.isDisabled));
|
const firstEnableView = data.filter((x) => !x.isDisabled);
|
||||||
|
setEnabledCronViews(firstEnableView);
|
||||||
|
setPageConf({
|
||||||
|
page: 1,
|
||||||
|
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
||||||
|
sorter: {},
|
||||||
|
filters: {},
|
||||||
|
});
|
||||||
|
setViewConf({
|
||||||
|
...firstEnableView[0],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -8,13 +8,8 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
import { logEnded } from '@/utils';
|
import { logEnded } from '@/utils';
|
||||||
|
import { CrontabStatus } from './type';
|
||||||
|
|
||||||
enum CrontabStatus {
|
|
||||||
'running',
|
|
||||||
'idle',
|
|
||||||
'disabled',
|
|
||||||
'queued',
|
|
||||||
}
|
|
||||||
const { Countdown } = Statistic;
|
const { Countdown } = Statistic;
|
||||||
|
|
||||||
const CronLogModal = ({
|
const CronLogModal = ({
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
export enum CrontabStatus {
|
||||||
|
'running' = 0,
|
||||||
|
'queued' = 0.5,
|
||||||
|
'idle' = 1,
|
||||||
|
'disabled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OperationName {
|
||||||
|
'启用',
|
||||||
|
'禁用',
|
||||||
|
'运行',
|
||||||
|
'停止',
|
||||||
|
'置顶',
|
||||||
|
'取消置顶',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OperationPath {
|
||||||
|
'enable',
|
||||||
|
'disable',
|
||||||
|
'run',
|
||||||
|
'stop',
|
||||||
|
'pin',
|
||||||
|
'unpin',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICrontab {
|
||||||
|
name: string;
|
||||||
|
command: string;
|
||||||
|
schedule: string;
|
||||||
|
id: number;
|
||||||
|
status: number;
|
||||||
|
isDisabled?: 1 | 0;
|
||||||
|
isPinned?: 1 | 0;
|
||||||
|
labels?: string[];
|
||||||
|
last_running_time?: number;
|
||||||
|
last_execution_time?: number;
|
||||||
|
nextRunTime: Date;
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import config from '@/utils/config';
|
|||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import IconFont from '@/components/iconfont';
|
import IconFont from '@/components/iconfont';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
import { CrontabStatus } from './type';
|
||||||
|
|
||||||
const PROPERTIES = [
|
const PROPERTIES = [
|
||||||
{ name: '命令', value: 'command' },
|
{ name: '命令', value: 'command' },
|
||||||
@@ -47,9 +48,9 @@ const SORTTYPES = [
|
|||||||
|
|
||||||
const STATUS_MAP = {
|
const STATUS_MAP = {
|
||||||
status: [
|
status: [
|
||||||
{ name: '运行中', value: 0 },
|
{ name: '运行中', value: CrontabStatus.running },
|
||||||
{ name: '空闲中', value: 1 },
|
{ name: '空闲中', value: CrontabStatus.idle },
|
||||||
{ name: '已禁用', value: 2 },
|
{ name: '已禁用', value: CrontabStatus.disabled },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,9 +254,9 @@ const ViewCreateModal = ({
|
|||||||
name={[name, 'value']}
|
name={[name, 'value']}
|
||||||
rules={[{ required: true, message: '请输入内容' }]}
|
rules={[{ required: true, message: '请输入内容' }]}
|
||||||
>
|
>
|
||||||
{EOperation[filtersValue[name]['operation']] ===
|
{EOperation[filtersValue?.[name]['operation']] ===
|
||||||
'select' ? (
|
'select' ? (
|
||||||
statusElement(filtersValue[name]['property'])
|
statusElement(filtersValue?.[name]['property'])
|
||||||
) : (
|
) : (
|
||||||
<Input placeholder="请输入内容" />
|
<Input placeholder="请输入内容" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ const ViewManageModal = ({
|
|||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
cronViewChange: (data?: any) => void;
|
cronViewChange: (data?: any) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const islastEnableView = (record) => {
|
||||||
|
return list.filter((x) => !x.isDisabled).length <= 1 && !record.isDisabled;
|
||||||
|
};
|
||||||
|
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
@@ -95,6 +99,7 @@ const ViewManageModal = ({
|
|||||||
render: (text: string, record: any, index: number) => {
|
render: (text: string, record: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
|
disabled={islastEnableView(record)}
|
||||||
checked={!record.isDisabled}
|
checked={!record.isDisabled}
|
||||||
onChange={(checked) => onShowChange(checked, record, index)}
|
onChange={(checked) => onShowChange(checked, record, index)}
|
||||||
/>
|
/>
|
||||||
@@ -111,9 +116,11 @@ const ViewManageModal = ({
|
|||||||
<a onClick={() => editView(record, index)}>
|
<a onClick={() => editView(record, index)}>
|
||||||
<EditOutlined />
|
<EditOutlined />
|
||||||
</a>
|
</a>
|
||||||
|
{!islastEnableView(record) && (
|
||||||
<a onClick={() => deleteView(record, index)}>
|
<a onClick={() => deleteView(record, index)}>
|
||||||
<DeleteOutlined />
|
<DeleteOutlined />
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
) : (
|
||||||
'-'
|
'-'
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ const Dependence = () => {
|
|||||||
})
|
})
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200 && force) {
|
if (code === 200 && force) {
|
||||||
const i = value.findIndex((x) => x.id === data.data[0].id);
|
const i = value.findIndex((x) => x.id === data[0].id);
|
||||||
if (i !== -1) {
|
if (i !== -1) {
|
||||||
const result = [...value];
|
const result = [...value];
|
||||||
result.splice(i, 1);
|
result.splice(i, 1);
|
||||||
|
|||||||
Vendored
+1
-1
@@ -394,7 +394,7 @@ const Env = () => {
|
|||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
const newData = [...value];
|
const newData = [...value];
|
||||||
newData.splice(dragIndex, 1);
|
newData.splice(dragIndex, 1);
|
||||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
newData.splice(hoverIndex, 0, { ...dragRow, ...data });
|
||||||
setValue([...newData]);
|
setValue([...newData]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.error-wrapper {
|
.error-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
||||||
@@ -9,4 +8,46 @@
|
|||||||
height: calc(100vh - 80px);
|
height: calc(100vh - 80px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-box {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 80vw;
|
||||||
|
height: 90vh;
|
||||||
|
margin: 16px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid rgba(5, 5, 5, 0.06);
|
||||||
|
border-radius: 6px;
|
||||||
|
-webkit-transition: all 0.2s;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
border-bottom: 1px solid rgba(5, 5, 5, 0.06);
|
||||||
|
|
||||||
|
.browser-markup {
|
||||||
|
position: relative;
|
||||||
|
border-top: 2em solid rgba(230, 230, 230, 0.7);
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: -1.25em;
|
||||||
|
left: 1em;
|
||||||
|
display: block;
|
||||||
|
width: 0.5em;
|
||||||
|
height: 0.5em;
|
||||||
|
background-color: #f44;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 0 2px #f44, 1.5em 0 0 2px #9b3, 3em 0 0 2px #fb5;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
height: calc(90vh - 150px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-37
@@ -1,43 +1,34 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import Terminal, { ColorMode, LineType } from '../../components/terminal';
|
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
import { history, useOutletContext } from '@umijs/max';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import Ansi from 'ansi-to-react';
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
|
import { Alert, Typography } from 'antd';
|
||||||
|
|
||||||
const Error = () => {
|
const Error = () => {
|
||||||
const { user, theme, reloadUser } = useOutletContext<SharedContext>();
|
const { user, theme, reloadUser } = useOutletContext<SharedContext>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState('暂无日志');
|
const [data, setData] = useState('暂无日志');
|
||||||
|
const retryTimes = useRef(1);
|
||||||
const getTimes = () => {
|
|
||||||
return parseInt(localStorage.getItem('error_retry_times') || '0', 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
let times = getTimes();
|
|
||||||
|
|
||||||
const getLog = (needLoading: boolean = true) => {
|
const getLog = (needLoading: boolean = true) => {
|
||||||
needLoading && setLoading(true);
|
needLoading && setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}public/panel/log`)
|
.get(`${config.apiPrefix}public/health`)
|
||||||
.then(({ code, data }) => {
|
.then(({ error, data }) => {
|
||||||
if (code === 200) {
|
if (data?.status === 1) {
|
||||||
setData(data);
|
|
||||||
if (!data) {
|
|
||||||
times = getTimes();
|
|
||||||
if (times > 5) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localStorage.setItem('error_retry_times', `${times + 1}`);
|
if (retryTimes.current > 3) {
|
||||||
|
setData(error?.details);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
retryTimes.current += 1;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reloadUser();
|
|
||||||
getLog(false);
|
getLog(false);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally(() => needLoading && setLoading(false));
|
.finally(() => needLoading && setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -56,24 +47,39 @@ const Error = () => {
|
|||||||
<div className="error-wrapper">
|
<div className="error-wrapper">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<PageLoading />
|
<PageLoading />
|
||||||
) : data ? (
|
) : retryTimes.current > 3 ? (
|
||||||
<Terminal
|
<div className="code-box">
|
||||||
name="服务错误"
|
<div className="browser-markup"></div>
|
||||||
colorMode={theme === 'vs-dark' ? ColorMode.Dark : ColorMode.Light}
|
<Alert
|
||||||
lineData={[
|
type="error"
|
||||||
{ type: LineType.Input, value: 'pm2 logs panel' },
|
message={
|
||||||
{
|
<Typography.Title level={5} type="danger">
|
||||||
type: LineType.Output,
|
服务启动超时
|
||||||
value: (
|
</Typography.Title>
|
||||||
<pre>
|
}
|
||||||
<Ansi>{data}</Ansi>
|
description={
|
||||||
</pre>
|
<Typography.Text type="danger">
|
||||||
),
|
<div>请先按如下方式修复:</div>
|
||||||
},
|
<div>
|
||||||
]}
|
1. 宿主机执行 docker run --rm -v
|
||||||
|
/var/run/docker.sock:/var/run/docker.sock
|
||||||
|
containrrr/watchtower -cR <容器名>
|
||||||
|
</div>
|
||||||
|
<div>2. 容器内执行 ql -l check、ql -l update</div>
|
||||||
|
<div>
|
||||||
|
3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果
|
||||||
|
<Typography.Link href="https://github.com/whyour/qinglong/issues/new?assignees=&labels=&template=bug_report.yml">
|
||||||
|
提交 issue
|
||||||
|
</Typography.Link>
|
||||||
|
</div>
|
||||||
|
</Typography.Text>
|
||||||
|
}
|
||||||
|
banner
|
||||||
/>
|
/>
|
||||||
) : times > 5 ? (
|
<Typography.Paragraph code className="log">
|
||||||
<>服务启动超时,请手动进入容器执行 ql -l check 后刷新再试</>
|
{data}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<PageLoading tip="启动中,请稍后..." />
|
<PageLoading tip="启动中,请稍后..." />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -57,10 +57,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
height: 350px;
|
height: 400px;
|
||||||
|
|
||||||
.ant-steps {
|
.ant-steps {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
|
min-width: 110px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -100,53 +100,6 @@ const Initialization = () => {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '通知设置',
|
|
||||||
content: (
|
|
||||||
<Form onFinish={submitNotification} layout="vertical">
|
|
||||||
<Form.Item
|
|
||||||
label="通知方式"
|
|
||||||
name="type"
|
|
||||||
rules={[{ required: true, message: '请选择通知方式' }]}
|
|
||||||
style={{ maxWidth: 350 }}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
onChange={notificationModeChange}
|
|
||||||
placeholder="请选择通知方式"
|
|
||||||
>
|
|
||||||
{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={true}
|
|
||||||
placeholder={`请输入${x.label}`}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Button type="primary" htmlType="submit" loading={loading}>
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
<Button type="link" htmlType="button" onClick={() => next()}>
|
|
||||||
跳过
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '账户设置',
|
title: '账户设置',
|
||||||
content: (
|
content: (
|
||||||
@@ -202,6 +155,53 @@ const Initialization = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '通知设置',
|
||||||
|
content: (
|
||||||
|
<Form onFinish={submitNotification} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
label="通知方式"
|
||||||
|
name="type"
|
||||||
|
rules={[{ required: true, message: '请选择通知方式' }]}
|
||||||
|
style={{ maxWidth: 350 }}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
onChange={notificationModeChange}
|
||||||
|
placeholder="请选择通知方式"
|
||||||
|
>
|
||||||
|
{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={true}
|
||||||
|
placeholder={`请输入${x.label}`}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
<Button type="primary" htmlType="submit" loading={loading}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button type="link" htmlType="button" onClick={() => next()}>
|
||||||
|
跳过
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '完成安装',
|
title: '完成安装',
|
||||||
content: (
|
content: (
|
||||||
@@ -219,7 +219,7 @@ const Initialization = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
history.push('/login');
|
window.location.reload();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
去登录
|
去登录
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { SharedContext } from '@/layouts';
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const { Countdown } = Statistic;
|
const { Countdown } = Statistic;
|
||||||
|
const isDemoEnv = window.__ENV__DeployEnv === 'demo';
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const { reloadUser } = useOutletContext<SharedContext>();
|
const { reloadUser } = useOutletContext<SharedContext>();
|
||||||
@@ -104,6 +105,10 @@ const Login = () => {
|
|||||||
password: values.password,
|
password: values.password,
|
||||||
});
|
});
|
||||||
setTwoFactor(true);
|
setTwoFactor(true);
|
||||||
|
} else if (code === 100) {
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload();
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -169,10 +174,16 @@ const Login = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Form layout="vertical" onFinish={handleOk}>
|
<Form layout="vertical" onFinish={handleOk}>
|
||||||
<FormItem name="username" label="用户名" hasFeedback>
|
<FormItem name="username" label="用户名" hasFeedback>
|
||||||
<Input placeholder="用户名" autoFocus />
|
<Input
|
||||||
|
placeholder={`用户名${isDemoEnv ? ': admin' : ''}`}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem name="password" label="密码" hasFeedback>
|
<FormItem name="password" label="密码" hasFeedback>
|
||||||
<Input type="password" placeholder="密码" />
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder={`密码${isDemoEnv ? ': 123' : ''}`}
|
||||||
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<Row>
|
<Row>
|
||||||
{waitTime ? (
|
{waitTime ? (
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { SharedContext } from '@/layouts';
|
|||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
const isDemoEnv = window.__ENV__DeployEnv === 'demo';
|
||||||
|
|
||||||
const Setting = () => {
|
const Setting = () => {
|
||||||
const {
|
const {
|
||||||
@@ -252,6 +253,12 @@ const Setting = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDemoEnv) {
|
||||||
|
getApps();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper ql-container-wrapper-has-tab ql-setting-container"
|
className="ql-container-wrapper ql-container-wrapper-has-tab ql-setting-container"
|
||||||
@@ -275,11 +282,17 @@ const Setting = () => {
|
|||||||
tabPosition="top"
|
tabPosition="top"
|
||||||
onChange={tabChange}
|
onChange={tabChange}
|
||||||
items={[
|
items={[
|
||||||
|
...(!isDemoEnv
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
key: 'security',
|
key: 'security',
|
||||||
label: '安全设置',
|
label: '安全设置',
|
||||||
children: <SecuritySettings user={user} userChange={reloadUser} />,
|
children: (
|
||||||
|
<SecuritySettings user={user} userChange={reloadUser} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
key: 'app',
|
key: 'app',
|
||||||
label: '应用设置',
|
label: '应用设置',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const handleOk = (values: any) => {
|
const handleOk = (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
const { type } = values;
|
const { type } = values;
|
||||||
if (type == 'closed') {
|
if (type == 'closed') {
|
||||||
values.type = '';
|
values.type = '';
|
||||||
@@ -30,7 +31,8 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const notificationModeChange = (value: string) => {
|
const notificationModeChange = (value: string) => {
|
||||||
@@ -56,7 +58,7 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
style={{ maxWidth: 400 }}
|
style={{ maxWidth: 400 }}
|
||||||
initialValue={notificationMode}
|
initialValue={notificationMode}
|
||||||
>
|
>
|
||||||
<Select onChange={notificationModeChange}>
|
<Select onChange={notificationModeChange} disabled={loading}>
|
||||||
{config.notificationModes.map((x) => (
|
{config.notificationModes.map((x) => (
|
||||||
<Option key={x.value} value={x.value}>
|
<Option key={x.value} value={x.value}>
|
||||||
{x.label}
|
{x.label}
|
||||||
@@ -74,7 +76,10 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
style={{ maxWidth: 400 }}
|
style={{ maxWidth: 400 }}
|
||||||
>
|
>
|
||||||
{x.items ? (
|
{x.items ? (
|
||||||
<Select placeholder={x.placeholder || `请选择${x.label}`}>
|
<Select
|
||||||
|
placeholder={x.placeholder || `请选择${x.label}`}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
{x.items.map((y) => (
|
{x.items.map((y) => (
|
||||||
<Option key={y.value} value={y.value}>
|
<Option key={y.value} value={y.value}>
|
||||||
{y.label || y.value}
|
{y.label || y.value}
|
||||||
@@ -83,14 +88,15 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
</Select>
|
</Select>
|
||||||
) : (
|
) : (
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
|
disabled={loading}
|
||||||
autoSize={true}
|
autoSize={true}
|
||||||
placeholder={x.placeholder || `请输入${x.label}`}
|
placeholder={x.placeholder || `请输入${x.label}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit" disabled={loading}>
|
||||||
保存
|
{loading ? '测试中...' : '保存'}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+32
-15
@@ -19,7 +19,10 @@ const Other = ({
|
|||||||
reloadTheme,
|
reloadTheme,
|
||||||
}: Pick<SharedContext, 'socketMessage' | 'reloadTheme' | 'systemInfo'>) => {
|
}: Pick<SharedContext, 'socketMessage' | 'reloadTheme' | 'systemInfo'>) => {
|
||||||
const defaultTheme = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
const defaultTheme = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
||||||
const [logRemoveFrequency, setLogRemoveFrequency] = useState<number | null>();
|
const [systemConfig, setSystemConfig] = useState<{
|
||||||
|
logRemoveFrequency?: number | null;
|
||||||
|
cronConcurrency?: number | null;
|
||||||
|
}>();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -45,13 +48,12 @@ const Other = ({
|
|||||||
reloadTheme();
|
reloadTheme();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLogRemoveFrequency = () => {
|
const getSystemConfig = () => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}system/log/remove`)
|
.get(`${config.apiPrefix}system/config`)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200 && data.info) {
|
if (code === 200 && data.info) {
|
||||||
const { frequency } = data.info;
|
setSystemConfig(data.info);
|
||||||
setLogRemoveFrequency(frequency);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -59,11 +61,10 @@ const Other = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRemoveLogFrequency = () => {
|
const updateSystemConfig = () => {
|
||||||
setTimeout(() => {
|
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}system/log/remove`, {
|
.put(`${config.apiPrefix}system/config`, {
|
||||||
data: { frequency: logRemoveFrequency },
|
data: { ...systemConfig },
|
||||||
})
|
})
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
@@ -73,11 +74,10 @@ const Other = ({
|
|||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLogRemoveFrequency();
|
getSystemConfig();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -100,12 +100,29 @@ const Other = ({
|
|||||||
<InputNumber
|
<InputNumber
|
||||||
addonBefore="每"
|
addonBefore="每"
|
||||||
addonAfter="天"
|
addonAfter="天"
|
||||||
style={{ width: 150 }}
|
style={{ width: 142 }}
|
||||||
min={0}
|
min={0}
|
||||||
value={logRemoveFrequency}
|
value={systemConfig?.logRemoveFrequency}
|
||||||
onChange={(value) => setLogRemoveFrequency(value)}
|
onChange={(value) => {
|
||||||
|
setSystemConfig({ ...systemConfig, logRemoveFrequency: value });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Button type="primary" onClick={updateRemoveLogFrequency}>
|
<Button type="primary" onClick={updateSystemConfig}>
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="定时任务并发数" name="frequency">
|
||||||
|
<Input.Group compact>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: 142 }}
|
||||||
|
min={1}
|
||||||
|
value={systemConfig?.cronConcurrency}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSystemConfig({ ...systemConfig, cronConcurrency: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type="primary" onClick={updateSystemConfig}>
|
||||||
确认
|
确认
|
||||||
</Button>
|
</Button>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
|
|||||||
+12
-2
@@ -1,6 +1,8 @@
|
|||||||
|
const baseUrl = window.__ENV__QlBaseUrl || '/';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
siteName: '青龙',
|
siteName: '青龙',
|
||||||
apiPrefix: '/api/',
|
apiPrefix: `${baseUrl}api/`,
|
||||||
authKey: 'token',
|
authKey: 'token',
|
||||||
|
|
||||||
/* Layout configuration, specify which layout to use for route. */
|
/* Layout configuration, specify which layout to use for route. */
|
||||||
@@ -91,7 +93,7 @@ export default {
|
|||||||
{ value: 'aibotk', label: '智能微秘书' },
|
{ value: 'aibotk', label: '智能微秘书' },
|
||||||
{ value: 'iGot', label: 'IGot' },
|
{ value: 'iGot', label: 'IGot' },
|
||||||
{ value: 'pushPlus', label: 'PushPlus' },
|
{ value: 'pushPlus', label: 'PushPlus' },
|
||||||
{ value: 'chat', label: '群辉chat' },
|
{ value: 'chat', label: '群晖chat' },
|
||||||
{ value: 'email', label: '邮箱' },
|
{ value: 'email', label: '邮箱' },
|
||||||
{ value: 'lark', label: '飞书机器人' },
|
{ value: 'lark', label: '飞书机器人' },
|
||||||
{ value: 'webhook', label: '自定义通知' },
|
{ value: 'webhook', label: '自定义通知' },
|
||||||
@@ -194,6 +196,10 @@ export default {
|
|||||||
tip: '企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa',
|
tip: '企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'weWorkOrigin',
|
||||||
|
tip: '企业微信代理地址',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
weWorkApp: [
|
weWorkApp: [
|
||||||
{
|
{
|
||||||
@@ -201,6 +207,10 @@ export default {
|
|||||||
tip: 'corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号),例如:wwcfrs,B-76WERQ,qinglong,1000001,2COat',
|
tip: 'corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号),例如:wwcfrs,B-76WERQ,qinglong,1000001,2COat',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'weWorkOrigin',
|
||||||
|
tip: '企业微信代理地址',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
aibotk: [
|
aibotk: [
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ import browserType from './index';
|
|||||||
export const useCtx = () => {
|
export const useCtx = () => {
|
||||||
const [width, setWidth] = useState('100%');
|
const [width, setWidth] = useState('100%');
|
||||||
const [marginLeft, setMarginLeft] = useState(0);
|
const [marginLeft, setMarginLeft] = useState(0);
|
||||||
const [marginTop, setMarginTop] = useState(-72);
|
const [marginTop, setMarginTop] = useState(-48);
|
||||||
const [isPhone, setIsPhone] = useState(false);
|
const [isPhone, setIsPhone] = useState(false);
|
||||||
const { platform } = useMemo(() => browserType(), []);
|
const { platform } = useMemo(() => browserType(), []);
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export const useCtx = () => {
|
|||||||
} else {
|
} else {
|
||||||
setWidth('100%');
|
setWidth('100%');
|
||||||
setMarginLeft(0);
|
setMarginLeft(0);
|
||||||
setMarginTop(-72);
|
setMarginTop(-48);
|
||||||
setIsPhone(false);
|
setIsPhone(false);
|
||||||
document.body.setAttribute('data-mode', 'desktop');
|
document.body.setAttribute('data-mode', 'desktop');
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -4,7 +4,7 @@ import config from './config';
|
|||||||
import { history } from '@umijs/max';
|
import { history } from '@umijs/max';
|
||||||
|
|
||||||
message.config({
|
message.config({
|
||||||
duration: 1.5,
|
duration: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
@@ -23,7 +23,10 @@ const errorHandler = function (error: any) {
|
|||||||
history.push('/login');
|
history.push('/login');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error(msg);
|
message.error({
|
||||||
|
content: msg,
|
||||||
|
style: { maxWidth: 500, margin: '0 auto' },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(error.message);
|
console.log(error.message);
|
||||||
@@ -66,7 +69,11 @@ _request.interceptors.response.use(async (response) => {
|
|||||||
const res = await response.clone().json();
|
const res = await response.clone().json();
|
||||||
if (res.code !== 200) {
|
if (res.code !== 200) {
|
||||||
const msg = res.message || res.data;
|
const msg = res.message || res.data;
|
||||||
msg && message.error(msg);
|
msg &&
|
||||||
|
message.error({
|
||||||
|
content: msg,
|
||||||
|
style: { maxWidth: 500, margin: '0 auto' },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
@@ -10,3 +10,8 @@ declare module '*.svg' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module 'pstree.remy';
|
declare module 'pstree.remy';
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
__ENV__QlBaseUrl: string;
|
||||||
|
__ENV__DeployEnv: string;
|
||||||
|
}
|
||||||
|
|||||||
+5
-8
@@ -1,9 +1,6 @@
|
|||||||
version: 2.15.12
|
version: 2.15.17
|
||||||
changeLogLink: https://t.me/jiao_long/368
|
changeLogLink: https://t.me/jiao_long/383
|
||||||
changeLog: |
|
changeLog: |
|
||||||
1. 修复定时任务筛选
|
1. 系统设置增加定时任务并发数设置
|
||||||
2. 修复更新环境变量、定时任务、订阅,状态被重置
|
2. 修改默认并发数
|
||||||
3. 重构六位定时服务
|
3. 修改更新仓库逻辑
|
||||||
4. 修改手机端页面样式
|
|
||||||
5. 修改依赖安装流程
|
|
||||||
6. 其他bug修复
|
|
||||||
|
|||||||
Reference in New Issue
Block a user