mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 03:10:48 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
db564d1add | ||
|
|
4f422c0658 | ||
|
|
f267720af4 | ||
|
|
4a0c66bcc0 | ||
|
|
ef7283a9fd | ||
|
|
4498449eea | ||
|
|
ebb9676a51 | ||
|
|
6fb39ce835 | ||
|
|
59c26d90d3 | ||
|
|
1d2df860e8 | ||
|
|
ce22cad5b2 | ||
|
|
dc44ce6b1a | ||
|
|
b27ee23cc3 | ||
|
|
083c8869aa | ||
|
|
6a971a0d6e | ||
|
|
a25bfb6912 | ||
|
|
f19dd21155 | ||
|
|
79b342d1d2 | ||
|
|
20b0d75b0d | ||
|
|
c89724906e | ||
|
|
c3a548ece3 | ||
|
|
b431e39f8f | ||
|
|
710746809b | ||
|
|
8a225e9beb | ||
|
|
a5ecb204b2 | ||
|
|
f1f009da3b | ||
|
|
7291098f98 | ||
|
|
666545ff03 | ||
|
|
81e2bbb14c | ||
|
|
970dff144c | ||
|
|
819b15b15e | ||
|
|
3fa57c69ac | ||
|
|
18689c8119 | ||
|
|
61e7049baa | ||
|
|
39c40a328d | ||
|
|
955d815d14 | ||
|
|
3a3b945de8 | ||
|
|
73c53dbc8d | ||
|
|
f76828d4ec | ||
|
|
7b2956eb0a | ||
|
|
b2167ae5e4 | ||
|
|
98dfa4c425 | ||
|
|
5bf08b76de | ||
|
|
5c5b4f804e | ||
|
|
6455245d11 | ||
|
|
2ff3d9c601 | ||
|
|
d342fbf29f | ||
|
|
f865018cd6 | ||
|
|
3b9a4f0834 | ||
|
|
67bc305950 | ||
|
|
97f128d59f | ||
|
|
e25885c4d9 | ||
|
|
c285026339 | ||
|
|
6c34045a48 | ||
|
|
e1e6261b4f | ||
|
|
697bcb5922 | ||
|
|
e05b0a2491 | ||
|
|
e1655455d8 | ||
|
|
9921dab064 | ||
|
|
403ab7f0f0 | ||
|
|
9bc7ca4094 | ||
|
|
e924aa76b0 | ||
|
|
0c46606e14 | ||
|
|
dbfa4049f5 | ||
|
|
622fe2a8f8 | ||
|
|
7bce5c4f6a | ||
|
|
1f7f2c8971 | ||
|
|
437a849c2a | ||
|
|
8f7261dd50 | ||
|
|
76fa82c3a7 | ||
|
|
46c9774efa | ||
|
|
1bc64a23e3 | ||
|
|
cc9a17afa3 | ||
|
|
fcbcfc3a03 | ||
|
|
0144f3b131 | ||
|
|
518436d5ca | ||
|
|
1961a6b913 | ||
|
|
5edebdd5f3 | ||
|
|
48edf2c212 | ||
|
|
9b16dca9da | ||
|
|
9479c2498c | ||
|
|
4a18bcabe2 | ||
|
|
34f690c53d | ||
|
|
e70cd6579f | ||
|
|
99c047e9c7 | ||
|
|
978ae326f8 | ||
|
|
b01f27f2d3 | ||
|
|
4d34e0dd96 | ||
|
|
9b0065e533 | ||
|
|
34a51feee9 | ||
|
|
679091de59 | ||
|
|
c6220cb920 | ||
|
|
633b30269c | ||
|
|
8ad26267d8 | ||
|
|
7cfb125682 | ||
|
|
a2dc5de5ce | ||
|
|
4f41ff29ec | ||
|
|
13d95b8c19 | ||
|
|
335c13ab44 | ||
|
|
015cdc10a7 | ||
|
|
a5075ab28f | ||
|
|
2e3c3274c7 | ||
|
|
443c581955 | ||
|
|
5e41faf590 | ||
|
|
0579fb83d3 | ||
|
|
2864845d5b | ||
|
|
659dddace7 | ||
|
|
b096f30558 | ||
|
|
dcfb72143a | ||
|
|
c63bcd7706 | ||
|
|
bac306f9b3 | ||
|
|
a6fd96e97b | ||
|
|
7edd91f923 | ||
|
|
66df445bd5 | ||
|
|
5c03034bb4 |
@@ -10,3 +10,4 @@ SECRET='whyour'
|
||||
QINIU_AK=''
|
||||
QINIU_SK=''
|
||||
QINIU_SCOPE=''
|
||||
TEMP=''
|
||||
|
||||
@@ -5,15 +5,11 @@ on:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'develop'
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
schedule:
|
||||
# 参考 https://jasonet.co/posts/scheduled-actions/
|
||||
# note: 这里是GMT时间,北京时间减去八小时即可。如北京时间 22:30 => GMT 14:30
|
||||
# minute hour day month dayOfWeek
|
||||
- cron: '00 14 * * *' # GMT 14:00 => 北京时间 22:00
|
||||
#- cron: '30 16 * * *' # GMT 16:30(前一天) => 北京时间 00:30
|
||||
- cron: '00 14 * * *' # GMT 15:00 => 北京时间 23:00
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -49,7 +45,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
version: '8.3.1'
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -89,6 +85,8 @@ jobs:
|
||||
git push --force --quiet gitlab ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
||||
|
||||
build:
|
||||
if: github.ref != 'refs/heads/master'
|
||||
|
||||
needs: build-static
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -101,7 +99,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
version: '8.3.1'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
@@ -155,12 +153,16 @@ jobs:
|
||||
build-args: |
|
||||
MAINTAINER=${{ github.repository_owner }}
|
||||
QL_BRANCH=${{ github.ref_name }}
|
||||
SOURCE_COMMIT=${{ github.sha }}
|
||||
network: host
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
context: docker/
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x,linux/386
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=whyour/qinglong:cache
|
||||
cache-to: type=registry,ref=whyour/qinglong:cache,mode=max
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
|
||||
@@ -29,4 +29,3 @@
|
||||
/manual_log
|
||||
/scripts
|
||||
/bak
|
||||
node_modules
|
||||
@@ -6,14 +6,16 @@ export default defineConfig({
|
||||
antd: {},
|
||||
outputPath: 'static/dist',
|
||||
fastRefresh: true,
|
||||
favicons: ['/images/favicon.svg'],
|
||||
favicons: ['./images/favicon.svg'],
|
||||
mfsu: {
|
||||
strategy: 'eager',
|
||||
},
|
||||
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
||||
proxy: {
|
||||
'/api/public': {
|
||||
target: 'http://127.0.0.1:5400/',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^/api/public': '/api/' },
|
||||
},
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:5600/',
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
Timed task management panel with python3, javaScript, shell, typescript support
|
||||
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]
|
||||
|
||||
@@ -22,7 +22,7 @@ Timed task management panel with python3, javaScript, shell, typescript support
|
||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||
</div>
|
||||
|
||||
[](https://whyour.cn)
|
||||
[](https://whyour.cn)
|
||||
|
||||
[简体中文](./README.md) | English
|
||||
|
||||
@@ -198,7 +198,7 @@ $ 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
|
||||
$ npm install -g pnpm@8.3.1
|
||||
$ pnpm install
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
支持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]
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||
</div>
|
||||
|
||||
[](https://whyour.cn)
|
||||
[](https://whyour.cn)
|
||||
|
||||
简体中文 | [English](./README-en.md)
|
||||
|
||||
@@ -73,7 +73,6 @@ sudo curl -sSL get.docker.com | sh
|
||||
```
|
||||
|
||||
2. 配置国内镜像源
|
||||
Configure domestic mirror sources
|
||||
|
||||
```bash
|
||||
mkdir -p /etc/docker
|
||||
@@ -198,7 +197,7 @@ $ git clone git@github.com:whyour/qinglong.git
|
||||
$ cd qinglong
|
||||
$ cp .env.example .env
|
||||
# 推荐使用 pnpm https://pnpm.io/zh/installation
|
||||
$ npm install -g pnpm
|
||||
$ npm install -g pnpm@8.3.1
|
||||
$ pnpm install
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Logger } from 'winston';
|
||||
import config from '../config';
|
||||
import * as fs from 'fs';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import { join } from 'path';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
@@ -41,11 +42,11 @@ export default (app: Router) => {
|
||||
}
|
||||
if (req.params.file.includes('sample')) {
|
||||
content = getFileContentByName(
|
||||
`${config.samplePath}${req.params.file}`,
|
||||
join(config.samplePath, req.params.file),
|
||||
);
|
||||
} else {
|
||||
content = getFileContentByName(
|
||||
`${config.configPath}${req.params.file}`,
|
||||
join(config.configPath, req.params.file),
|
||||
);
|
||||
}
|
||||
res.send({ code: 200, data: content });
|
||||
@@ -70,7 +71,7 @@ export default (app: Router) => {
|
||||
if (config.blackFileList.includes(name)) {
|
||||
res.send({ code: 403, message: '文件无法访问' });
|
||||
}
|
||||
const path = `${config.configPath}${name}`;
|
||||
const path = join(config.configPath, name);
|
||||
fs.writeFileSync(path, content);
|
||||
res.send({ code: 200, message: '保存成功' });
|
||||
} catch (e) {
|
||||
@@ -152,6 +152,21 @@ export default (app: Router) => {
|
||||
}
|
||||
});
|
||||
|
||||
route.get(
|
||||
'/detail',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.find(req.query as any);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.post(
|
||||
'/',
|
||||
celebrate({
|
||||
@@ -160,6 +175,7 @@ export default (app: Router) => {
|
||||
schedule: Joi.string().required(),
|
||||
name: Joi.string().optional(),
|
||||
labels: Joi.array().optional(),
|
||||
sub_id: Joi.number().optional().allow(null),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -316,6 +332,7 @@ export default (app: Router) => {
|
||||
command: Joi.string().required(),
|
||||
schedule: Joi.string().required(),
|
||||
name: Joi.string().optional().allow(null),
|
||||
sub_id: Joi.number().optional().allow(null),
|
||||
id: Joi.number().required(),
|
||||
}),
|
||||
}),
|
||||
@@ -53,7 +53,7 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().allow(''),
|
||||
type: Joi.string().optional(),
|
||||
type: Joi.string().optional()
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -75,4 +75,5 @@ export default (app: Router) => {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
};
|
||||
@@ -94,7 +94,7 @@ export default (app: Router) => {
|
||||
path += '/';
|
||||
}
|
||||
if (!path.startsWith('/')) {
|
||||
path = `${config.scriptPath}${path}`;
|
||||
path = join(config.scriptPath, path);
|
||||
}
|
||||
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
||||
return res.send({
|
||||
@@ -124,7 +124,7 @@ export default (app: Router) => {
|
||||
if (fs.existsSync(originFilePath)) {
|
||||
fs.copyFileSync(
|
||||
originFilePath,
|
||||
`${config.bakPath}${originFilename.replace(/\//g, '')}`,
|
||||
join(config.bakPath, originFilename.replace(/\//g, '')),
|
||||
);
|
||||
if (filename !== originFilename) {
|
||||
fs.unlinkSync(originFilePath);
|
||||
@@ -207,7 +207,7 @@ export default (app: Router) => {
|
||||
let { filename } = req.body as {
|
||||
filename: string;
|
||||
};
|
||||
const filePath = `${config.scriptPath}${filename}`;
|
||||
const filePath = join(config.scriptPath, filename);
|
||||
// const stats = fs.statSync(filePath);
|
||||
// res.set({
|
||||
// 'Content-Type': 'application/octet-stream', //告诉浏览器这是一个二进制文件
|
||||
@@ -50,6 +50,8 @@ export default (app: Router) => {
|
||||
schedule_type: Joi.string().required(),
|
||||
alias: Joi.string().required(),
|
||||
proxy: Joi.string().optional().allow('').allow(null),
|
||||
autoAddCron: Joi.boolean().optional().allow('').allow(null),
|
||||
autoDelCron: Joi.boolean().optional().allow('').allow(null),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -179,6 +181,8 @@ export default (app: Router) => {
|
||||
sub_after: Joi.string().optional().allow('').allow(null),
|
||||
alias: Joi.string().required(),
|
||||
proxy: Joi.string().optional().allow('').allow(null),
|
||||
autoAddCron: Joi.boolean().optional().allow('').allow(null),
|
||||
autoDelCron: Joi.boolean().optional().allow('').allow(null),
|
||||
id: Joi.number().required(),
|
||||
}),
|
||||
}),
|
||||
@@ -7,7 +7,12 @@ import SystemService from '../services/system';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import UserService from '../services/user';
|
||||
import { EnvModel } from '../data/env';
|
||||
import { parseVersion, promiseExec } from '../config/util';
|
||||
import {
|
||||
getUniqPath,
|
||||
handleLogPath,
|
||||
parseVersion,
|
||||
promiseExec,
|
||||
} from '../config/util';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const route = Router();
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,7 @@ async function startServer() {
|
||||
const server = app
|
||||
.listen(config.port, () => {
|
||||
Logger.debug(`✌️ 后端服务启动成功!`);
|
||||
process.send?.('ready');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
@@ -0,0 +1,7 @@
|
||||
export const LOG_END_SYMBOL = ' ';
|
||||
|
||||
export const TASK_COMMAND = 'task';
|
||||
export const QL_COMMAND = 'ql';
|
||||
|
||||
export const TASK_PREFIX = `${TASK_COMMAND} `;
|
||||
export const QL_PREFIX = `${QL_COMMAND} `;
|
||||
@@ -6,7 +6,7 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
if (!process.env.QL_DIR) {
|
||||
// 声明QL_DIR环境变量
|
||||
let qlHomePath = path.join(__dirname, '../../../../');
|
||||
let qlHomePath = path.join(__dirname, '../../');
|
||||
// 生产环境
|
||||
if (qlHomePath.endsWith('/static/')) {
|
||||
qlHomePath = path.join(qlHomePath, '../');
|
||||
@@ -27,6 +27,7 @@ const bakPath = path.join(dataPath, 'bak/');
|
||||
const logPath = path.join(dataPath, 'log/');
|
||||
const dbPath = path.join(dataPath, 'db/');
|
||||
const uploadPath = path.join(dataPath, 'upload/');
|
||||
const sshdPath = path.join(dataPath, 'ssh.d/');
|
||||
|
||||
const envFile = path.join(configPath, 'env.sh');
|
||||
const confFile = path.join(configPath, 'config.sh');
|
||||
@@ -95,4 +96,5 @@ export default {
|
||||
versionFile,
|
||||
lastVersionFile,
|
||||
sqliteFile,
|
||||
sshdPath,
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Subscription } from '../data/subscription';
|
||||
import isNil from 'lodash/isNil';
|
||||
|
||||
export function formatUrl(doc: Subscription) {
|
||||
let url = doc.url;
|
||||
let host = '';
|
||||
if (doc.type === 'private-repo') {
|
||||
if (doc.pull_type === 'ssh-key') {
|
||||
host = doc.url!.replace(/.*\@([^\:]+)\:.*/, '$1');
|
||||
url = doc.url!.replace(host, doc.alias);
|
||||
} else {
|
||||
host = doc.url!.replace(/.*\:\/\/([^\/]+)\/.*/, '$1');
|
||||
const { username, password } = doc.pull_option as any;
|
||||
url = doc.url!.replace(host, `${username}:${password}@${host}`);
|
||||
}
|
||||
}
|
||||
return { url, host };
|
||||
}
|
||||
|
||||
export function formatCommand(doc: Subscription, url?: string) {
|
||||
let command = `SUB_ID=${doc.id} ql `;
|
||||
let _url = url || formatUrl(doc).url;
|
||||
const {
|
||||
type,
|
||||
whitelist,
|
||||
blacklist,
|
||||
dependences,
|
||||
branch,
|
||||
extensions,
|
||||
proxy,
|
||||
autoAddCron,
|
||||
autoDelCron,
|
||||
} = doc;
|
||||
if (type === 'file') {
|
||||
command += `raw "${_url}"`;
|
||||
} else {
|
||||
command += `repo "${_url}" "${whitelist || ''}" "${blacklist || ''}" "${dependences || ''
|
||||
}" "${branch || ''}" "${extensions || ''}" "${proxy || ''}" "${isNil(autoAddCron) ? true : Boolean(autoAddCron)
|
||||
}" "${isNil(autoDelCron) ? true : Boolean(autoDelCron)}"`;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import FormData from 'form-data';
|
||||
import psTreeFun from 'pstree.remy';
|
||||
import { promisify } from 'util';
|
||||
import { load } from 'js-yaml';
|
||||
import config from './index';
|
||||
import { TASK_COMMAND } from './const';
|
||||
|
||||
export function getFileContentByName(fileName: string) {
|
||||
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(
|
||||
fnList: Array<() => Promise<any>> = [],
|
||||
max = 5,
|
||||
@@ -469,19 +483,22 @@ export function psTree(pid: number): Promise<number[]> {
|
||||
|
||||
export async function killTask(pid: number) {
|
||||
const pids = await psTree(pid);
|
||||
// SIGALRM 14 时钟信号
|
||||
// SIGINT 2 程序终止(interrupt)信号,不会打印额外信息
|
||||
if (pids.length) {
|
||||
process.kill(pids[0], 14);
|
||||
try {
|
||||
[pid, ...pids].forEach((x) => {
|
||||
process.kill(x, 2);
|
||||
});
|
||||
} catch (error) {}
|
||||
} else {
|
||||
process.kill(pid, 14);
|
||||
process.kill(pid, 2);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPid(name: string) {
|
||||
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
|
||||
const execAsync = promisify(exec);
|
||||
let pid = (await execAsync(taskCommand)).stdout;
|
||||
return Number(pid);
|
||||
const taskCommand = `ps -eo pid,command | grep "${name}" | grep -v grep | awk '{print $1}' | head -1 | xargs echo -n`;
|
||||
const pid = await promiseExec(taskCommand);
|
||||
return pid ? Number(pid) : undefined;
|
||||
}
|
||||
|
||||
interface IVersion {
|
||||
@@ -497,3 +514,40 @@ export async function parseVersion(path: string): Promise<IVersion> {
|
||||
export async function parseContentVersion(content: string): Promise<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}`;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export class Crontab {
|
||||
labels?: string[];
|
||||
last_running_time?: number;
|
||||
last_execution_time?: number;
|
||||
sub_id?: number;
|
||||
|
||||
constructor(options: Crontab) {
|
||||
this.name = options.name;
|
||||
@@ -25,7 +26,7 @@ export class Crontab {
|
||||
this.saved = options.saved;
|
||||
this.id = options.id;
|
||||
this.status =
|
||||
options.status && CrontabStatus[options.status]
|
||||
typeof options.status === 'number' && CrontabStatus[options.status]
|
||||
? options.status
|
||||
: CrontabStatus.idle;
|
||||
this.timestamp = new Date().toString();
|
||||
@@ -37,6 +38,7 @@ export class Crontab {
|
||||
this.labels = options.labels || [];
|
||||
this.last_running_time = options.last_running_time || 0;
|
||||
this.last_execution_time = options.last_execution_time || 0;
|
||||
this.sub_id = options.sub_id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +74,5 @@ export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
|
||||
labels: DataTypes.JSON,
|
||||
last_running_time: DataTypes.NUMBER,
|
||||
last_execution_time: DataTypes.NUMBER,
|
||||
sub_id: { type: DataTypes.NUMBER, allowNull: true },
|
||||
});
|
||||
@@ -12,7 +12,10 @@ export class Dependence {
|
||||
|
||||
constructor(options: Dependence) {
|
||||
this.id = options.id;
|
||||
this.status = options.status || DependenceStatus.installing;
|
||||
this.status =
|
||||
typeof options.status === 'number' && DependenceStatus[options.status]
|
||||
? options.status
|
||||
: DependenceStatus.installing;
|
||||
this.type = options.type || DependenceTypes.nodejs;
|
||||
this.timestamp = new Date().toString();
|
||||
this.name = options.name;
|
||||
@@ -28,6 +31,7 @@ export enum DependenceStatus {
|
||||
'removing',
|
||||
'removed',
|
||||
'removeFailed',
|
||||
'queued',
|
||||
}
|
||||
|
||||
export enum DependenceTypes {
|
||||
@@ -13,7 +13,10 @@ export class Env {
|
||||
constructor(options: Env) {
|
||||
this.value = options.value;
|
||||
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.position = options.position;
|
||||
this.name = options.name;
|
||||
@@ -28,7 +31,7 @@ export enum EnvStatus {
|
||||
|
||||
export const maxPosition = 9000000000000000;
|
||||
export const initPosition = 4500000000000000;
|
||||
export const stepPosition = 10000000;
|
||||
export const stepPosition = 10000000000;
|
||||
export const minPosition = 100;
|
||||
|
||||
interface EnvInstance extends Model<Env, Env>, Env {}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Sequelize, Transaction } from 'sequelize';
|
||||
import config from '../config/index';
|
||||
import { join } from 'path';
|
||||
|
||||
export const sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: `${config.dbPath}database.sqlite`,
|
||||
storage: join(config.dbPath, 'database.sqlite'),
|
||||
logging: false,
|
||||
retry: {
|
||||
max: 10,
|
||||
@@ -29,14 +29,16 @@ export class Subscription {
|
||||
sub_before?: string;
|
||||
sub_after?: string;
|
||||
proxy?: string;
|
||||
autoAddCron?: 1 | 0;
|
||||
autoDelCron?: 1 | 0;
|
||||
|
||||
constructor(options: Subscription) {
|
||||
this.id = options.id;
|
||||
this.name = options.name || options.alias;
|
||||
this.type = options.type;
|
||||
this.schedule = options.schedule;
|
||||
this.status =
|
||||
options.status && SubscriptionStatus[options.status]
|
||||
this.status = this.status =
|
||||
typeof options.status === 'number' && SubscriptionStatus[options.status]
|
||||
? options.status
|
||||
: SubscriptionStatus.idle;
|
||||
this.url = options.url;
|
||||
@@ -56,6 +58,8 @@ export class Subscription {
|
||||
this.sub_before = options.sub_before;
|
||||
this.sub_after = options.sub_after;
|
||||
this.proxy = options.proxy;
|
||||
this.autoAddCron = options.autoAddCron ? 1 : 0;
|
||||
this.autoDelCron = options.autoDelCron ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,5 +109,7 @@ export const SubscriptionModel = sequelize.define<SubscriptionInstance>(
|
||||
schedule_type: DataTypes.STRING,
|
||||
alias: { type: DataTypes.STRING, unique: 'alias' },
|
||||
proxy: { type: DataTypes.STRING, allowNull: true },
|
||||
autoAddCron: { type: DataTypes.NUMBER, allowNull: true },
|
||||
autoDelCron: { type: DataTypes.NUMBER, allowNull: true },
|
||||
},
|
||||
);
|
||||
@@ -36,6 +36,19 @@ export default async () => {
|
||||
try {
|
||||
await sequelize.query('alter table CrontabViews add column type NUMBER');
|
||||
} catch (error) {}
|
||||
try {
|
||||
await sequelize.query(
|
||||
'alter table Subscriptions add column autoAddCron NUMBER',
|
||||
);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await sequelize.query(
|
||||
'alter table Subscriptions add column autoDelCron NUMBER',
|
||||
);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await sequelize.query('alter table Crontabs add column sub_id NUMBER');
|
||||
} catch (error) {}
|
||||
|
||||
// 2.10-2.11 升级
|
||||
const cronDbFile = path.join(config.rootPath, 'db/crontab.db');
|
||||
@@ -14,19 +14,41 @@ import handler from 'serve-handler';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { EnvModel } from '../data/env';
|
||||
import { errors } from 'celebrate';
|
||||
import path from 'path';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
|
||||
export default ({ app }: { app: Application }) => {
|
||||
app.enable('trust proxy');
|
||||
app.use(cors());
|
||||
app.use(`${config.api.prefix}/static`, express.static(config.uploadPath));
|
||||
|
||||
app.use(
|
||||
'/api/public',
|
||||
createProxyMiddleware({
|
||||
target: `http://localhost:${config.publicPort}/api`,
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '/api/public': '' },
|
||||
}),
|
||||
);
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.path.startsWith('/api') || req.path.startsWith('/open')) {
|
||||
next();
|
||||
} else {
|
||||
return handler(req, res, {
|
||||
public: 'static/dist',
|
||||
public: path.join(config.rootPath, 'static/dist'),
|
||||
rewrites: [{ source: '**', destination: '/index.html' }],
|
||||
headers: [
|
||||
{
|
||||
source: 'index.html',
|
||||
headers: [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: 'no-cache',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -4,8 +4,7 @@ import { Container } from 'typedi';
|
||||
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
|
||||
import CronService from '../services/cron';
|
||||
import EnvService from '../services/env';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { DependenceModel } from '../data/dependence';
|
||||
import { DependenceModel, DependenceStatus } from '../data/dependence';
|
||||
import { Op } from 'sequelize';
|
||||
import config from '../config';
|
||||
import { CrontabViewModel, CronViewType } from '../data/cronView';
|
||||
@@ -42,16 +41,14 @@ export default async () => {
|
||||
order: [['type', 'DESC']],
|
||||
raw: true,
|
||||
}).then(async (docs) => {
|
||||
const groups = groupBy(docs, 'type');
|
||||
const keys = Object.keys(groups).sort((a, b) => parseInt(b) - parseInt(a));
|
||||
for (const key of keys) {
|
||||
const group = groups[key];
|
||||
const depIds = group.map((x) => x.id);
|
||||
await dependenceService.reInstall(depIds as number[]);
|
||||
}
|
||||
await DependenceModel.update(
|
||||
{ status: DependenceStatus.queued, log: [] },
|
||||
{ where: { id: docs.map((x) => x.id!) } },
|
||||
);
|
||||
dependenceService.installDependenceOneByOne(docs);
|
||||
});
|
||||
|
||||
// 初始化时执行一次所有的ql repo 任务
|
||||
// 初始化时执行一次所有的 ql repo 任务
|
||||
CrontabModel.findAll({
|
||||
where: {
|
||||
isDisabled: { [Op.ne]: 1 },
|
||||
@@ -4,7 +4,8 @@ import os from 'os';
|
||||
import dotenv from 'dotenv';
|
||||
import Logger from './logger';
|
||||
import { fileExist } from '../config/util';
|
||||
const rootPath = process.env.QL_DIR as string;
|
||||
|
||||
const rootPath = process.env.QL_DIR as string;;
|
||||
const dataPath = path.join(rootPath, 'data/');
|
||||
const configPath = path.join(dataPath, 'config/');
|
||||
const scriptPath = path.join(dataPath, 'scripts/');
|
||||
@@ -18,6 +19,7 @@ const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
|
||||
const sampleAuthFile = path.join(samplePath, 'auth.sample.json');
|
||||
const homedir = os.homedir();
|
||||
const sshPath = path.resolve(homedir, '.ssh');
|
||||
const sshdPath = path.join(dataPath, 'ssh.d');
|
||||
|
||||
export default async () => {
|
||||
const authFileExist = await fileExist(authConfigFile);
|
||||
@@ -28,6 +30,7 @@ export default async () => {
|
||||
const uploadDirExist = await fileExist(uploadPath);
|
||||
const sshDirExist = await fileExist(sshPath);
|
||||
const bakDirExist = await fileExist(bakPath);
|
||||
const sshdDirExist = await fileExist(sshdPath);
|
||||
|
||||
if (!configDirExist) {
|
||||
fs.mkdirSync(configPath);
|
||||
@@ -61,6 +64,10 @@ export default async () => {
|
||||
fs.mkdirSync(bakPath);
|
||||
}
|
||||
|
||||
if (!sshdDirExist) {
|
||||
fs.mkdirSync(sshdPath);
|
||||
}
|
||||
|
||||
dotenv.config({ path: confFile });
|
||||
|
||||
Logger.info('✌️ Init file down');
|
||||
@@ -4,6 +4,7 @@ import ScheduleService from '../services/schedule';
|
||||
import SubscriptionService from '../services/subscription';
|
||||
import config from '../config';
|
||||
import { fileExist } from '../config/util';
|
||||
import { join } from 'path';
|
||||
|
||||
export default async () => {
|
||||
const systemService = Container.get(SystemService);
|
||||
@@ -11,8 +12,9 @@ export default async () => {
|
||||
const subscriptionService = Container.get(SubscriptionService);
|
||||
|
||||
// 生成内置token
|
||||
let tokenCommand = `ts-node-transpile-only ${config.rootPath}/back/token.ts`;
|
||||
const tokenFile = `${config.rootPath}static/build/token.js`;
|
||||
let tokenCommand = `tsx ${join(config.rootPath, 'back/token.ts')}`;
|
||||
const tokenFile = join(config.rootPath, 'static/build/token.js');
|
||||
|
||||
if (await fileExist(tokenFile)) {
|
||||
tokenCommand = `node ${tokenFile}`;
|
||||
}
|
||||
@@ -21,6 +23,7 @@ export default async () => {
|
||||
name: '生成token',
|
||||
command: tokenCommand,
|
||||
};
|
||||
await scheduleService.cancelIntervalTask(cron);
|
||||
scheduleService.createIntervalTask(cron, {
|
||||
days: 28,
|
||||
});
|
||||
@@ -28,24 +31,21 @@ export default async () => {
|
||||
// 运行删除日志任务
|
||||
const data = await systemService.getLogRemoveFrequency();
|
||||
if (data && data.info && data.info.frequency) {
|
||||
const cron = {
|
||||
const rmlogCron = {
|
||||
id: data.id,
|
||||
name: '删除日志',
|
||||
command: `ql rmlog ${data.info.frequency}`,
|
||||
};
|
||||
scheduleService.createIntervalTask(cron, {
|
||||
await scheduleService.cancelIntervalTask(rmlogCron);
|
||||
scheduleService.createIntervalTask(rmlogCron, {
|
||||
days: data.info.frequency,
|
||||
});
|
||||
}
|
||||
|
||||
// 运行所有订阅
|
||||
await subscriptionService.setSshConfig();
|
||||
const subs = await subscriptionService.list();
|
||||
for (const sub of subs) {
|
||||
await subscriptionService.handleTask(
|
||||
sub,
|
||||
!sub.is_disabled,
|
||||
true,
|
||||
!sub.is_disabled,
|
||||
);
|
||||
subscriptionService.handleTask(sub, !sub.is_disabled, !sub.is_disabled);
|
||||
}
|
||||
};
|
||||
@@ -9,8 +9,14 @@ export default async ({ server }: { server: Server }) => {
|
||||
process.on('SIGINT', () => {
|
||||
Logger.info('✌️ Server need close');
|
||||
server.close(() => {
|
||||
Logger.info('✌️ Server closed');
|
||||
process.exit(0);
|
||||
setTimeout(() => {
|
||||
process.exit();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Forcing server close !!!');
|
||||
process.exit(1);
|
||||
}, 15000);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package com.ql.cron;
|
||||
|
||||
service Cron {
|
||||
rpc addCron(AddCronRequest) returns (AddCronResponse);
|
||||
rpc delCron(DeleteCronRequest) returns (DeleteCronResponse);
|
||||
}
|
||||
|
||||
message ICron {
|
||||
string id = 1;
|
||||
string schedule = 2;
|
||||
string command = 3;
|
||||
}
|
||||
|
||||
message AddCronRequest { repeated ICron crons = 1; }
|
||||
|
||||
message AddCronResponse {}
|
||||
|
||||
message DeleteCronRequest { repeated string ids = 1; }
|
||||
|
||||
message DeleteCronResponse {}
|
||||
@@ -0,0 +1,482 @@
|
||||
/* eslint-disable */
|
||||
import {
|
||||
CallOptions,
|
||||
ChannelCredentials,
|
||||
Client,
|
||||
ClientOptions,
|
||||
ClientUnaryCall,
|
||||
handleUnaryCall,
|
||||
makeGenericClientConstructor,
|
||||
Metadata,
|
||||
ServiceError,
|
||||
UntypedServiceImplementation,
|
||||
} from '@grpc/grpc-js';
|
||||
import _m0 from 'protobufjs/minimal';
|
||||
|
||||
export const protobufPackage = 'com.ql.cron';
|
||||
|
||||
export interface ICron {
|
||||
id: string;
|
||||
schedule: string;
|
||||
command: string;
|
||||
}
|
||||
|
||||
export interface AddCronRequest {
|
||||
crons: ICron[];
|
||||
}
|
||||
|
||||
export interface AddCronResponse {}
|
||||
|
||||
export interface DeleteCronRequest {
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
export interface DeleteCronResponse {}
|
||||
|
||||
function createBaseICron(): ICron {
|
||||
return { id: '', schedule: '', command: '' };
|
||||
}
|
||||
|
||||
export const ICron = {
|
||||
encode(message: ICron, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.id !== '') {
|
||||
writer.uint32(10).string(message.id);
|
||||
}
|
||||
if (message.schedule !== '') {
|
||||
writer.uint32(18).string(message.schedule);
|
||||
}
|
||||
if (message.command !== '') {
|
||||
writer.uint32(26).string(message.command);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): ICron {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseICron();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag != 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.id = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
if (tag != 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.schedule = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
if (tag != 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.command = reader.string();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): ICron {
|
||||
return {
|
||||
id: isSet(object.id) ? String(object.id) : '',
|
||||
schedule: isSet(object.schedule) ? String(object.schedule) : '',
|
||||
command: isSet(object.command) ? String(object.command) : '',
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: ICron): unknown {
|
||||
const obj: any = {};
|
||||
message.id !== undefined && (obj.id = message.id);
|
||||
message.schedule !== undefined && (obj.schedule = message.schedule);
|
||||
message.command !== undefined && (obj.command = message.command);
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<ICron>, I>>(base?: I): ICron {
|
||||
return ICron.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<ICron>, I>>(object: I): ICron {
|
||||
const message = createBaseICron();
|
||||
message.id = object.id ?? '';
|
||||
message.schedule = object.schedule ?? '';
|
||||
message.command = object.command ?? '';
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseAddCronRequest(): AddCronRequest {
|
||||
return { crons: [] };
|
||||
}
|
||||
|
||||
export const AddCronRequest = {
|
||||
encode(
|
||||
message: AddCronRequest,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
for (const v of message.crons) {
|
||||
ICron.encode(v!, writer.uint32(10).fork()).ldelim();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): AddCronRequest {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseAddCronRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag != 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.crons.push(ICron.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): AddCronRequest {
|
||||
return {
|
||||
crons: Array.isArray(object?.crons)
|
||||
? object.crons.map((e: any) => ICron.fromJSON(e))
|
||||
: [],
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: AddCronRequest): unknown {
|
||||
const obj: any = {};
|
||||
if (message.crons) {
|
||||
obj.crons = message.crons.map((e) => (e ? ICron.toJSON(e) : undefined));
|
||||
} else {
|
||||
obj.crons = [];
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<AddCronRequest>, I>>(
|
||||
base?: I,
|
||||
): AddCronRequest {
|
||||
return AddCronRequest.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<AddCronRequest>, I>>(
|
||||
object: I,
|
||||
): AddCronRequest {
|
||||
const message = createBaseAddCronRequest();
|
||||
message.crons = object.crons?.map((e) => ICron.fromPartial(e)) || [];
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseAddCronResponse(): AddCronResponse {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const AddCronResponse = {
|
||||
encode(
|
||||
_: AddCronResponse,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): AddCronResponse {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseAddCronResponse();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(_: any): AddCronResponse {
|
||||
return {};
|
||||
},
|
||||
|
||||
toJSON(_: AddCronResponse): unknown {
|
||||
const obj: any = {};
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<AddCronResponse>, I>>(
|
||||
base?: I,
|
||||
): AddCronResponse {
|
||||
return AddCronResponse.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<AddCronResponse>, I>>(
|
||||
_: I,
|
||||
): AddCronResponse {
|
||||
const message = createBaseAddCronResponse();
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseDeleteCronRequest(): DeleteCronRequest {
|
||||
return { ids: [] };
|
||||
}
|
||||
|
||||
export const DeleteCronRequest = {
|
||||
encode(
|
||||
message: DeleteCronRequest,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
for (const v of message.ids) {
|
||||
writer.uint32(10).string(v!);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): DeleteCronRequest {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseDeleteCronRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag != 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ids.push(reader.string());
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): DeleteCronRequest {
|
||||
return {
|
||||
ids: Array.isArray(object?.ids)
|
||||
? object.ids.map((e: any) => String(e))
|
||||
: [],
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: DeleteCronRequest): unknown {
|
||||
const obj: any = {};
|
||||
if (message.ids) {
|
||||
obj.ids = message.ids.map((e) => e);
|
||||
} else {
|
||||
obj.ids = [];
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(
|
||||
base?: I,
|
||||
): DeleteCronRequest {
|
||||
return DeleteCronRequest.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(
|
||||
object: I,
|
||||
): DeleteCronRequest {
|
||||
const message = createBaseDeleteCronRequest();
|
||||
message.ids = object.ids?.map((e) => e) || [];
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseDeleteCronResponse(): DeleteCronResponse {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const DeleteCronResponse = {
|
||||
encode(
|
||||
_: DeleteCronResponse,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): DeleteCronResponse {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseDeleteCronResponse();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(_: any): DeleteCronResponse {
|
||||
return {};
|
||||
},
|
||||
|
||||
toJSON(_: DeleteCronResponse): unknown {
|
||||
const obj: any = {};
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(
|
||||
base?: I,
|
||||
): DeleteCronResponse {
|
||||
return DeleteCronResponse.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(
|
||||
_: I,
|
||||
): DeleteCronResponse {
|
||||
const message = createBaseDeleteCronResponse();
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export type CronService = typeof CronService;
|
||||
export const CronService = {
|
||||
addCron: {
|
||||
path: '/com.ql.cron.Cron/addCron',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestSerialize: (value: AddCronRequest) =>
|
||||
Buffer.from(AddCronRequest.encode(value).finish()),
|
||||
requestDeserialize: (value: Buffer) => AddCronRequest.decode(value),
|
||||
responseSerialize: (value: AddCronResponse) =>
|
||||
Buffer.from(AddCronResponse.encode(value).finish()),
|
||||
responseDeserialize: (value: Buffer) => AddCronResponse.decode(value),
|
||||
},
|
||||
delCron: {
|
||||
path: '/com.ql.cron.Cron/delCron',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestSerialize: (value: DeleteCronRequest) =>
|
||||
Buffer.from(DeleteCronRequest.encode(value).finish()),
|
||||
requestDeserialize: (value: Buffer) => DeleteCronRequest.decode(value),
|
||||
responseSerialize: (value: DeleteCronResponse) =>
|
||||
Buffer.from(DeleteCronResponse.encode(value).finish()),
|
||||
responseDeserialize: (value: Buffer) => DeleteCronResponse.decode(value),
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface CronServer extends UntypedServiceImplementation {
|
||||
addCron: handleUnaryCall<AddCronRequest, AddCronResponse>;
|
||||
delCron: handleUnaryCall<DeleteCronRequest, DeleteCronResponse>;
|
||||
}
|
||||
|
||||
export interface CronClient extends Client {
|
||||
addCron(
|
||||
request: AddCronRequest,
|
||||
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
||||
): ClientUnaryCall;
|
||||
addCron(
|
||||
request: AddCronRequest,
|
||||
metadata: Metadata,
|
||||
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
||||
): ClientUnaryCall;
|
||||
addCron(
|
||||
request: AddCronRequest,
|
||||
metadata: Metadata,
|
||||
options: Partial<CallOptions>,
|
||||
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
||||
): ClientUnaryCall;
|
||||
delCron(
|
||||
request: DeleteCronRequest,
|
||||
callback: (
|
||||
error: ServiceError | null,
|
||||
response: DeleteCronResponse,
|
||||
) => void,
|
||||
): ClientUnaryCall;
|
||||
delCron(
|
||||
request: DeleteCronRequest,
|
||||
metadata: Metadata,
|
||||
callback: (
|
||||
error: ServiceError | null,
|
||||
response: DeleteCronResponse,
|
||||
) => void,
|
||||
): ClientUnaryCall;
|
||||
delCron(
|
||||
request: DeleteCronRequest,
|
||||
metadata: Metadata,
|
||||
options: Partial<CallOptions>,
|
||||
callback: (
|
||||
error: ServiceError | null,
|
||||
response: DeleteCronResponse,
|
||||
) => void,
|
||||
): ClientUnaryCall;
|
||||
}
|
||||
|
||||
export const CronClient = makeGenericClientConstructor(
|
||||
CronService,
|
||||
'com.ql.cron.Cron',
|
||||
) as unknown as {
|
||||
new (
|
||||
address: string,
|
||||
credentials: ChannelCredentials,
|
||||
options?: Partial<ClientOptions>,
|
||||
): CronClient;
|
||||
service: typeof CronService;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import express from 'express';
|
||||
import Logger from './loaders/logger';
|
||||
import config from './config';
|
||||
import { HealthClient } from './protos/health';
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
|
||||
const app = express();
|
||||
const client = new HealthClient(
|
||||
`localhost:${config.cronPort}`,
|
||||
credentials.createInsecure(),
|
||||
);
|
||||
|
||||
app.get('/api/health', (req, res) => {
|
||||
client.check({ service: 'cron' }, (err, response) => {
|
||||
if (err) {
|
||||
return res.status(500).send({ code: 500, error: err });
|
||||
}
|
||||
return res.status(200).send({ code: 200, data: response });
|
||||
});
|
||||
});
|
||||
|
||||
app
|
||||
.listen(config.publicPort, async () => {
|
||||
await require('./loaders/sentry').default({ expressApp: app });
|
||||
await require('./loaders/db').default();
|
||||
|
||||
Logger.debug(`✌️ 公共服务启动成功!`);
|
||||
process.send?.('ready');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
||||
import { AddCronRequest, AddCronResponse } from '../protos/cron';
|
||||
import nodeSchedule from 'node-schedule';
|
||||
import { scheduleStacks } from './data';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
const addCron = (
|
||||
call: ServerUnaryCall<AddCronRequest, AddCronResponse>,
|
||||
callback: sendUnaryData<AddCronResponse>,
|
||||
) => {
|
||||
for (const item of call.request.crons) {
|
||||
const { id, schedule, command } = item;
|
||||
if (scheduleStacks.has(id)) {
|
||||
scheduleStacks.get(id)?.cancel();
|
||||
}
|
||||
scheduleStacks.set(
|
||||
id,
|
||||
nodeSchedule.scheduleJob(id, schedule, async () => {
|
||||
exec(`ID=${id} ${command}`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
callback(null, null);
|
||||
};
|
||||
|
||||
export { addCron };
|
||||
@@ -0,0 +1,40 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import {
|
||||
AddCronRequest,
|
||||
AddCronResponse,
|
||||
CronClient,
|
||||
DeleteCronRequest,
|
||||
DeleteCronResponse,
|
||||
} from '../protos/cron';
|
||||
import config from '../config';
|
||||
|
||||
class Client {
|
||||
private client = new CronClient(
|
||||
`localhost:${config.cronPort}`,
|
||||
credentials.createInsecure(),
|
||||
);
|
||||
|
||||
addCron(request: AddCronRequest['crons']): Promise<AddCronResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.addCron({ crons: request }, (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
delCron(request: DeleteCronRequest['ids']): Promise<DeleteCronResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.delCron({ ids: request }, (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new Client();
|
||||
@@ -0,0 +1,6 @@
|
||||
import nodeSchedule from 'node-schedule';
|
||||
import { ToadScheduler } from 'toad-scheduler';
|
||||
|
||||
export const scheduleStacks = new Map<string, nodeSchedule.Job>();
|
||||
|
||||
export const intervalSchedule = new ToadScheduler();
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
||||
import { DeleteCronRequest, DeleteCronResponse } from '../protos/cron';
|
||||
import { scheduleStacks } from './data';
|
||||
|
||||
const delCron = (
|
||||
call: ServerUnaryCall<DeleteCronRequest, DeleteCronResponse>,
|
||||
callback: sendUnaryData<DeleteCronResponse>,
|
||||
) => {
|
||||
for (const id of call.request.ids) {
|
||||
if (scheduleStacks.has(id)) {
|
||||
scheduleStacks.get(id)?.cancel();
|
||||
scheduleStacks.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, null);
|
||||
};
|
||||
|
||||
export { delCron };
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
||||
import { HealthCheckRequest, HealthCheckResponse } from '../protos/health';
|
||||
import { exec } from 'child_process';
|
||||
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 -sf http://localhost:${config.port}/api/system`,
|
||||
);
|
||||
|
||||
if (res.includes('200')) {
|
||||
return callback(null, { status: 1 });
|
||||
}
|
||||
const errLog = await promiseExec(
|
||||
`tail -n 300 ~/.pm2/logs/panel-error.log`,
|
||||
);
|
||||
return callback(new Error(errLog));
|
||||
|
||||
default:
|
||||
return callback(null, { status: 1 });
|
||||
}
|
||||
};
|
||||
|
||||
export { check };
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Server, ServerCredentials } from '@grpc/grpc-js';
|
||||
import { CronService } from '../protos/cron';
|
||||
import { addCron } from './addCron';
|
||||
import { delCron } from './delCron';
|
||||
import { HealthService } from '../protos/health';
|
||||
import { check } from './health';
|
||||
import config from '../config';
|
||||
import Logger from '../loaders/logger';
|
||||
|
||||
const server = new Server();
|
||||
server.addService(HealthService, { check });
|
||||
server.addService(CronService, { addCron, delCron });
|
||||
server.bindAsync(
|
||||
`localhost:${config.cronPort}`,
|
||||
ServerCredentials.createInsecure(),
|
||||
() => {
|
||||
server.start();
|
||||
Logger.debug(`✌️ 定时服务启动成功!`);
|
||||
process.send?.('ready');
|
||||
},
|
||||
);
|
||||
@@ -12,12 +12,14 @@ import {
|
||||
killTask,
|
||||
} from '../config/util';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { Op, where, col as colFn } from 'sequelize';
|
||||
import { Op, where, col as colFn, FindOptions } from 'sequelize';
|
||||
import path from 'path';
|
||||
import { TASK_PREFIX, QL_PREFIX } from '../config/const';
|
||||
import cronClient from '../schedule/client';
|
||||
|
||||
@Service()
|
||||
export default class CronService {
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
constructor(@Inject('logger') private logger: winston.Logger) { }
|
||||
|
||||
private isSixCron(cron: Crontab) {
|
||||
const { schedule } = cron;
|
||||
@@ -31,7 +33,12 @@ export default class CronService {
|
||||
const tab = new Crontab(payload);
|
||||
tab.saved = false;
|
||||
const doc = await this.insert(tab);
|
||||
await this.set_crontab(this.isSixCron(doc));
|
||||
if (this.isSixCron(doc)) {
|
||||
await cronClient.addCron([
|
||||
{ id: String(doc.id), schedule: doc.schedule!, command: doc.command },
|
||||
]);
|
||||
}
|
||||
await this.set_crontab();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -40,9 +47,26 @@ export default class CronService {
|
||||
}
|
||||
|
||||
public async update(payload: Crontab): Promise<Crontab> {
|
||||
payload.saved = false;
|
||||
const newDoc = await this.updateDb(payload);
|
||||
await this.set_crontab(this.isSixCron(newDoc));
|
||||
const doc = await this.getDb({ id: payload.id });
|
||||
const tab = new Crontab({ ...doc, ...payload });
|
||||
tab.saved = false;
|
||||
const newDoc = await this.updateDb(tab);
|
||||
if (doc.isDisabled === 1) {
|
||||
return newDoc;
|
||||
}
|
||||
if (this.isSixCron(doc)) {
|
||||
await cronClient.delCron([String(newDoc.id)]);
|
||||
}
|
||||
if (this.isSixCron(newDoc)) {
|
||||
await cronClient.addCron([
|
||||
{
|
||||
id: String(newDoc.id),
|
||||
schedule: newDoc.schedule!,
|
||||
command: newDoc.command,
|
||||
},
|
||||
]);
|
||||
}
|
||||
await this.set_crontab();
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -81,7 +105,8 @@ export default class CronService {
|
||||
|
||||
public async remove(ids: number[]) {
|
||||
await CrontabModel.destroy({ where: { id: ids } });
|
||||
await this.set_crontab(true);
|
||||
await cronClient.delCron(ids.map(String));
|
||||
await this.set_crontab();
|
||||
}
|
||||
|
||||
public async pin(ids: number[]) {
|
||||
@@ -243,8 +268,15 @@ export default class CronService {
|
||||
const filterKeys: any = Object.keys(filterQuery);
|
||||
for (const key of filterKeys) {
|
||||
let q: any = {};
|
||||
if (filterKeys[key]) {
|
||||
q[key] = filterKeys[key];
|
||||
if (!filterQuery[key]) continue;
|
||||
if (key === 'status') {
|
||||
if (filterQuery[key].includes(2)) {
|
||||
q = { [Op.or]: [{ [key]: filterQuery[key] }, { isDisabled: 1 }] };
|
||||
} else {
|
||||
q = { [Op.and]: [{ [key]: filterQuery[key] }, { isDisabled: 0 }] };
|
||||
}
|
||||
} else {
|
||||
q[key] = filterQuery[key];
|
||||
}
|
||||
query[Op.and].push(q);
|
||||
}
|
||||
@@ -259,6 +291,19 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
|
||||
public async find({
|
||||
log_path,
|
||||
}: {
|
||||
log_path: string;
|
||||
}): Promise<Crontab | null> {
|
||||
try {
|
||||
const result = await CrontabModel.findOne({ where: { log_path } });
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async crontabs(params?: {
|
||||
searchValue: string;
|
||||
page: string;
|
||||
@@ -310,7 +355,7 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Crontab> {
|
||||
public async getDb(query: FindOptions<Crontab>['where']): Promise<Crontab> {
|
||||
const doc: any = await CrontabModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Crontab);
|
||||
}
|
||||
@@ -361,8 +406,8 @@ export default class CronService {
|
||||
this.logger.silly('Original command: ' + command);
|
||||
|
||||
let cmdStr = command;
|
||||
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
|
||||
cmdStr = `task ${cmdStr}`;
|
||||
if (!cmdStr.startsWith(TASK_PREFIX) && !cmdStr.startsWith(QL_PREFIX)) {
|
||||
cmdStr = `${TASK_PREFIX}${cmdStr}`;
|
||||
}
|
||||
if (
|
||||
cmdStr.endsWith('.js') ||
|
||||
@@ -408,12 +453,22 @@ export default class CronService {
|
||||
|
||||
public async disabled(ids: number[]) {
|
||||
await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
||||
await this.set_crontab(true);
|
||||
await cronClient.delCron(ids.map(String));
|
||||
await this.set_crontab();
|
||||
}
|
||||
|
||||
public async enabled(ids: number[]) {
|
||||
await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
||||
await this.set_crontab(true);
|
||||
const docs = await CrontabModel.findAll({ where: { id: ids } });
|
||||
const sixCron = docs
|
||||
.filter((x) => this.isSixCron(x))
|
||||
.map((doc) => ({
|
||||
id: String(doc.id),
|
||||
schedule: doc.schedule!,
|
||||
command: doc.command,
|
||||
}));
|
||||
await cronClient.addCron(sixCron);
|
||||
await this.set_crontab();
|
||||
}
|
||||
|
||||
public async log(id: number) {
|
||||
@@ -426,35 +481,17 @@ export default class CronService {
|
||||
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
||||
if (logFileExist) {
|
||||
return getFileContentByName(`${absolutePath}`);
|
||||
}
|
||||
const [, commandStr, url] = doc.command.split(/ +/);
|
||||
let logPath = this.getKey(commandStr);
|
||||
const isQlCommand = doc.command.startsWith('ql ');
|
||||
const key =
|
||||
(url && ['repo', 'raw'].includes(commandStr) && this.getKey(url)) ||
|
||||
logPath;
|
||||
if (isQlCommand) {
|
||||
logPath = 'update';
|
||||
}
|
||||
let logDir = `${config.logPath}${logPath}`;
|
||||
if (existsSync(logDir)) {
|
||||
let files = await promises.readdir(logDir);
|
||||
if (isQlCommand) {
|
||||
files = files.filter((x) => x.includes(key));
|
||||
}
|
||||
return getFileContentByName(`${logDir}/${files[files.length - 1]}`);
|
||||
} else {
|
||||
return '';
|
||||
return '任务未运行';
|
||||
}
|
||||
}
|
||||
|
||||
public async logs(id: number) {
|
||||
const doc = await this.getDb({ id });
|
||||
if (!doc) {
|
||||
if (!doc || !doc.log_path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (doc.log_path) {
|
||||
const relativeDir = path.dirname(`${doc.log_path}`);
|
||||
const dir = path.resolve(config.logPath, relativeDir);
|
||||
if (existsSync(dir)) {
|
||||
@@ -466,65 +503,24 @@ export default class CronService {
|
||||
time: fs.statSync(`${dir}/${x}`).mtime.getTime(),
|
||||
}))
|
||||
.sort((a, b) => b.time - a.time);
|
||||
}
|
||||
}
|
||||
|
||||
const [, commandStr, url] = doc.command.split(/ +/);
|
||||
let logPath = this.getKey(commandStr);
|
||||
const isQlCommand = doc.command.startsWith('ql ');
|
||||
const key =
|
||||
(url && ['repo', 'raw'].includes(commandStr) && this.getKey(url)) ||
|
||||
logPath;
|
||||
if (isQlCommand) {
|
||||
logPath = 'update';
|
||||
}
|
||||
let logDir = `${config.logPath}${logPath}`;
|
||||
if (existsSync(logDir)) {
|
||||
let files = await promises.readdir(logDir);
|
||||
if (isQlCommand) {
|
||||
files = files.filter((x) => x.includes(key));
|
||||
}
|
||||
return files
|
||||
.map((x) => ({
|
||||
filename: x,
|
||||
directory: logPath,
|
||||
time: fs.statSync(`${logDir}/${x}`).mtime.getTime(),
|
||||
}))
|
||||
.sort((a, b) => b.time - a.time);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private getKey(command: string): string {
|
||||
const start =
|
||||
command.lastIndexOf('/') !== -1 ? command.lastIndexOf('/') + 1 : 0;
|
||||
const end =
|
||||
command.lastIndexOf('.') !== -1
|
||||
? command.lastIndexOf('.')
|
||||
: command.length;
|
||||
|
||||
const tmpStr = command.substring(0, start - 1);
|
||||
let index = 0;
|
||||
if (tmpStr.lastIndexOf('/') !== -1 && tmpStr.startsWith('http')) {
|
||||
index = tmpStr.lastIndexOf('/');
|
||||
} else if (tmpStr.lastIndexOf(':') !== -1 && tmpStr.startsWith('git@')) {
|
||||
index = tmpStr.lastIndexOf(':');
|
||||
}
|
||||
if (index) {
|
||||
return `${tmpStr.substring(index + 1)}_${command.substring(start, end)}`;
|
||||
} else {
|
||||
return command.substring(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
private make_command(tab: Crontab) {
|
||||
if (
|
||||
!tab.command.startsWith(TASK_PREFIX) &&
|
||||
!tab.command.startsWith(QL_PREFIX)
|
||||
) {
|
||||
tab.command = `${TASK_PREFIX}${tab.command}`;
|
||||
}
|
||||
const crontab_job_string = `ID=${tab.id} ${tab.command}`;
|
||||
return crontab_job_string;
|
||||
}
|
||||
|
||||
private async set_crontab(needReloadSchedule: boolean = false) {
|
||||
const tabs = await this.crontabs();
|
||||
private async set_crontab(data?: { data: Crontab[]; total: number }) {
|
||||
const tabs = data ?? (await this.crontabs());
|
||||
var crontab_string = '';
|
||||
tabs.data.forEach((tab) => {
|
||||
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
||||
@@ -546,9 +542,6 @@ export default class CronService {
|
||||
fs.writeFileSync(config.crontabFile, crontab_string);
|
||||
|
||||
execSync(`crontab ${config.crontabFile}`);
|
||||
if (needReloadSchedule) {
|
||||
exec(`pm2 reload schedule`);
|
||||
}
|
||||
await CrontabModel.update({ saved: true }, { where: {} });
|
||||
}
|
||||
|
||||
@@ -586,7 +579,17 @@ export default class CronService {
|
||||
});
|
||||
}
|
||||
|
||||
public autosave_crontab() {
|
||||
return this.set_crontab();
|
||||
public async autosave_crontab() {
|
||||
const tabs = await this.crontabs();
|
||||
this.set_crontab(tabs);
|
||||
|
||||
const sixCron = tabs.data
|
||||
.filter((x) => this.isSixCron(x) && x.isDisabled !== 1)
|
||||
.map((doc) => ({
|
||||
id: String(doc.id),
|
||||
schedule: doc.schedule!,
|
||||
command: doc.command,
|
||||
}));
|
||||
await cronClient.addCron(sixCron);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
minPosition,
|
||||
stepPosition,
|
||||
} from '../data/env';
|
||||
import { FindOptions } from 'sequelize';
|
||||
|
||||
@Service()
|
||||
export default class CronViewService {
|
||||
@@ -31,7 +32,9 @@ export default class CronViewService {
|
||||
}
|
||||
|
||||
public async update(payload: CrontabView): Promise<CrontabView> {
|
||||
const newDoc = await this.updateDb(payload);
|
||||
const doc = await this.getDb({ id: payload.id })
|
||||
const tab = new CrontabView({ ...doc, ...payload });
|
||||
const newDoc = await this.updateDb(tab);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -56,7 +59,7 @@ export default class CronViewService {
|
||||
}
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<CrontabView> {
|
||||
public async getDb(query: FindOptions<CrontabView>['where']): Promise<CrontabView> {
|
||||
const doc: any = await CrontabViewModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as CrontabView);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '../data/dependence';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import { Op } from 'sequelize';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
import { concurrentRun } from '../config/util';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class DependenceService {
|
||||
|
||||
public async create(payloads: Dependence[]): Promise<Dependence[]> {
|
||||
const tabs = payloads.map((x) => {
|
||||
const tab = new Dependence({ ...x, status: DependenceStatus.installing });
|
||||
const tab = new Dependence({ ...x, status: DependenceStatus.queued });
|
||||
return tab;
|
||||
});
|
||||
const docs = await this.insert(tabs);
|
||||
@@ -45,7 +45,7 @@ export default class DependenceService {
|
||||
const tab = new Dependence({
|
||||
...doc,
|
||||
...other,
|
||||
status: DependenceStatus.installing,
|
||||
status: DependenceStatus.queued,
|
||||
});
|
||||
const newDoc = await this.updateDb(tab);
|
||||
this.installDependenceOneByOne([newDoc]);
|
||||
@@ -59,7 +59,7 @@ export default class DependenceService {
|
||||
|
||||
public async remove(ids: number[], force = false): Promise<Dependence[]> {
|
||||
await DependenceModel.update(
|
||||
{ status: DependenceStatus.removing, log: [] },
|
||||
{ status: DependenceStatus.queued, log: [] },
|
||||
{ where: { id: ids } },
|
||||
);
|
||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||
@@ -99,23 +99,30 @@ export default class DependenceService {
|
||||
}
|
||||
}
|
||||
|
||||
private installDependenceOneByOne(
|
||||
public installDependenceOneByOne(
|
||||
docs: Dependence[],
|
||||
isInstall: boolean = true,
|
||||
force: boolean = false,
|
||||
) {
|
||||
concurrentRun(
|
||||
docs.map(
|
||||
(dep) => async () =>
|
||||
await this.installOrUninstallDependencies([dep], isInstall, force),
|
||||
),
|
||||
docs.map((dep) => async () => {
|
||||
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[]> {
|
||||
await DependenceModel.update(
|
||||
{ status: DependenceStatus.installing, log: [] },
|
||||
{ status: DependenceStatus.queued, log: [] },
|
||||
{ where: { id: ids } },
|
||||
);
|
||||
|
||||
@@ -132,7 +139,9 @@ export default class DependenceService {
|
||||
return docs;
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Dependence> {
|
||||
public async getDb(
|
||||
query: FindOptions<Dependence>['where'],
|
||||
): Promise<Dependence> {
|
||||
const doc: any = await DependenceModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Dependence);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
stepPosition,
|
||||
} from '../data/env';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { Op } from 'sequelize';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
|
||||
@Service()
|
||||
export default class EnvService {
|
||||
@@ -49,7 +49,9 @@ export default class EnvService {
|
||||
}
|
||||
|
||||
public async update(payload: Env): Promise<Env> {
|
||||
const newDoc = await this.updateDb(payload);
|
||||
const doc = await this.getDb({ id: payload.id });
|
||||
const tab = new Env({ ...doc, ...payload });
|
||||
const newDoc = await this.updateDb(tab);
|
||||
await this.set_envs();
|
||||
return newDoc;
|
||||
}
|
||||
@@ -92,13 +94,17 @@ export default class EnvService {
|
||||
position: this.getPrecisionPosition(targetPosition),
|
||||
});
|
||||
|
||||
await this.checkPosition(targetPosition);
|
||||
await this.checkPosition(targetPosition, envs[toIndex].position!);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
private async checkPosition(position: number) {
|
||||
private async checkPosition(position: number, edge: number = 0) {
|
||||
const precisionPosition = parseFloat(position.toPrecision(16));
|
||||
if (precisionPosition < minPosition || precisionPosition > maxPosition) {
|
||||
if (
|
||||
precisionPosition < minPosition ||
|
||||
precisionPosition > maxPosition ||
|
||||
Math.abs(precisionPosition - edge) < minPosition
|
||||
) {
|
||||
const envs = await this.envs();
|
||||
let position = initPosition;
|
||||
for (const env of envs) {
|
||||
@@ -140,7 +146,6 @@ export default class EnvService {
|
||||
}
|
||||
try {
|
||||
const result = await this.find(condition, [
|
||||
['status', 'ASC'],
|
||||
['position', 'DESC'],
|
||||
['createdAt', 'ASC'],
|
||||
]);
|
||||
@@ -158,7 +163,7 @@ export default class EnvService {
|
||||
return docs;
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Env> {
|
||||
public async getDb(query: FindOptions<Env>['where']): Promise<Env> {
|
||||
const doc: any = await EnvModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Env);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export default class NotificationService {
|
||||
private content = '';
|
||||
private params!: Omit<NotificationInfo, 'type'>;
|
||||
private gotOption = {
|
||||
timeout: 30000,
|
||||
timeout: 10000,
|
||||
retry: 1,
|
||||
};
|
||||
|
||||
@@ -78,7 +78,8 @@ export default class NotificationService {
|
||||
}
|
||||
|
||||
private async gotify() {
|
||||
const { gotifyUrl, gotifyToken, gotifyPriority } = this.params;
|
||||
const { gotifyUrl, gotifyToken, gotifyPriority = 1 } = this.params;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
|
||||
...this.gotOption,
|
||||
@@ -93,10 +94,14 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return typeof res.id === 'number';
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async goCqHttpBot() {
|
||||
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(`${goCqHttpBotUrl}?${goCqHttpBotQq}`, {
|
||||
...this.gotOption,
|
||||
@@ -105,6 +110,9 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return res.retcode === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async serverChan() {
|
||||
@@ -112,6 +120,7 @@ export default class NotificationService {
|
||||
const url = serverChanKey.startsWith('SCT')
|
||||
? `https://sctapi.ftqq.com/${serverChanKey}.send`
|
||||
: `https://sc.ftqq.com/${serverChanKey}.send`;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -120,11 +129,15 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return res.errno === 0 || res.data.errno === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async pushDeer() {
|
||||
const { pushDeerKey, pushDeerUrl } = this.params;
|
||||
const url = pushDeerUrl || `https://api2.pushdeer.com/message/push`;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -137,11 +150,15 @@ export default class NotificationService {
|
||||
return (
|
||||
res.content.result.length !== undefined && res.content.result.length > 0
|
||||
);
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async chat() {
|
||||
const { chatUrl, chatToken } = this.params;
|
||||
const url = `${chatUrl}${chatToken}`;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -150,18 +167,23 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return res.success;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async bark() {
|
||||
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
||||
if (!barkPush.startsWith('http') && !barkPush.startsWith('https')) {
|
||||
if (!barkPush.startsWith('http')) {
|
||||
barkPush = `https://api.day.app/${barkPush}`;
|
||||
}
|
||||
const url = `${barkPush}/${encodeURIComponent(
|
||||
this.title,
|
||||
)}/${encodeURIComponent(
|
||||
this.content,
|
||||
)}?icon=${barkIcon}?sound=${barkSound}&group=${barkGroup}`;
|
||||
)}?icon=${barkIcon}&sound=${barkSound}&group=${barkGroup}`;
|
||||
|
||||
try {
|
||||
const res: any = await got
|
||||
.get(url, {
|
||||
...this.gotOption,
|
||||
@@ -169,6 +191,9 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return res.code === 200;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async telegramBot() {
|
||||
@@ -200,6 +225,7 @@ export default class NotificationService {
|
||||
https: httpsAgent,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -209,6 +235,9 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return !!res.ok;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async dingtalkBot() {
|
||||
@@ -222,6 +251,7 @@ export default class NotificationService {
|
||||
secretParam = `×tamp=${dateNow}&sign=${result}`;
|
||||
}
|
||||
const url = `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkBotToken}${secretParam}`;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -234,11 +264,15 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return res.errcode === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async weWorkBot() {
|
||||
const { weWorkBotKey } = this.params;
|
||||
const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weWorkBotKey}`;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -251,6 +285,9 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return res.errcode === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async weWorkApp() {
|
||||
@@ -306,6 +343,7 @@ export default class NotificationService {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(
|
||||
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
|
||||
@@ -322,6 +360,9 @@ export default class NotificationService {
|
||||
.json();
|
||||
|
||||
return res.errcode === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async aibotk() {
|
||||
@@ -353,6 +394,7 @@ export default class NotificationService {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -363,11 +405,15 @@ export default class NotificationService {
|
||||
.json();
|
||||
|
||||
return res.code === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async iGot() {
|
||||
const { iGotPushKey } = this.params;
|
||||
const url = `https://push.hellyw.com/${iGotPushKey.toLowerCase()}`;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -377,11 +423,15 @@ export default class NotificationService {
|
||||
.json();
|
||||
|
||||
return res.ret === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async pushPlus() {
|
||||
const { pushPlusToken, pushPlusUser } = this.params;
|
||||
const url = `https://www.pushplus.plus/send`;
|
||||
try {
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
...this.gotOption,
|
||||
@@ -395,12 +445,21 @@ export default class NotificationService {
|
||||
.json();
|
||||
|
||||
return res.code === 200;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
.post(`https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`, {
|
||||
.post(larkKey, {
|
||||
...this.gotOption,
|
||||
json: {
|
||||
msg_type: 'text',
|
||||
@@ -410,10 +469,15 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
return res.StatusCode === 0;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async email() {
|
||||
const { emailPass, emailService, emailUser } = this.params;
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: emailService,
|
||||
auth: {
|
||||
@@ -432,6 +496,9 @@ export default class NotificationService {
|
||||
transporter.close();
|
||||
|
||||
return !!info.messageId;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private async webhook() {
|
||||
@@ -460,8 +527,12 @@ export default class NotificationService {
|
||||
allowGetBody: true,
|
||||
...bodyParam,
|
||||
};
|
||||
try {
|
||||
const res = await got(formatUrl, options);
|
||||
return String(res.statusCode).startsWith('20');
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatBody(contentType: string, body: any): object {
|
||||
@@ -484,11 +555,11 @@ export default class NotificationService {
|
||||
|
||||
return {
|
||||
formatUrl: url
|
||||
.replaceAll('$title', encodeURIComponent(this.title))
|
||||
.replaceAll('$content', encodeURIComponent(this.content)),
|
||||
?.replaceAll('$title', encodeURIComponent(this.title))
|
||||
?.replaceAll('$content', encodeURIComponent(this.content)),
|
||||
formatBody: body
|
||||
.replaceAll('$title', this.title)
|
||||
.replaceAll('$content', this.content),
|
||||
?.replaceAll('$title', this.title)
|
||||
?.replaceAll('$content', this.content),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import { ChildProcessWithoutNullStreams, exec, spawn } from 'child_process';
|
||||
import {
|
||||
ToadScheduler,
|
||||
LongIntervalJob,
|
||||
AsyncTask,
|
||||
SimpleIntervalSchedule,
|
||||
Task,
|
||||
} from 'toad-scheduler';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -63,8 +63,8 @@ export default class ScheduleService {
|
||||
});
|
||||
|
||||
cp.stderr.on('data', async (data) => {
|
||||
this.logger.error(
|
||||
'执行任务 %s 失败,时间:%s, 错误信息:%j',
|
||||
this.logger.info(
|
||||
'[执行任务失败] %s,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
data.toString(),
|
||||
@@ -74,7 +74,7 @@ export default class ScheduleService {
|
||||
|
||||
cp.on('error', async (err) => {
|
||||
this.logger.error(
|
||||
'创建任务 %s 失败,时间:%s, 错误信息:%j',
|
||||
'[创建任务失败] %s,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
err,
|
||||
@@ -84,7 +84,7 @@ export default class ScheduleService {
|
||||
|
||||
cp.on('exit', async (code, signal) => {
|
||||
this.logger.info(
|
||||
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
|
||||
`[任务退出] ${command} 进程id: ${cp.pid},退出码 ${code}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -138,7 +138,10 @@ export default class ScheduleService {
|
||||
async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
|
||||
const _id = this.formatId(id);
|
||||
this.logger.info('[取消定时任务],任务名:%s', name);
|
||||
this.scheduleStacks.has(_id) && this.scheduleStacks.get(_id)?.cancel();
|
||||
if (this.scheduleStacks.has(_id)) {
|
||||
this.scheduleStacks.get(_id)?.cancel();
|
||||
this.scheduleStacks.delete(_id);
|
||||
}
|
||||
}
|
||||
|
||||
async createIntervalTask(
|
||||
@@ -154,12 +157,10 @@ export default class ScheduleService {
|
||||
name,
|
||||
command,
|
||||
);
|
||||
const task = new AsyncTask(
|
||||
const task = new Task(
|
||||
name,
|
||||
async () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await this.runTask(command, callbacks);
|
||||
});
|
||||
() => {
|
||||
this.runTask(command, callbacks);
|
||||
},
|
||||
(err) => {
|
||||
this.logger.error(
|
||||
@@ -180,7 +181,7 @@ export default class ScheduleService {
|
||||
this.intervalSchedule.addIntervalJob(job);
|
||||
|
||||
if (runImmediately) {
|
||||
await this.runTask(command, callbacks);
|
||||
this.runTask(command, callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import SockService from './sock';
|
||||
import CronService from './cron';
|
||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||
import config from '../config';
|
||||
import { LOG_END_SYMBOL } from '../config/const';
|
||||
import { TASK_COMMAND } from '../config/const';
|
||||
import { getPid, killTask } from '../config/util';
|
||||
|
||||
@Service()
|
||||
@@ -42,7 +42,7 @@ export default class ScriptService {
|
||||
|
||||
public async runScript(filePath: string) {
|
||||
const relativePath = path.relative(config.scriptPath, filePath);
|
||||
const command = `task -l ${relativePath} now`;
|
||||
const command = `${TASK_COMMAND} -l ${relativePath} now`;
|
||||
const pid = await this.scheduleService.runTask(
|
||||
command,
|
||||
this.taskCallbacks(filePath),
|
||||
@@ -56,7 +56,7 @@ export default class ScriptService {
|
||||
let str = '';
|
||||
if (!pid) {
|
||||
const relativePath = path.relative(config.scriptPath, filePath);
|
||||
pid = await getPid(`task -l ${relativePath} now`);
|
||||
pid = await getPid(`${TASK_COMMAND} -l ${relativePath} now`);
|
||||
}
|
||||
try {
|
||||
await killTask(pid);
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import fs, { existsSync } from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Subscription } from '../data/subscription';
|
||||
import { formatUrl } from '../config/subscription';
|
||||
import config from '../config';
|
||||
|
||||
@Service()
|
||||
export default class SshKeyService {
|
||||
private homedir = os.homedir();
|
||||
private sshPath = config.sshdPath;
|
||||
private sshConfigFilePath = path.resolve(this.homedir, '.ssh', 'config');
|
||||
private sshConfigHeader = `Include ${path.join(this.sshPath, '*.config')}`;
|
||||
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {
|
||||
this.initSshConfigFile();
|
||||
}
|
||||
|
||||
private initSshConfigFile() {
|
||||
let config = '';
|
||||
if (existsSync(this.sshConfigFilePath)) {
|
||||
config = fs.readFileSync(this.sshConfigFilePath, { encoding: 'utf-8' });
|
||||
} else {
|
||||
fs.writeFileSync(this.sshConfigFilePath, '');
|
||||
}
|
||||
if (!config.includes(this.sshConfigHeader)) {
|
||||
fs.writeFileSync(
|
||||
this.sshConfigFilePath,
|
||||
`${this.sshConfigHeader}\n\n${config}`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private generatePrivateKeyFile(alias: string, key: string): void {
|
||||
try {
|
||||
fs.writeFileSync(path.join(this.sshPath, alias), `${key}${os.EOL}`, {
|
||||
encoding: 'utf8',
|
||||
mode: '400',
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error('生成私钥文件失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
private removePrivateKeyFile(alias: string): void {
|
||||
try {
|
||||
const filePath = path.join(this.sshPath, alias);
|
||||
if (existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('删除私钥文件失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
private generateSingleSshConfig(alias: string, host: string, proxy?: string) {
|
||||
if (host === 'github.com') {
|
||||
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 config = `Host ${alias}\n Hostname ${host}\n IdentityFile ${path.join(
|
||||
this.sshPath,
|
||||
alias,
|
||||
)}\n StrictHostKeyChecking no\n${proxyStr}`;
|
||||
fs.writeFileSync(`${path.join(this.sshPath, `${alias}.config`)}`, config, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
}
|
||||
|
||||
private removeSshConfig(alias: string) {
|
||||
try {
|
||||
const filePath = path.join(this.sshPath, `${alias}.config`);
|
||||
if (existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`删除ssh配置文件${alias}失败`, error);
|
||||
}
|
||||
}
|
||||
|
||||
public addSSHKey(
|
||||
key: string,
|
||||
alias: string,
|
||||
host: string,
|
||||
proxy?: string,
|
||||
): void {
|
||||
this.generatePrivateKeyFile(alias, key);
|
||||
this.generateSingleSshConfig(alias, host, proxy);
|
||||
}
|
||||
|
||||
public removeSSHKey(alias: string, host: string, proxy?: string): void {
|
||||
this.removePrivateKeyFile(alias);
|
||||
this.removeSshConfig(alias);
|
||||
}
|
||||
|
||||
public setSshConfig(docs: Subscription[]) {
|
||||
for (const doc of docs) {
|
||||
if (doc.type === 'private-repo' && doc.pull_type === 'ssh-key') {
|
||||
const { alias, proxy } = doc;
|
||||
const { host } = formatUrl(doc);
|
||||
this.removePrivateKeyFile(alias);
|
||||
this.removeSshConfig(alias);
|
||||
this.generatePrivateKeyFile(
|
||||
alias,
|
||||
(doc.pull_option as any).private_key,
|
||||
);
|
||||
this.generateSingleSshConfig(alias, host, proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,16 @@ import {
|
||||
spawn,
|
||||
} from 'child_process';
|
||||
import fs from 'fs';
|
||||
import cron_parser from 'cron-parser';
|
||||
import {
|
||||
getFileContentByName,
|
||||
concurrentRun,
|
||||
fileExist,
|
||||
createFile,
|
||||
killTask,
|
||||
handleLogPath,
|
||||
} from '../config/util';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { Op } from 'sequelize';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
import path from 'path';
|
||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||
import { SimpleIntervalSchedule } from 'toad-scheduler';
|
||||
@@ -30,6 +30,7 @@ import SockService from './sock';
|
||||
import SshKeyService from './sshKey';
|
||||
import dayjs from 'dayjs';
|
||||
import { LOG_END_SYMBOL } from '../config/const';
|
||||
import { formatCommand, formatUrl } from '../config/subscription';
|
||||
|
||||
@Service()
|
||||
export default class SubscriptionService {
|
||||
@@ -68,71 +69,20 @@ export default class SubscriptionService {
|
||||
['createdAt', 'DESC'],
|
||||
],
|
||||
});
|
||||
return result as any;
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private formatCommand(doc: Subscription, url?: string) {
|
||||
let command = 'ql ';
|
||||
let _url = url || this.formatUrl(doc).url;
|
||||
const {
|
||||
type,
|
||||
whitelist,
|
||||
blacklist,
|
||||
dependences,
|
||||
branch,
|
||||
extensions,
|
||||
proxy,
|
||||
} = doc;
|
||||
if (type === 'file') {
|
||||
command += `raw "${_url}"`;
|
||||
} else {
|
||||
command += `repo "${_url}" "${whitelist || ''}" "${blacklist || ''}" "${
|
||||
dependences || ''
|
||||
}" "${branch || ''}" "${extensions || ''}" "${proxy || ''}"`;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private formatUrl(doc: Subscription) {
|
||||
let url = doc.url;
|
||||
let host = '';
|
||||
if (doc.type === 'private-repo') {
|
||||
if (doc.pull_type === 'ssh-key') {
|
||||
host = doc.url!.replace(/.*\@([^\:]+)\:.*/, '$1');
|
||||
url = doc.url!.replace(host, doc.alias);
|
||||
} else {
|
||||
host = doc.url!.replace(/.*\:\/\/([^\/]+)\/.*/, '$1');
|
||||
const { username, password } = doc.pull_option as any;
|
||||
url = doc.url!.replace(host, `${username}:${password}@${host}`);
|
||||
}
|
||||
}
|
||||
return { url, host };
|
||||
}
|
||||
|
||||
public async handleTask(
|
||||
doc: Subscription,
|
||||
needCreate = true,
|
||||
needAddKey = true,
|
||||
runImmediately = false,
|
||||
) {
|
||||
const { url, host } = this.formatUrl(doc);
|
||||
if (doc.type === 'private-repo' && doc.pull_type === 'ssh-key') {
|
||||
if (needAddKey) {
|
||||
this.sshKeyService.addSSHKey(
|
||||
(doc.pull_option as any).private_key,
|
||||
doc.alias,
|
||||
host,
|
||||
doc.proxy,
|
||||
);
|
||||
} else {
|
||||
this.sshKeyService.removeSSHKey(doc.alias, host, doc.proxy);
|
||||
}
|
||||
}
|
||||
const { url } = formatUrl(doc);
|
||||
|
||||
doc.command = this.formatCommand(doc, url as string);
|
||||
doc.command = formatCommand(doc, url as string);
|
||||
|
||||
if (doc.schedule_type === 'crontab') {
|
||||
this.scheduleService.cancelCronTask(doc as any);
|
||||
@@ -155,6 +105,11 @@ export default class SubscriptionService {
|
||||
}
|
||||
}
|
||||
|
||||
public async setSshConfig() {
|
||||
const docs = await SubscriptionModel.findAll();
|
||||
this.sshKeyService.setSshConfig(docs);
|
||||
}
|
||||
|
||||
private async promiseExec(command: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
@@ -167,18 +122,6 @@ export default class SubscriptionService {
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
return {
|
||||
onBefore: async (startTime) => {
|
||||
@@ -191,7 +134,7 @@ export default class SubscriptionService {
|
||||
},
|
||||
{ where: { id: doc.id } },
|
||||
);
|
||||
const absolutePath = await this.handleLogPath(
|
||||
const absolutePath = await handleLogPath(
|
||||
logPath as string,
|
||||
`## 开始执行... ${startTime.format('YYYY-MM-DD HH:mm:ss')}\n`,
|
||||
);
|
||||
@@ -221,7 +164,7 @@ export default class SubscriptionService {
|
||||
},
|
||||
onEnd: async (cp, endTime, diff) => {
|
||||
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
|
||||
let afterStr = '';
|
||||
@@ -258,12 +201,12 @@ export default class SubscriptionService {
|
||||
},
|
||||
onError: async (message: string) => {
|
||||
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}`);
|
||||
},
|
||||
onLog: async (message: string) => {
|
||||
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}`);
|
||||
},
|
||||
};
|
||||
@@ -273,6 +216,7 @@ export default class SubscriptionService {
|
||||
const tab = new Subscription(payload);
|
||||
const doc = await this.insert(tab);
|
||||
await this.handleTask(doc);
|
||||
await this.setSshConfig();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -281,8 +225,11 @@ export default class SubscriptionService {
|
||||
}
|
||||
|
||||
public async update(payload: Subscription): Promise<Subscription> {
|
||||
const newDoc = await this.updateDb(payload);
|
||||
const doc = await this.getDb({ id: payload.id });
|
||||
const tab = new Subscription({ ...doc, ...payload });
|
||||
const newDoc = await this.updateDb(tab);
|
||||
await this.handleTask(newDoc, !newDoc.is_disabled);
|
||||
await this.setSshConfig();
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -325,12 +272,15 @@ export default class SubscriptionService {
|
||||
public async remove(ids: number[]) {
|
||||
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
|
||||
for (const doc of docs) {
|
||||
await this.handleTask(doc, false, false);
|
||||
await this.handleTask(doc, false);
|
||||
}
|
||||
await SubscriptionModel.destroy({ where: { id: ids } });
|
||||
await this.setSshConfig();
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Subscription> {
|
||||
public async getDb(
|
||||
query: FindOptions<Subscription>['where'],
|
||||
): Promise<Subscription> {
|
||||
const doc: any = await SubscriptionModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Subscription);
|
||||
}
|
||||
@@ -356,7 +306,7 @@ export default class SubscriptionService {
|
||||
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(
|
||||
`${absolutePath}`,
|
||||
@@ -378,7 +328,7 @@ export default class SubscriptionService {
|
||||
return;
|
||||
}
|
||||
|
||||
const command = this.formatCommand(subscription);
|
||||
const command = formatCommand(subscription);
|
||||
|
||||
await this.scheduleService.runTask(
|
||||
command,
|
||||
@@ -387,19 +337,21 @@ export default class SubscriptionService {
|
||||
}
|
||||
|
||||
public async disabled(ids: number[]) {
|
||||
await SubscriptionModel.update({ is_disabled: 1 }, { where: { id: ids } });
|
||||
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
|
||||
await this.setSshConfig();
|
||||
for (const doc of docs) {
|
||||
await this.handleTask(doc, false);
|
||||
}
|
||||
await SubscriptionModel.update({ is_disabled: 1 }, { where: { id: ids } });
|
||||
}
|
||||
|
||||
public async enabled(ids: number[]) {
|
||||
await SubscriptionModel.update({ is_disabled: 0 }, { where: { id: ids } });
|
||||
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
|
||||
await this.setSshConfig();
|
||||
for (const doc of docs) {
|
||||
await this.handleTask(doc);
|
||||
}
|
||||
await SubscriptionModel.update({ is_disabled: 0 }, { where: { id: ids } });
|
||||
}
|
||||
|
||||
public async log(id: number) {
|
||||
@@ -408,7 +360,7 @@ export default class SubscriptionService {
|
||||
return '';
|
||||
}
|
||||
|
||||
const absolutePath = await this.handleLogPath(doc.log_path as string);
|
||||
const absolutePath = await handleLogPath(doc.log_path as string);
|
||||
return getFileContentByName(absolutePath);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,17 @@ import * as fs from 'fs';
|
||||
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
|
||||
import { NotificationInfo } from '../data/notify';
|
||||
import NotificationService from './notify';
|
||||
import ScheduleService from './schedule';
|
||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import got from 'got';
|
||||
import { parseContentVersion, parseVersion } from '../config/util';
|
||||
import {
|
||||
getPid,
|
||||
killTask,
|
||||
parseContentVersion,
|
||||
parseVersion,
|
||||
} from '../config/util';
|
||||
import { TASK_COMMAND } from '../config/const';
|
||||
|
||||
@Service()
|
||||
export default class SystemService {
|
||||
@@ -170,4 +176,39 @@ export default class SystemService {
|
||||
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: '任务未找到' };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import { createRandomString, getNetIp, getPlatform } from '../config/util';
|
||||
import {
|
||||
createRandomString,
|
||||
fileExist,
|
||||
getNetIp,
|
||||
getPlatform,
|
||||
} from '../config/util';
|
||||
import config from '../config';
|
||||
import * as fs from 'fs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
@@ -205,17 +210,16 @@ export default class UserService {
|
||||
}
|
||||
|
||||
private initAuthInfo() {
|
||||
const newPassword = createRandomString(16, 22);
|
||||
fs.writeFileSync(
|
||||
config.authConfigFile,
|
||||
JSON.stringify({
|
||||
username: 'admin',
|
||||
password: newPassword,
|
||||
password: 'admin',
|
||||
}),
|
||||
);
|
||||
return {
|
||||
code: 100,
|
||||
message: '已初始化密码,请前往auth.json查看并重新登录',
|
||||
message: '未找到认证文件,重新初始化',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -240,13 +244,18 @@ export default class UserService {
|
||||
return { code: 200, data: avatar, message: '更新成功' };
|
||||
}
|
||||
|
||||
public getUserInfo(): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
fs.readFile(config.authConfigFile, 'utf8', (err, data) => {
|
||||
if (err) console.log(err);
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
public async getUserInfo(): Promise<any> {
|
||||
const authFileExist = await fileExist(config.authConfigFile);
|
||||
if (!authFileExist) {
|
||||
fs.writeFileSync(
|
||||
config.authConfigFile,
|
||||
JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
}),
|
||||
);
|
||||
}
|
||||
return this.getAuthInfo();
|
||||
}
|
||||
|
||||
public initTwoFactor() {
|
||||
+31
-13
@@ -1,3 +1,12 @@
|
||||
FROM python:3.10-alpine as builder
|
||||
COPY package.json .npmrc pnpm-lock.yaml /tmp/build/
|
||||
RUN set -x \
|
||||
&& apk update \
|
||||
&& apk add nodejs npm git \
|
||||
&& npm i -g pnpm@8.3.1 \
|
||||
&& cd /tmp/build \
|
||||
&& pnpm --registry https://registry.npmmirror.com install --prod
|
||||
|
||||
FROM python:3.10-alpine
|
||||
|
||||
ARG QL_MAINTAINER="whyour"
|
||||
@@ -7,21 +16,19 @@ ARG QL_BRANCH=develop
|
||||
|
||||
ENV PNPM_HOME=/root/.local/share/pnpm \
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules:$PNPM_HOME \
|
||||
LANG=zh_CN.UTF-8 \
|
||||
NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules:/root/.local/share/pnpm/global/5/node_modules \
|
||||
LANG=C.UTF-8 \
|
||||
SHELL=/bin/bash \
|
||||
PS1="\u@\h:\w \$ " \
|
||||
QL_DIR=/ql \
|
||||
QL_BRANCH=${QL_BRANCH}
|
||||
|
||||
WORKDIR ${QL_DIR}
|
||||
|
||||
RUN set -x \
|
||||
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk update -f \
|
||||
&& apk upgrade \
|
||||
&& apk --no-cache add -f bash \
|
||||
coreutils \
|
||||
moreutils \
|
||||
git \
|
||||
curl \
|
||||
wget \
|
||||
@@ -32,6 +39,8 @@ RUN set -x \
|
||||
nodejs \
|
||||
jq \
|
||||
openssh \
|
||||
procps \
|
||||
netcat-openbsd \
|
||||
npm \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& apk update \
|
||||
@@ -40,21 +49,30 @@ RUN set -x \
|
||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||
&& git config --global user.name "qinglong" \
|
||||
&& git config --global http.postBuffer 524288000 \
|
||||
&& npm install -g pnpm \
|
||||
&& pnpm add -g pm2 ts-node typescript tslib \
|
||||
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||
&& npm install -g pnpm@8.3.1 \
|
||||
&& cd && pnpm config set registry https://registry.npmmirror.com \
|
||||
&& pnpm add -g pm2 tsx \
|
||||
&& rm -rf /root/.pnpm-store \
|
||||
&& rm -rf /root/.local/share/pnpm/store \
|
||||
&& rm -rf /root/.cache \
|
||||
&& rm -rf /root/.npm
|
||||
|
||||
ARG SOURCE_COMMIT
|
||||
RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||
&& cd ${QL_DIR} \
|
||||
&& cp -f .env.example .env \
|
||||
&& chmod 777 ${QL_DIR}/shell/*.sh \
|
||||
&& chmod 777 ${QL_DIR}/docker/*.sh \
|
||||
&& pnpm install --prod \
|
||||
&& rm -rf /root/.pnpm-store \
|
||||
&& rm -rf /root/.local/share/pnpm/store \
|
||||
&& rm -rf /root/.cache \
|
||||
&& rm -rf /root/.npm \
|
||||
&& 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 \
|
||||
&& cp -rf /static/* ${QL_DIR}/static \
|
||||
&& rm -rf /static
|
||||
|
||||
COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
|
||||
|
||||
WORKDIR ${QL_DIR}
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
|
||||
CMD curl -sf http://127.0.0.1:5400/api/health || exit 1
|
||||
|
||||
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
||||
|
||||
@@ -7,3 +7,8 @@ services:
|
||||
ports:
|
||||
- "0.0.0.0:5700:5700"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://127.0.0.1:5400/api/health", "||", "exit", "1"]
|
||||
interval: 2m
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
+19
-25
@@ -9,15 +9,22 @@ export isFirstStartServer=true
|
||||
echo -e "======================1. 检测配置文件========================\n"
|
||||
make_dir /etc/nginx/conf.d
|
||||
make_dir /run/nginx
|
||||
cp -fv $nginx_conf /etc/nginx/nginx.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
|
||||
init_nginx
|
||||
fix_config
|
||||
|
||||
pm2 l &>/dev/null
|
||||
|
||||
patch_version &>/dev/null
|
||||
echo
|
||||
|
||||
echo -e "======================2. 安装依赖========================\n"
|
||||
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
|
||||
echo
|
||||
|
||||
@@ -25,36 +32,23 @@ echo -e "======================3. 启动nginx========================\n"
|
||||
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
||||
echo -e "nginx启动成功...\n"
|
||||
|
||||
echo -e "======================4. 启动面板监控========================\n"
|
||||
pm2 delete public &>/dev/null
|
||||
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.js -n schedule --source-map-support --time
|
||||
echo -e "定时任务启动成功...\n"
|
||||
echo -e "======================4. 启动pm2服务========================\n"
|
||||
reload_pm2
|
||||
|
||||
if [[ $AutoStartBot == true ]]; then
|
||||
echo -e "======================7. 启动bot========================\n"
|
||||
nohup ql bot >$dir_log/bot.log 2>&1 &
|
||||
echo -e "======================5. 启动bot========================\n"
|
||||
nohup ql -l bot >$dir_log/bot.log 2>&1 &
|
||||
echo -e "bot后台启动中...\n"
|
||||
fi
|
||||
|
||||
if [[ $EnableExtraShell == true ]]; then
|
||||
echo -e "======================8. 执行自定义脚本========================\n"
|
||||
nohup ql extra >$dir_log/extra.log 2>&1 &
|
||||
echo -e "====================6. 执行自定义脚本========================\n"
|
||||
nohup ql -l extra >$dir_log/extra.log 2>&1 &
|
||||
echo -e "自定义脚本后台执行中...\n"
|
||||
fi
|
||||
|
||||
echo -e "############################################################\n"
|
||||
echo -e "容器启动成功..."
|
||||
echo -e "\n请先访问5700端口,登录成功面板之后再执行添加定时任务..."
|
||||
echo -e "############################################################\n"
|
||||
|
||||
crond -f >/dev/null
|
||||
|
||||
+5
-1
@@ -13,6 +13,7 @@ map $http_upgrade $connection_upgrade {
|
||||
|
||||
server {
|
||||
listen 5700;
|
||||
IPV6_CONFIG
|
||||
root /ql/static/dist;
|
||||
ssl_session_timeout 5m;
|
||||
|
||||
@@ -20,7 +21,8 @@ server {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
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/ {
|
||||
@@ -28,6 +30,7 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://baseApi/api/;
|
||||
proxy_buffering off;
|
||||
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
@@ -38,6 +41,7 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://baseApi/open/;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'schedule',
|
||||
max_restarts: 10,
|
||||
kill_timeout: 15000,
|
||||
wait_ready: true,
|
||||
source_map_support: true,
|
||||
time: true,
|
||||
script: 'static/build/schedule/index.js',
|
||||
},
|
||||
{
|
||||
name: 'public',
|
||||
max_restarts: 10,
|
||||
kill_timeout: 15000,
|
||||
wait_ready: true,
|
||||
source_map_support: true,
|
||||
time: true,
|
||||
script: 'static/build/public.js',
|
||||
},
|
||||
{
|
||||
name: 'panel',
|
||||
max_restarts: 10,
|
||||
kill_timeout: 15000,
|
||||
wait_ready: true,
|
||||
source_map_support: true,
|
||||
time: true,
|
||||
script: 'static/build/app.js',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"watch": ["back", ".env"],
|
||||
"ext": "js,ts,json",
|
||||
"ignore": ["src/**/*.spec.ts"],
|
||||
"exec": "ts-node --transpile-only ./back/app.ts"
|
||||
}
|
||||
+22
-12
@@ -2,15 +2,16 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "concurrently -n w: npm:start:*",
|
||||
"start:env": "pnpm run --filter @qinglong/env start",
|
||||
"start:front": "pnpm run --filter @qinglong/web start",
|
||||
"start:back": "pnpm run --filter @qinglong/back start",
|
||||
"start:public": "pnpm run --filter @qinglong/public start",
|
||||
"start:front": "max dev",
|
||||
"start:back": "nodemon",
|
||||
"start:public": "ts-node --transpile-only ./back/public.ts",
|
||||
"start:rpc": "ts-node --transpile-only ./back/schedule/index.ts",
|
||||
"build:front": "max build",
|
||||
"build:back": "tsc -p tsconfig.back.json",
|
||||
"panel": "npm run build:back && node static/build/app.js",
|
||||
"schedule": "npm run build:back && node static/build/schedule.js",
|
||||
"schedule": "npm run build:back && node static/build/schedule/index.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/*.proto --ts_proto_out=./ --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true",
|
||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||
"postinstall": "max setup 2>/dev/null || true",
|
||||
"test": "umi-test",
|
||||
@@ -43,7 +44,8 @@
|
||||
"monaco-editor",
|
||||
"rc-field-form",
|
||||
"@types/lodash.merge",
|
||||
"rollup"
|
||||
"rollup",
|
||||
"styled-components"
|
||||
],
|
||||
"allowedVersions": {
|
||||
"react": "18",
|
||||
@@ -53,6 +55,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.8.13",
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^7.12.1",
|
||||
"@sentry/tracing": "^7.12.1",
|
||||
@@ -69,6 +72,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.8.2",
|
||||
"hpagent": "^0.1.2",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
@@ -77,12 +81,13 @@
|
||||
"nedb": "^1.8.0",
|
||||
"node-schedule": "^2.1.0",
|
||||
"nodemailer": "^6.7.2",
|
||||
"protobufjs": "^7.2.3",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sequelize": "^6.25.5",
|
||||
"serve-handler": "^6.1.3",
|
||||
"sockjs": "^0.3.24",
|
||||
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
|
||||
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3",
|
||||
"toad-scheduler": "^1.6.0",
|
||||
"typedi": "^0.10.0",
|
||||
"uuid": "^8.3.2",
|
||||
@@ -91,8 +96,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@ant-design/pro-layout": "^6.33.1",
|
||||
"@monaco-editor/react": "4.4.6",
|
||||
"@ant-design/pro-layout": "6.38.22",
|
||||
"@monaco-editor/react": "4.2.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@sentry/react": "^7.12.1",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
@@ -109,21 +114,22 @@
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/serve-handler": "^6.1.1",
|
||||
"@types/sockjs": "^0.3.33",
|
||||
"@types/sockjs-client": "^1.5.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@umijs/max": "^4.0.21",
|
||||
"@umijs/max": "^4.0.55",
|
||||
"@umijs/ssr-darkreader": "^4.9.45",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"antd": "^4.23.0",
|
||||
"antd": "^4.24.8",
|
||||
"antd-img-crop": "^4.2.3",
|
||||
"codemirror": "^5.65.2",
|
||||
"compression-webpack-plugin": "9.2.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"monaco-editor": "0.33.0",
|
||||
"nodemon": "^2.0.15",
|
||||
"prettier": "^2.5.1",
|
||||
"qiniu": "^7.4.0",
|
||||
@@ -132,6 +138,7 @@
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
@@ -139,10 +146,13 @@
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
"ts-proto": "^1.146.0",
|
||||
"tslib": "^2.4.0",
|
||||
"tsx": "^3.12.3",
|
||||
"typescript": "4.8.4",
|
||||
"umi-request": "^1.4.0",
|
||||
"vh-check": "^2.0.5",
|
||||
"virtualizedtableforantd4": "1.3.0",
|
||||
"webpack": "^5.70.0",
|
||||
"yorkie": "^2.0.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"watch": ["src", ".env"],
|
||||
"ext": "js,ts,json",
|
||||
"ignore": ["src/**/*.spec.ts"],
|
||||
"exec": "ts-node --transpile-only ./src/app.ts"
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
{
|
||||
"name": "@qinglong/back",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodemon",
|
||||
"build": "tsc"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,less,md,json}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.ts?(x)": [
|
||||
"prettier --parser=typescript --write"
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"antd",
|
||||
"dva",
|
||||
"postcss",
|
||||
"webpack",
|
||||
"eslint",
|
||||
"stylelint",
|
||||
"redux",
|
||||
"@babel/core",
|
||||
"monaco-editor",
|
||||
"rc-field-form",
|
||||
"@types/lodash.merge",
|
||||
"rollup"
|
||||
],
|
||||
"allowedVersions": {
|
||||
"react": "18",
|
||||
"react-dom": "18",
|
||||
"dva-core": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^7.12.1",
|
||||
"@sentry/tracing": "^7.12.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"celebrate": "^15.0.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.2.1",
|
||||
"dayjs": "^1.11.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.17.3",
|
||||
"express-jwt": "^6.1.1",
|
||||
"express-urlrewrite": "^1.4.0",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.8.2",
|
||||
"hpagent": "^0.1.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.4",
|
||||
"nedb": "^1.8.0",
|
||||
"node-schedule": "^2.1.0",
|
||||
"nodemailer": "^6.7.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sequelize": "^6.25.5",
|
||||
"serve-handler": "^6.1.3",
|
||||
"sockjs": "^0.3.24",
|
||||
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
|
||||
"toad-scheduler": "^1.6.0",
|
||||
"typedi": "^0.10.0",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.6.0",
|
||||
"yargs": "^17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@ant-design/pro-layout": "^6.33.1",
|
||||
"@monaco-editor/react": "4.4.6",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@sentry/react": "^7.12.1",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-jwt": "^6.0.4",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/lodash": "^4.14.185",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/nedb": "^1.8.12",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/node-schedule": "^1.3.2",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/serve-handler": "^6.1.1",
|
||||
"@types/sockjs": "^0.3.33",
|
||||
"@types/sockjs-client": "^1.5.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@umijs/max": "^4.0.21",
|
||||
"@umijs/ssr-darkreader": "^4.9.45",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"antd": "^4.23.0",
|
||||
"antd-img-crop": "^4.2.3",
|
||||
"codemirror": "^5.65.2",
|
||||
"compression-webpack-plugin": "9.2.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nodemon": "^2.0.15",
|
||||
"prettier": "^2.5.1",
|
||||
"qiniu": "^7.4.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "4.8.4",
|
||||
"umi-request": "^1.4.0",
|
||||
"vh-check": "^2.0.5",
|
||||
"webpack": "^5.70.0",
|
||||
"yorkie": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export const LOG_END_SYMBOL = '\n ';
|
||||
@@ -1,97 +0,0 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
@Service()
|
||||
export default class SshKeyService {
|
||||
private homedir = os.homedir();
|
||||
private sshPath = path.resolve(this.homedir, '.ssh');
|
||||
private sshConfigFilePath = path.resolve(this.sshPath, 'config');
|
||||
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
|
||||
private generatePrivateKeyFile(alias: string, key: string): void {
|
||||
try {
|
||||
fs.writeFileSync(`${this.sshPath}/${alias}`, `${key}${os.EOL}`, {
|
||||
encoding: 'utf8',
|
||||
mode: '400',
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error('生成私钥文件失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
private getConfigRegx(alias: string) {
|
||||
return new RegExp(
|
||||
`Host ${alias}\n.*[^StrictHostKeyChecking]*.*[\n]*.*StrictHostKeyChecking no`,
|
||||
'g',
|
||||
);
|
||||
}
|
||||
|
||||
private removePrivateKeyFile(alias: string): void {
|
||||
try {
|
||||
fs.unlinkSync(`${this.sshPath}/${alias}`);
|
||||
} catch (error) {
|
||||
this.logger.error('删除私钥文件失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
private generateSingleSshConfig(
|
||||
alias: string,
|
||||
host: string,
|
||||
proxy?: string,
|
||||
): string {
|
||||
if (host === 'github.com') {
|
||||
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` : '';
|
||||
return `\nHost ${alias}\n Hostname ${host}\n IdentityFile ${this.sshPath}/${alias}\n StrictHostKeyChecking no\n${proxyStr}`;
|
||||
}
|
||||
|
||||
private generateSshConfig(configs: string[]) {
|
||||
try {
|
||||
for (const config of configs) {
|
||||
fs.appendFileSync(this.sshConfigFilePath, config, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('写入ssh配置文件失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
private removeSshConfig(alias: string) {
|
||||
try {
|
||||
const configRegx = this.getConfigRegx(alias);
|
||||
const data = fs
|
||||
.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' })
|
||||
.replace(configRegx, '')
|
||||
.replace(/\n[\n]+/g, '\n');
|
||||
fs.writeFileSync(this.sshConfigFilePath, data, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(`删除ssh配置文件${alias}失败`, error);
|
||||
}
|
||||
}
|
||||
|
||||
public addSSHKey(
|
||||
key: string,
|
||||
alias: string,
|
||||
host: string,
|
||||
proxy?: string,
|
||||
): void {
|
||||
this.generatePrivateKeyFile(alias, key);
|
||||
const config = this.generateSingleSshConfig(alias, host, proxy);
|
||||
this.removeSshConfig(alias);
|
||||
this.generateSshConfig([config]);
|
||||
}
|
||||
|
||||
public removeSSHKey(alias: string, host: string, proxy?: string): void {
|
||||
this.removePrivateKeyFile(alias);
|
||||
const config = this.generateSingleSshConfig(alias, host, proxy);
|
||||
this.removeSshConfig(config);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"lib": ["es2021", "esnext.asynciterable", "DOM"],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./src/types",
|
||||
"./node_modules/celebrate/lib/index.d.ts"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./back/*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"pretty": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./static/build",
|
||||
"allowJs": true,
|
||||
"noEmit": false,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["./back/**/*"],
|
||||
"exclude": ["node_modules", "tests"]
|
||||
}
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "js,ts,json",
|
||||
"ignore": ["src/**/*.spec.ts"],
|
||||
"exec": "ts-node --transpile-only ./src/env.ts"
|
||||
}
|
||||
Vendored
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "@qinglong/env",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "nodemon",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.0"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
if (!process.env.QL_DIR) {
|
||||
// 声明QL_DIR环境变量
|
||||
let qlHomePath = path.join(__dirname, '../../../');
|
||||
// 生产环境
|
||||
if (qlHomePath.endsWith('/static/')) {
|
||||
qlHomePath = path.join(qlHomePath, '../');
|
||||
}
|
||||
process.env.QL_DIR = qlHomePath.replace(/\/$/g, '');
|
||||
}
|
||||
const rootPath = process.env.QL_DIR as string;
|
||||
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
|
||||
|
||||
if (envFound.error) {
|
||||
throw new Error("⚠️ Couldn't find .env file ⚠️");
|
||||
}
|
||||
|
||||
const envs = process.env;
|
||||
console.log(envs);
|
||||
export default { ...envs };
|
||||
Vendored
-28
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"lib": ["es2021", "esnext.asynciterable", "DOM"],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./src/types",
|
||||
"./node_modules/celebrate/lib/index.d.ts"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"pretty": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./lib",
|
||||
"allowJs": true,
|
||||
"noEmit": false,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["node_modules", "tests"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "js,ts,json",
|
||||
"ignore": ["src/**/*.spec.ts"],
|
||||
"exec": "ts-node --transpile-only ./src/public.ts"
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "@qinglong/public",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodemon",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@qinglong/env": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import express from 'express';
|
||||
import { exec } from 'child_process';
|
||||
import env from '@qinglong/env';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.get('/api/public/panel/log', (req, res) => {
|
||||
exec(
|
||||
'pm2 logs panel --lines 500 --nostream --timestamp',
|
||||
(err, stdout, stderr) => {
|
||||
if (err || stderr) {
|
||||
return res.send({ code: 400, message: (err && err.message) || stderr });
|
||||
}
|
||||
return res.send({ code: 200, data: stdout });
|
||||
},
|
||||
);
|
||||
});
|
||||
console.log(env);
|
||||
app
|
||||
.listen(env.PUBLIC_PORT, async () => {
|
||||
console.log('启动成功');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"lib": ["es2021", "esnext.asynciterable", "DOM"],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./src/types",
|
||||
"./node_modules/celebrate/lib/index.d.ts"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./back/*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"pretty": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./static/build",
|
||||
"allowJs": true,
|
||||
"noEmit": false,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["./back/**/*"],
|
||||
"exclude": ["node_modules", "tests"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "js,ts,json",
|
||||
"ignore": ["src/**/*.spec.ts"],
|
||||
"exec": "ts-node --transpile-only ./src/public.ts"
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
{
|
||||
"name": "@qinglong/schedule",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodemon",
|
||||
"build": "tsc"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,less,md,json}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.ts?(x)": [
|
||||
"prettier --parser=typescript --write"
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"antd",
|
||||
"dva",
|
||||
"postcss",
|
||||
"webpack",
|
||||
"eslint",
|
||||
"stylelint",
|
||||
"redux",
|
||||
"@babel/core",
|
||||
"monaco-editor",
|
||||
"rc-field-form",
|
||||
"@types/lodash.merge",
|
||||
"rollup"
|
||||
],
|
||||
"allowedVersions": {
|
||||
"react": "18",
|
||||
"react-dom": "18",
|
||||
"dva-core": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^7.12.1",
|
||||
"@sentry/tracing": "^7.12.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"celebrate": "^15.0.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.2.1",
|
||||
"dayjs": "^1.11.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.17.3",
|
||||
"express-jwt": "^6.1.1",
|
||||
"express-urlrewrite": "^1.4.0",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.8.2",
|
||||
"hpagent": "^0.1.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.4",
|
||||
"nedb": "^1.8.0",
|
||||
"node-schedule": "^2.1.0",
|
||||
"nodemailer": "^6.7.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sequelize": "^6.25.5",
|
||||
"serve-handler": "^6.1.3",
|
||||
"sockjs": "^0.3.24",
|
||||
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
|
||||
"toad-scheduler": "^1.6.0",
|
||||
"typedi": "^0.10.0",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.6.0",
|
||||
"yargs": "^17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@ant-design/pro-layout": "^6.33.1",
|
||||
"@monaco-editor/react": "4.4.6",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@sentry/react": "^7.12.1",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-jwt": "^6.0.4",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/lodash": "^4.14.185",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/nedb": "^1.8.12",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/node-schedule": "^1.3.2",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/serve-handler": "^6.1.1",
|
||||
"@types/sockjs": "^0.3.33",
|
||||
"@types/sockjs-client": "^1.5.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@umijs/max": "^4.0.21",
|
||||
"@umijs/ssr-darkreader": "^4.9.45",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"antd": "^4.23.0",
|
||||
"antd-img-crop": "^4.2.3",
|
||||
"codemirror": "^5.65.2",
|
||||
"compression-webpack-plugin": "9.2.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nodemon": "^2.0.15",
|
||||
"prettier": "^2.5.1",
|
||||
"qiniu": "^7.4.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "4.8.4",
|
||||
"umi-request": "^1.4.0",
|
||||
"vh-check": "^2.0.5",
|
||||
"webpack": "^5.70.0",
|
||||
"yorkie": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import schedule from 'node-schedule';
|
||||
import express from 'express';
|
||||
import { exec } from 'child_process';
|
||||
import Logger from './loaders/logger';
|
||||
import { CrontabModel, CrontabStatus } from './data/cron';
|
||||
import config from './config';
|
||||
|
||||
const app = express();
|
||||
|
||||
const run = async () => {
|
||||
CrontabModel.findAll({ where: {} })
|
||||
.then((docs) => {
|
||||
if (docs && docs.length > 0) {
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const task = docs[i];
|
||||
const _schedule = task.schedule && task.schedule.split(/ +/);
|
||||
if (
|
||||
_schedule &&
|
||||
_schedule.length > 5 &&
|
||||
task.status !== CrontabStatus.disabled &&
|
||||
!task.isDisabled &&
|
||||
task.schedule
|
||||
) {
|
||||
schedule.scheduleJob(task.schedule, function () {
|
||||
let command = task.command as string;
|
||||
if (!command.includes('task ') && !command.includes('ql ')) {
|
||||
command = `task ${command}`;
|
||||
}
|
||||
exec(`ID=${task.id} ${command}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
Logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
app
|
||||
.listen(config.cronPort, async () => {
|
||||
await require('./loaders/sentry').default({ expressApp: app });
|
||||
await require('./loaders/db').default();
|
||||
|
||||
await run();
|
||||
Logger.debug('定时任务服务启动成功!');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
{
|
||||
"name": "@qinglong/web",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "max dev",
|
||||
"build": "max build"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,less,md,json}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.ts?(x)": [
|
||||
"prettier --parser=typescript --write"
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"antd",
|
||||
"dva",
|
||||
"postcss",
|
||||
"webpack",
|
||||
"eslint",
|
||||
"stylelint",
|
||||
"redux",
|
||||
"@babel/core",
|
||||
"monaco-editor",
|
||||
"rc-field-form",
|
||||
"@types/lodash.merge",
|
||||
"rollup"
|
||||
],
|
||||
"allowedVersions": {
|
||||
"react": "18",
|
||||
"react-dom": "18",
|
||||
"dva-core": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^7.12.1",
|
||||
"@sentry/tracing": "^7.12.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"celebrate": "^15.0.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.2.1",
|
||||
"dayjs": "^1.11.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.17.3",
|
||||
"express-jwt": "^6.1.1",
|
||||
"express-urlrewrite": "^1.4.0",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.8.2",
|
||||
"hpagent": "^0.1.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.4",
|
||||
"nedb": "^1.8.0",
|
||||
"node-schedule": "^2.1.0",
|
||||
"nodemailer": "^6.7.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sequelize": "^6.25.5",
|
||||
"serve-handler": "^6.1.3",
|
||||
"sockjs": "^0.3.24",
|
||||
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
|
||||
"toad-scheduler": "^1.6.0",
|
||||
"typedi": "^0.10.0",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.6.0",
|
||||
"yargs": "^17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@ant-design/pro-layout": "^6.33.1",
|
||||
"@monaco-editor/react": "4.4.6",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@sentry/react": "^7.12.1",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-jwt": "^6.0.4",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/lodash": "^4.14.185",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/nedb": "^1.8.12",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/node-schedule": "^1.3.2",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/serve-handler": "^6.1.1",
|
||||
"@types/sockjs": "^0.3.33",
|
||||
"@types/sockjs-client": "^1.5.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@umijs/max": "^4.0.21",
|
||||
"@umijs/ssr-darkreader": "^4.9.45",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"antd": "^4.23.0",
|
||||
"antd-img-crop": "^4.2.3",
|
||||
"codemirror": "^5.65.2",
|
||||
"compression-webpack-plugin": "9.2.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nodemon": "^2.0.15",
|
||||
"prettier": "^2.5.1",
|
||||
"qiniu": "^7.4.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "4.8.4",
|
||||
"umi-request": "^1.4.0",
|
||||
"vh-check": "^2.0.5",
|
||||
"webpack": "^5.70.0",
|
||||
"yorkie": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// This file is generated by Umi automatically
|
||||
// DO NOT CHANGE IT MANUALLY!
|
||||
import React from 'react';
|
||||
import { Outlet, useOutletContext } from 'umi';
|
||||
export default function EmptyRoute() {
|
||||
const context = useOutletContext();
|
||||
return <Outlet context={context} />;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// This file is generated by Umi automatically
|
||||
// DO NOT CHANGE IT MANUALLY!
|
||||
interface IDefaultRuntimeConfig {
|
||||
onRouteChange?: (props: {
|
||||
routes: any;
|
||||
clientRoutes: any;
|
||||
location: any;
|
||||
action: any;
|
||||
}) => void;
|
||||
patchRoutes?: (props: { routes: any }) => void;
|
||||
patchClientRoutes?: (props: { routes: any }) => void;
|
||||
render?: (oldRender: () => void) => void;
|
||||
rootContainer?: (lastRootContainer: JSX.Element, args?: any) => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type RuntimeConfig = IDefaultRuntimeConfig;
|
||||
|
||||
export function defineApp(config: RuntimeConfig): RuntimeConfig {
|
||||
return config;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// This file is generated by Umi automatically
|
||||
// DO NOT CHANGE IT MANUALLY!
|
||||
import {
|
||||
createHashHistory,
|
||||
createMemoryHistory,
|
||||
createBrowserHistory,
|
||||
History,
|
||||
} from '/Users/whyour/resp/qinglong/node_modules/.pnpm/@umijs+renderer-react@4.0.21_ef5jwxihqo6n7gxfmzogljlgcm/node_modules/@umijs/renderer-react';
|
||||
|
||||
let history: History;
|
||||
let basename: string = '/';
|
||||
export function createHistory(opts: any) {
|
||||
let h;
|
||||
if (opts.type === 'hash') {
|
||||
h = createHashHistory();
|
||||
} else if (opts.type === 'memory') {
|
||||
h = createMemoryHistory(opts);
|
||||
} else {
|
||||
h = createBrowserHistory();
|
||||
}
|
||||
if (opts.basename) {
|
||||
basename = opts.basename;
|
||||
}
|
||||
|
||||
history = {
|
||||
...h,
|
||||
push(to, state) {
|
||||
h.push(patchTo(to), state);
|
||||
},
|
||||
replace(to, state) {
|
||||
h.replace(patchTo(to), state);
|
||||
},
|
||||
get location() {
|
||||
return h.location;
|
||||
},
|
||||
get action() {
|
||||
return h.action;
|
||||
},
|
||||
};
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
// Patch `to` to support basename
|
||||
// Refs:
|
||||
// https://github.com/remix-run/history/blob/3e9dab4/packages/history/index.ts#L484
|
||||
// https://github.com/remix-run/history/blob/dev/docs/api-reference.md#to
|
||||
function patchTo(to: any) {
|
||||
if (typeof to === 'string') {
|
||||
return `${stripLastSlash(basename)}${to}`;
|
||||
} else if (typeof to === 'object' && to.pathname) {
|
||||
return {
|
||||
...to,
|
||||
pathname: `${stripLastSlash(basename)}${to.pathname}`,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unexpected to: ${to}`);
|
||||
}
|
||||
}
|
||||
|
||||
function stripLastSlash(path) {
|
||||
return path.slice(-1) === '/' ? path.slice(0, -1) : path;
|
||||
}
|
||||
|
||||
export { history };
|
||||
@@ -1,44 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// This file is generated by Umi automatically
|
||||
// DO NOT CHANGE IT MANUALLY!
|
||||
import { PluginManager } from 'umi';
|
||||
|
||||
function __defaultExport(obj) {
|
||||
if (obj.default) {
|
||||
return typeof obj.default === 'function' ? obj.default() : obj.default;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
export function getPlugins() {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getValidKeys() {
|
||||
return [
|
||||
'patchRoutes',
|
||||
'patchClientRoutes',
|
||||
'modifyContextOpts',
|
||||
'rootContainer',
|
||||
'innerProvider',
|
||||
'i18nProvider',
|
||||
'accessProvider',
|
||||
'dataflowProvider',
|
||||
'outerProvider',
|
||||
'render',
|
||||
'onRouteChange',
|
||||
'qiankun',
|
||||
];
|
||||
}
|
||||
|
||||
let pluginManager = null;
|
||||
export function createPluginManager() {
|
||||
pluginManager = PluginManager.create({
|
||||
plugins: getPlugins(),
|
||||
validKeys: getValidKeys(),
|
||||
});
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
export function getPluginManager() {
|
||||
return pluginManager;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user