Compare commits

..

17 Commits

Author SHA1 Message Date
whyour 77a8e00b17 更新版本 v2.16.3 2023-09-24 13:39:53 +08:00
whyour 99281a061e 修复更新历史定时任务规则未生效 2023-09-24 13:39:52 +08:00
whyour 22eedebf14 修复任务管理创建视图 2023-09-24 00:47:56 +08:00
whyour d7e0531935 修复系统设置展示版本失败 2023-09-23 11:14:24 +08:00
whyour 16734326cf 修复 ws 变量错误 2023-09-22 20:44:47 +08:00
whyour 956cfe18be 环境变量名称增加复制功能 2023-09-22 00:47:36 +08:00
whyour a864a56917 修复调试脚本日志丢失 2023-09-22 00:46:16 +08:00
whyour ab3fc9b5f1 定时任务增加运行任务前和运行任务后参数 2023-09-19 22:44:32 +08:00
whyour 8d899f1a53 更新 workflow 2023-09-19 00:30:45 +08:00
whyour a1eef2c644 修复使用 ubuntu-latest linux/s390x 无法使用 npm 2023-09-18 13:09:14 +08:00
zui-lang aab096843c 消息通知 Bark 增加额外参数:时效性通知、跳转Url (#2112) 2023-09-17 01:01:34 +08:00
whyour acc7443004 定时任务支持多个定时规则 2023-09-16 00:15:50 +08:00
whyour 042d7d3b8e 修复任务管理创建视图 placeholder,更新 npm 包 2023-09-15 21:27:53 +08:00
whyour 0511a4af0d 增加 QlPort 环境变量,支持设置服务启动的端口,修复定时任务视图操作符筛选 2023-09-15 17:57:28 +08:00
whyour 748a099087 修复日志滚动逻辑 2023-09-12 22:28:26 +08:00
whyour 742d0b2135 更新版本 v2.16.2 2023-09-02 00:41:16 +08:00
whyour 2081364325 修改定时任务置顶样式 2023-09-02 00:41:13 +08:00
59 changed files with 945 additions and 636 deletions
+11 -11
View File
@@ -6,10 +6,9 @@ on:
- 'master'
- 'develop'
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
- 'v*'
schedule:
# 参考 https://jasonet.co/posts/scheduled-actions/
- cron: '00 14 * * *' # GMT 15:00 => 北京时间 23:00
- cron: '00 20 * * *'
workflow_dispatch:
jobs:
@@ -87,14 +86,15 @@ jobs:
build:
needs: build-static
runs-on: ubuntu-latest
# 由于 ubuntu-latest 中使用 linux6.x 内核 linux/s390x npm 无法使用
runs-on: ubuntu-20.04
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: '8.3.1'
@@ -108,13 +108,13 @@ jobs:
timezone: Asia/Shanghai
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -122,7 +122,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
${{ github.repository }}
@@ -142,14 +142,14 @@ jobs:
type=semver,pattern={{major}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build and push
id: docker_build
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
build-args: |
MAINTAINER=${{ github.repository_owner }}
+10
View File
@@ -61,7 +61,12 @@ npm i @whyour/qinglong
# curl -sSL get.docker.com | sh
docker run -dit \
-v $PWD/ql/data:/ql/data \
# The 5700 after the colon is the default port, if QlPort is set, it needs to be the same as QlPort.
-p 5700:5700 \
# Deployment paths are not required and begin and end with a slash, e.g. /test/.
-e QlBaseUrl="/" \
# Deployment port is not required, when using host mode, you can set the port after service startup, default 5700
-e QlPort="5700" \
--name qinglong \
--hostname qinglong \
--restart unless-stopped \
@@ -88,7 +93,12 @@ docker-compose down
podman run -dit \
--network bridge \
-v $PWD/ql/data:/ql/data \
# The 5700 after the colon is the default port, if QlPort is set, it needs to be the same as QlPort.
-p 5700:5700 \
# Deployment paths are not required and begin and end with a slash, e.g. /test/.
-e QlBaseUrl="/" \
# Deployment port is not required, when using host mode, you can set the port after service startup, default 5700
-e QlPort="5700" \
--name qinglong \
--hostname qinglong \
docker.io/whyour/qinglong:latest
+6
View File
@@ -63,9 +63,12 @@ npm i @whyour/qinglong
# curl -sSL get.docker.com | sh
docker run -dit \
-v $PWD/ql/data:/ql/data \
# 冒号后面的 5700 为默认端口,如果设置了 QlPort, 需要跟 QlPort 保持一致
-p 5700:5700 \
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
-e QlBaseUrl="/" \
# 部署端口非必须,当使用 host 模式时,可以设置服务启动后的端口,默认 5700
-e QlPort="5700" \
--name qinglong \
--hostname qinglong \
--restart unless-stopped \
@@ -92,9 +95,12 @@ docker-compose down
podman run -dit \
--network bridge \
-v $PWD/ql/data:/ql/data \
# 冒号后面的 5700 为默认端口,如果设置了 QlPort, 需要跟 QlPort 保持一致
-p 5700:5700 \
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
-e QlBaseUrl="/" \
# 部署端口非必须,当使用 host 模式时,可以设置服务启动后的端口,默认 5700
-e QlPort="5700" \
--name qinglong \
--hostname qinglong \
docker.io/whyour/qinglong:latest
+6
View File
@@ -176,6 +176,9 @@ export default (app: Router) => {
name: Joi.string().optional(),
labels: Joi.array().optional(),
sub_id: Joi.number().optional().allow(null),
extra_schedules: Joi.array().optional().allow(null),
task_before: Joi.string().optional().allow('').allow(null),
task_after: Joi.string().optional().allow('').allow(null),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@@ -333,6 +336,9 @@ export default (app: Router) => {
schedule: Joi.string().required(),
name: Joi.string().optional().allow(null),
sub_id: Joi.number().optional().allow(null),
extra_schedules: Joi.array().optional().allow(null),
task_before: Joi.string().optional().allow('').allow(null),
task_after: Joi.string().optional().allow('').allow(null),
id: Joi.number().required(),
}),
}),
+1 -1
View File
@@ -34,7 +34,7 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
let result = [];
const blacklist = ['node_modules', '.git'];
const blacklist = ['node_modules', '.git', '.pnpm'];
if (req.query.path) {
const targetPath = path.join(
config.scriptPath,
+1 -1
View File
@@ -1,5 +1,5 @@
import { Request, Response } from 'express';
import { pick } from 'lodash';
import pick from 'lodash/pick';
let pickedEnv: Record<string, string>;
+10 -1
View File
@@ -18,6 +18,9 @@ export class Crontab {
last_running_time?: number;
last_execution_time?: number;
sub_id?: number;
extra_schedules?: Array<{ schedule: string }>;
task_before?: string;
task_after?: string;
constructor(options: Crontab) {
this.name = options.name;
@@ -39,6 +42,9 @@ export class Crontab {
this.last_running_time = options.last_running_time || 0;
this.last_execution_time = options.last_execution_time || 0;
this.sub_id = options.sub_id;
this.extra_schedules = options.extra_schedules;
this.task_before = options.task_before;
this.task_after = options.task_after;
}
}
@@ -49,7 +55,7 @@ export enum CrontabStatus {
'disabled',
}
export interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
export interface CronInstance extends Model<Crontab, Crontab>, Crontab { }
export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
name: {
unique: 'compositeIndex',
@@ -75,4 +81,7 @@ export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
last_running_time: DataTypes.NUMBER,
last_execution_time: DataTypes.NUMBER,
sub_id: { type: DataTypes.NUMBER, allowNull: true },
extra_schedules: DataTypes.JSON,
task_before: DataTypes.STRING,
task_after: DataTypes.STRING,
});
+2
View File
@@ -55,6 +55,8 @@ export class BarkNotification extends NotificationBaseInfo {
public barkIcon = 'https://qn.whyour.cn/logo.png';
public barkSound = '';
public barkGroup = 'qinglong';
public barkLevel = 'active';
public barkUrl = '';
}
export class TelegramBotNotification extends NotificationBaseInfo {
+2
View File
@@ -3,3 +3,5 @@ declare namespace Express {
platform: 'desktop' | 'mobile';
}
}
declare module 'pstree.remy';
+10 -1
View File
@@ -48,7 +48,16 @@ export default async () => {
} catch (error) {}
try {
await sequelize.query('alter table Crontabs add column sub_id NUMBER');
} catch (error) {}
} catch (error) { }
try {
await sequelize.query('alter table Crontabs add column extra_schedules JSON');
} catch (error) { }
try {
await sequelize.query('alter table Crontabs add column task_before TEXT');
} catch (error) { }
try {
await sequelize.query('alter table Crontabs add column task_after TEXT');
} catch (error) { }
// 2.10-2.11 升级
const cronDbFile = path.join(config.rootPath, 'db/crontab.db');
+2 -2
View File
@@ -1,6 +1,6 @@
import { Container } from 'typedi';
import SystemService from '../services/system';
import ScheduleService from '../services/schedule';
import ScheduleService, { ScheduleTaskType } from '../services/schedule';
import SubscriptionService from '../services/subscription';
import config from '../config';
import { fileExist } from '../config/util';
@@ -22,7 +22,7 @@ export default async () => {
id: NaN,
name: '生成token',
command: tokenCommand,
};
} as ScheduleTaskType;
await scheduleService.cancelIntervalTask(cron);
scheduleService.createIntervalTask(cron, {
days: 28,
+3
View File
@@ -7,10 +7,13 @@ service Cron {
rpc delCron(DeleteCronRequest) returns (DeleteCronResponse);
}
message ISchedule { string schedule = 1; }
message ICron {
string id = 1;
string schedule = 2;
string command = 3;
repeated ISchedule extra_schedules = 4;
}
message AddCronRequest { repeated ICron crons = 1; }
+144 -138
View File
@@ -10,78 +10,150 @@ import {
Metadata,
ServiceError,
UntypedServiceImplementation,
} from '@grpc/grpc-js';
import _m0 from 'protobufjs/minimal';
} from "@grpc/grpc-js";
import _m0 from "protobufjs/minimal";
export const protobufPackage = 'com.ql.cron';
export const protobufPackage = "com.ql.cron";
export interface ISchedule {
schedule: string;
}
export interface ICron {
id: string;
schedule: string;
command: string;
extraSchedules: ISchedule[];
}
export interface AddCronRequest {
crons: ICron[];
}
export interface AddCronResponse {}
export interface AddCronResponse {
}
export interface DeleteCronRequest {
ids: string[];
}
export interface DeleteCronResponse {}
export interface DeleteCronResponse {
}
function createBaseISchedule(): ISchedule {
return { schedule: "" };
}
export const ISchedule = {
encode(message: ISchedule, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
if (message.schedule !== "") {
writer.uint32(10).string(message.schedule);
}
return writer;
},
decode(input: _m0.Reader | Uint8Array, length?: number): ISchedule {
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseISchedule();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
if (tag !== 10) {
break;
}
message.schedule = reader.string();
continue;
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
}
return message;
},
fromJSON(object: any): ISchedule {
return { schedule: isSet(object.schedule) ? String(object.schedule) : "" };
},
toJSON(message: ISchedule): unknown {
const obj: any = {};
message.schedule !== undefined && (obj.schedule = message.schedule);
return obj;
},
create<I extends Exact<DeepPartial<ISchedule>, I>>(base?: I): ISchedule {
return ISchedule.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<ISchedule>, I>>(object: I): ISchedule {
const message = createBaseISchedule();
message.schedule = object.schedule ?? "";
return message;
},
};
function createBaseICron(): ICron {
return { id: '', schedule: '', command: '' };
return { id: "", schedule: "", command: "", extraSchedules: [] };
}
export const ICron = {
encode(message: ICron, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
if (message.id !== '') {
if (message.id !== "") {
writer.uint32(10).string(message.id);
}
if (message.schedule !== '') {
if (message.schedule !== "") {
writer.uint32(18).string(message.schedule);
}
if (message.command !== '') {
if (message.command !== "") {
writer.uint32(26).string(message.command);
}
for (const v of message.extraSchedules) {
ISchedule.encode(v!, writer.uint32(34).fork()).ldelim();
}
return writer;
},
decode(input: _m0.Reader | Uint8Array, length?: number): ICron {
const reader =
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
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) {
if (tag !== 10) {
break;
}
message.id = reader.string();
continue;
case 2:
if (tag != 18) {
if (tag !== 18) {
break;
}
message.schedule = reader.string();
continue;
case 3:
if (tag != 26) {
if (tag !== 26) {
break;
}
message.command = reader.string();
continue;
case 4:
if (tag !== 34) {
break;
}
message.extraSchedules.push(ISchedule.decode(reader, reader.uint32()));
continue;
}
if ((tag & 7) == 4 || tag == 0) {
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
@@ -91,9 +163,12 @@ export const ICron = {
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) : '',
id: isSet(object.id) ? String(object.id) : "",
schedule: isSet(object.schedule) ? String(object.schedule) : "",
command: isSet(object.command) ? String(object.command) : "",
extraSchedules: Array.isArray(object?.extraSchedules)
? object.extraSchedules.map((e: any) => ISchedule.fromJSON(e))
: [],
};
},
@@ -102,6 +177,11 @@ export const ICron = {
message.id !== undefined && (obj.id = message.id);
message.schedule !== undefined && (obj.schedule = message.schedule);
message.command !== undefined && (obj.command = message.command);
if (message.extraSchedules) {
obj.extraSchedules = message.extraSchedules.map((e) => e ? ISchedule.toJSON(e) : undefined);
} else {
obj.extraSchedules = [];
}
return obj;
},
@@ -111,9 +191,10 @@ export const ICron = {
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 ?? '';
message.id = object.id ?? "";
message.schedule = object.schedule ?? "";
message.command = object.command ?? "";
message.extraSchedules = object.extraSchedules?.map((e) => ISchedule.fromPartial(e)) || [];
return message;
},
};
@@ -123,10 +204,7 @@ function createBaseAddCronRequest(): AddCronRequest {
}
export const AddCronRequest = {
encode(
message: AddCronRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
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();
}
@@ -134,22 +212,21 @@ export const AddCronRequest = {
},
decode(input: _m0.Reader | Uint8Array, length?: number): AddCronRequest {
const reader =
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
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) {
if (tag !== 10) {
break;
}
message.crons.push(ICron.decode(reader, reader.uint32()));
continue;
}
if ((tag & 7) == 4 || tag == 0) {
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
@@ -158,32 +235,24 @@ export const AddCronRequest = {
},
fromJSON(object: any): AddCronRequest {
return {
crons: Array.isArray(object?.crons)
? object.crons.map((e: any) => ICron.fromJSON(e))
: [],
};
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));
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 {
create<I extends Exact<DeepPartial<AddCronRequest>, I>>(base?: I): AddCronRequest {
return AddCronRequest.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<AddCronRequest>, I>>(
object: I,
): AddCronRequest {
fromPartial<I extends Exact<DeepPartial<AddCronRequest>, I>>(object: I): AddCronRequest {
const message = createBaseAddCronRequest();
message.crons = object.crons?.map((e) => ICron.fromPartial(e)) || [];
return message;
@@ -195,23 +264,19 @@ function createBaseAddCronResponse(): AddCronResponse {
}
export const AddCronResponse = {
encode(
_: AddCronResponse,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
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);
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) {
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
@@ -228,15 +293,11 @@ export const AddCronResponse = {
return obj;
},
create<I extends Exact<DeepPartial<AddCronResponse>, I>>(
base?: I,
): AddCronResponse {
create<I extends Exact<DeepPartial<AddCronResponse>, I>>(base?: I): AddCronResponse {
return AddCronResponse.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<AddCronResponse>, I>>(
_: I,
): AddCronResponse {
fromPartial<I extends Exact<DeepPartial<AddCronResponse>, I>>(_: I): AddCronResponse {
const message = createBaseAddCronResponse();
return message;
},
@@ -247,10 +308,7 @@ function createBaseDeleteCronRequest(): DeleteCronRequest {
}
export const DeleteCronRequest = {
encode(
message: DeleteCronRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
encode(message: DeleteCronRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
for (const v of message.ids) {
writer.uint32(10).string(v!);
}
@@ -258,22 +316,21 @@ export const DeleteCronRequest = {
},
decode(input: _m0.Reader | Uint8Array, length?: number): DeleteCronRequest {
const reader =
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
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) {
if (tag !== 10) {
break;
}
message.ids.push(reader.string());
continue;
}
if ((tag & 7) == 4 || tag == 0) {
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
@@ -282,11 +339,7 @@ export const DeleteCronRequest = {
},
fromJSON(object: any): DeleteCronRequest {
return {
ids: Array.isArray(object?.ids)
? object.ids.map((e: any) => String(e))
: [],
};
return { ids: Array.isArray(object?.ids) ? object.ids.map((e: any) => String(e)) : [] };
},
toJSON(message: DeleteCronRequest): unknown {
@@ -299,15 +352,11 @@ export const DeleteCronRequest = {
return obj;
},
create<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(
base?: I,
): DeleteCronRequest {
create<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(base?: I): DeleteCronRequest {
return DeleteCronRequest.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(
object: I,
): DeleteCronRequest {
fromPartial<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(object: I): DeleteCronRequest {
const message = createBaseDeleteCronRequest();
message.ids = object.ids?.map((e) => e) || [];
return message;
@@ -319,23 +368,19 @@ function createBaseDeleteCronResponse(): DeleteCronResponse {
}
export const DeleteCronResponse = {
encode(
_: DeleteCronResponse,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
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);
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) {
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
@@ -352,15 +397,11 @@ export const DeleteCronResponse = {
return obj;
},
create<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(
base?: I,
): DeleteCronResponse {
create<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(base?: I): DeleteCronResponse {
return DeleteCronResponse.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(
_: I,
): DeleteCronResponse {
fromPartial<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(_: I): DeleteCronResponse {
const message = createBaseDeleteCronResponse();
return message;
},
@@ -369,25 +410,21 @@ export const DeleteCronResponse = {
export type CronService = typeof CronService;
export const CronService = {
addCron: {
path: '/com.ql.cron.Cron/addCron',
path: "/com.ql.cron.Cron/addCron",
requestStream: false,
responseStream: false,
requestSerialize: (value: AddCronRequest) =>
Buffer.from(AddCronRequest.encode(value).finish()),
requestSerialize: (value: AddCronRequest) => Buffer.from(AddCronRequest.encode(value).finish()),
requestDeserialize: (value: Buffer) => AddCronRequest.decode(value),
responseSerialize: (value: AddCronResponse) =>
Buffer.from(AddCronResponse.encode(value).finish()),
responseSerialize: (value: AddCronResponse) => Buffer.from(AddCronResponse.encode(value).finish()),
responseDeserialize: (value: Buffer) => AddCronResponse.decode(value),
},
delCron: {
path: '/com.ql.cron.Cron/delCron',
path: "/com.ql.cron.Cron/delCron",
requestStream: false,
responseStream: false,
requestSerialize: (value: DeleteCronRequest) =>
Buffer.from(DeleteCronRequest.encode(value).finish()),
requestSerialize: (value: DeleteCronRequest) => Buffer.from(DeleteCronRequest.encode(value).finish()),
requestDeserialize: (value: Buffer) => DeleteCronRequest.decode(value),
responseSerialize: (value: DeleteCronResponse) =>
Buffer.from(DeleteCronResponse.encode(value).finish()),
responseSerialize: (value: DeleteCronResponse) => Buffer.from(DeleteCronResponse.encode(value).finish()),
responseDeserialize: (value: Buffer) => DeleteCronResponse.decode(value),
},
} as const;
@@ -415,67 +452,36 @@ export interface CronClient extends Client {
): ClientUnaryCall;
delCron(
request: DeleteCronRequest,
callback: (
error: ServiceError | null,
response: DeleteCronResponse,
) => void,
callback: (error: ServiceError | null, response: DeleteCronResponse) => void,
): ClientUnaryCall;
delCron(
request: DeleteCronRequest,
metadata: Metadata,
callback: (
error: ServiceError | null,
response: DeleteCronResponse,
) => void,
callback: (error: ServiceError | null, response: DeleteCronResponse) => void,
): ClientUnaryCall;
delCron(
request: DeleteCronRequest,
metadata: Metadata,
options: Partial<CallOptions>,
callback: (
error: ServiceError | null,
response: DeleteCronResponse,
) => void,
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;
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;
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]> }
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;
};
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;
+51 -114
View File
@@ -12,10 +12,10 @@ import {
Metadata,
ServiceError,
UntypedServiceImplementation,
} from '@grpc/grpc-js';
import _m0 from 'protobufjs/minimal';
} from "@grpc/grpc-js";
import _m0 from "protobufjs/minimal";
export const protobufPackage = 'com.ql.health';
export const protobufPackage = "com.ql.health";
export interface HealthCheckRequest {
service: string;
@@ -33,79 +33,71 @@ export enum HealthCheckResponse_ServingStatus {
UNRECOGNIZED = -1,
}
export function healthCheckResponse_ServingStatusFromJSON(
object: any,
): HealthCheckResponse_ServingStatus {
export function healthCheckResponse_ServingStatusFromJSON(object: any): HealthCheckResponse_ServingStatus {
switch (object) {
case 0:
case 'UNKNOWN':
case "UNKNOWN":
return HealthCheckResponse_ServingStatus.UNKNOWN;
case 1:
case 'SERVING':
case "SERVING":
return HealthCheckResponse_ServingStatus.SERVING;
case 2:
case 'NOT_SERVING':
case "NOT_SERVING":
return HealthCheckResponse_ServingStatus.NOT_SERVING;
case 3:
case 'SERVICE_UNKNOWN':
case "SERVICE_UNKNOWN":
return HealthCheckResponse_ServingStatus.SERVICE_UNKNOWN;
case -1:
case 'UNRECOGNIZED':
case "UNRECOGNIZED":
default:
return HealthCheckResponse_ServingStatus.UNRECOGNIZED;
}
}
export function healthCheckResponse_ServingStatusToJSON(
object: HealthCheckResponse_ServingStatus,
): string {
export function healthCheckResponse_ServingStatusToJSON(object: HealthCheckResponse_ServingStatus): string {
switch (object) {
case HealthCheckResponse_ServingStatus.UNKNOWN:
return 'UNKNOWN';
return "UNKNOWN";
case HealthCheckResponse_ServingStatus.SERVING:
return 'SERVING';
return "SERVING";
case HealthCheckResponse_ServingStatus.NOT_SERVING:
return 'NOT_SERVING';
return "NOT_SERVING";
case HealthCheckResponse_ServingStatus.SERVICE_UNKNOWN:
return 'SERVICE_UNKNOWN';
return "SERVICE_UNKNOWN";
case HealthCheckResponse_ServingStatus.UNRECOGNIZED:
default:
return 'UNRECOGNIZED';
return "UNRECOGNIZED";
}
}
function createBaseHealthCheckRequest(): HealthCheckRequest {
return { service: '' };
return { service: "" };
}
export const HealthCheckRequest = {
encode(
message: HealthCheckRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.service !== '') {
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);
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) {
if (tag !== 10) {
break;
}
message.service = reader.string();
continue;
}
if ((tag & 7) == 4 || tag == 0) {
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
@@ -114,7 +106,7 @@ export const HealthCheckRequest = {
},
fromJSON(object: any): HealthCheckRequest {
return { service: isSet(object.service) ? String(object.service) : '' };
return { service: isSet(object.service) ? String(object.service) : "" };
},
toJSON(message: HealthCheckRequest): unknown {
@@ -123,17 +115,13 @@ export const HealthCheckRequest = {
return obj;
},
create<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(
base?: I,
): HealthCheckRequest {
create<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(base?: I): HealthCheckRequest {
return HealthCheckRequest.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(
object: I,
): HealthCheckRequest {
fromPartial<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(object: I): HealthCheckRequest {
const message = createBaseHealthCheckRequest();
message.service = object.service ?? '';
message.service = object.service ?? "";
return message;
},
};
@@ -143,10 +131,7 @@ function createBaseHealthCheckResponse(): HealthCheckResponse {
}
export const HealthCheckResponse = {
encode(
message: HealthCheckResponse,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
encode(message: HealthCheckResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
if (message.status !== 0) {
writer.uint32(8).int32(message.status);
}
@@ -154,22 +139,21 @@ export const HealthCheckResponse = {
},
decode(input: _m0.Reader | Uint8Array, length?: number): HealthCheckResponse {
const reader =
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
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) {
if (tag !== 8) {
break;
}
message.status = reader.int32() as any;
continue;
}
if ((tag & 7) == 4 || tag == 0) {
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
@@ -178,29 +162,20 @@ export const HealthCheckResponse = {
},
fromJSON(object: any): HealthCheckResponse {
return {
status: isSet(object.status)
? healthCheckResponse_ServingStatusFromJSON(object.status)
: 0,
};
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));
message.status !== undefined && (obj.status = healthCheckResponse_ServingStatusToJSON(message.status));
return obj;
},
create<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(
base?: I,
): HealthCheckResponse {
create<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(base?: I): HealthCheckResponse {
return HealthCheckResponse.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(
object: I,
): HealthCheckResponse {
fromPartial<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(object: I): HealthCheckResponse {
const message = createBaseHealthCheckResponse();
message.status = object.status ?? 0;
return message;
@@ -210,25 +185,21 @@ export const HealthCheckResponse = {
export type HealthService = typeof HealthService;
export const HealthService = {
check: {
path: '/com.ql.health.Health/Check',
path: "/com.ql.health.Health/Check",
requestStream: false,
responseStream: false,
requestSerialize: (value: HealthCheckRequest) =>
Buffer.from(HealthCheckRequest.encode(value).finish()),
requestSerialize: (value: HealthCheckRequest) => Buffer.from(HealthCheckRequest.encode(value).finish()),
requestDeserialize: (value: Buffer) => HealthCheckRequest.decode(value),
responseSerialize: (value: HealthCheckResponse) =>
Buffer.from(HealthCheckResponse.encode(value).finish()),
responseSerialize: (value: HealthCheckResponse) => Buffer.from(HealthCheckResponse.encode(value).finish()),
responseDeserialize: (value: Buffer) => HealthCheckResponse.decode(value),
},
watch: {
path: '/com.ql.health.Health/Watch',
path: "/com.ql.health.Health/Watch",
requestStream: false,
responseStream: true,
requestSerialize: (value: HealthCheckRequest) =>
Buffer.from(HealthCheckRequest.encode(value).finish()),
requestSerialize: (value: HealthCheckRequest) => Buffer.from(HealthCheckRequest.encode(value).finish()),
requestDeserialize: (value: Buffer) => HealthCheckRequest.decode(value),
responseSerialize: (value: HealthCheckResponse) =>
Buffer.from(HealthCheckResponse.encode(value).finish()),
responseSerialize: (value: HealthCheckResponse) => Buffer.from(HealthCheckResponse.encode(value).finish()),
responseDeserialize: (value: Buffer) => HealthCheckResponse.decode(value),
},
} as const;
@@ -241,32 +212,20 @@ export interface HealthServer extends UntypedServiceImplementation {
export interface HealthClient extends Client {
check(
request: HealthCheckRequest,
callback: (
error: ServiceError | null,
response: HealthCheckResponse,
) => void,
callback: (error: ServiceError | null, response: HealthCheckResponse) => void,
): ClientUnaryCall;
check(
request: HealthCheckRequest,
metadata: Metadata,
callback: (
error: ServiceError | null,
response: HealthCheckResponse,
) => void,
callback: (error: ServiceError | null, response: HealthCheckResponse) => void,
): ClientUnaryCall;
check(
request: HealthCheckRequest,
metadata: Metadata,
options: Partial<CallOptions>,
callback: (
error: ServiceError | null,
response: HealthCheckResponse,
) => void,
callback: (error: ServiceError | null, response: HealthCheckResponse) => void,
): ClientUnaryCall;
watch(
request: HealthCheckRequest,
options?: Partial<CallOptions>,
): ClientReadableStream<HealthCheckResponse>;
watch(request: HealthCheckRequest, options?: Partial<CallOptions>): ClientReadableStream<HealthCheckResponse>;
watch(
request: HealthCheckRequest,
metadata?: Metadata,
@@ -274,43 +233,21 @@ export interface HealthClient extends Client {
): ClientReadableStream<HealthCheckResponse>;
}
export const HealthClient = makeGenericClientConstructor(
HealthService,
'com.ql.health.Health',
) as unknown as {
new (
address: string,
credentials: ChannelCredentials,
options?: Partial<ClientOptions>,
): HealthClient;
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;
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]> }
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;
};
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;
+24 -14
View File
@@ -5,21 +5,15 @@ import { scheduleStacks } from './data';
import { runCron } from '../shared/runCron';
import { QL_PREFIX, TASK_PREFIX } from '../config/const';
import Logger from '../loaders/logger';
import dayjs from 'dayjs';
const addCron = (
call: ServerUnaryCall<AddCronRequest, AddCronResponse>,
callback: sendUnaryData<AddCronResponse>,
) => {
for (const item of call.request.crons) {
const { id, schedule, command } = item;
const { id, schedule, command, extraSchedules } = item;
if (scheduleStacks.has(id)) {
scheduleStacks.get(id)?.cancel();
}
let cmdStr = command.trim();
if (!cmdStr.startsWith(TASK_PREFIX) && !cmdStr.startsWith(QL_PREFIX)) {
cmdStr = `${TASK_PREFIX}${cmdStr}`;
scheduleStacks.get(id)?.forEach((x) => x.cancel());
}
Logger.info(
@@ -29,15 +23,31 @@ const addCron = (
command,
);
scheduleStacks.set(
id,
nodeSchedule.scheduleJob(id, schedule, async () => {
if (extraSchedules?.length) {
extraSchedules.forEach(x => {
Logger.info(
`[schedule][准备运行任务] 命令: ${cmdStr}`,
'[schedule][创建定时任务], 任务ID: %s, cron: %s, 执行命令: %s',
id,
x.schedule,
command,
);
runCron(`ID=${id} ${cmdStr}`);
})
}
scheduleStacks.set(id, [
nodeSchedule.scheduleJob(id, schedule, async () => {
Logger.info(`[schedule][准备运行任务] 命令: ${command}`);
runCron(command);
}),
);
...(extraSchedules?.length
? extraSchedules.map((x) =>
nodeSchedule.scheduleJob(id, x.schedule, async () => {
Logger.info(`[schedule][准备运行任务] 命令: ${command}`);
runCron(command);
}),
)
: []),
]);
}
callback(null, null);
+1 -1
View File
@@ -1,6 +1,6 @@
import nodeSchedule from 'node-schedule';
import { ToadScheduler } from 'toad-scheduler';
export const scheduleStacks = new Map<string, nodeSchedule.Job>();
export const scheduleStacks = new Map<string, nodeSchedule.Job[]>();
export const intervalSchedule = new ToadScheduler();
+1 -1
View File
@@ -13,7 +13,7 @@ const delCron = (
'[schedule][取消定时任务], 任务ID: %s',
id,
);
scheduleStacks.get(id)?.cancel();
scheduleStacks.get(id)?.forEach(x => x.cancel());
scheduleStacks.delete(id);
}
}
+27 -28
View File
@@ -31,9 +31,9 @@ export default class CronService {
const tab = new Crontab(payload);
tab.saved = false;
const doc = await this.insert(tab);
if (this.isSixCron(doc)) {
if (this.isSixCron(doc) || doc.extra_schedules?.length) {
await cronClient.addCron([
{ id: String(doc.id), schedule: doc.schedule!, command: doc.command },
{ id: String(doc.id), schedule: doc.schedule!, command: this.makeCommand(doc), extraSchedules: doc.extra_schedules || [] },
]);
}
await this.set_crontab();
@@ -52,15 +52,16 @@ export default class CronService {
if (doc.isDisabled === 1) {
return newDoc;
}
if (this.isSixCron(doc)) {
await cronClient.delCron([String(newDoc.id)]);
if (this.isSixCron(doc) || doc.extra_schedules?.length) {
await cronClient.delCron([String(doc.id)]);
}
if (this.isSixCron(newDoc)) {
if (this.isSixCron(newDoc) || newDoc.extra_schedules?.length) {
await cronClient.addCron([
{
id: String(newDoc.id),
schedule: newDoc.schedule!,
command: newDoc.command,
command: this.makeCommand(newDoc),
extraSchedules: newDoc.extra_schedules || []
},
]);
}
@@ -405,21 +406,7 @@ export default class CronService {
this.logger.silly('ID: ' + id);
this.logger.silly('Original command: ' + command);
let cmdStr = command;
if (!cmdStr.startsWith(TASK_PREFIX) && !cmdStr.startsWith(QL_PREFIX)) {
cmdStr = `${TASK_PREFIX}${cmdStr}`;
}
if (
cmdStr.endsWith('.js') ||
cmdStr.endsWith('.py') ||
cmdStr.endsWith('.pyc') ||
cmdStr.endsWith('.sh') ||
cmdStr.endsWith('.ts')
) {
cmdStr = `${cmdStr} now`;
}
const cp = spawn(`real_log_path=${logPath} ID=${id} ${cmdStr}`, { shell: '/bin/bash' });
const cp = spawn(`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron)}`, { shell: '/bin/bash' });
await CrontabModel.update(
{ status: CrontabStatus.running, pid: cp.pid, log_path: logPath },
@@ -462,7 +449,8 @@ export default class CronService {
.map((doc) => ({
id: String(doc.id),
schedule: doc.schedule!,
command: doc.command,
command: this.makeCommand(doc),
extraSchedules: doc.extra_schedules || []
}));
await cronClient.addCron(sixCron);
await this.set_crontab();
@@ -505,12 +493,22 @@ export default class CronService {
}
}
private make_command(tab: Crontab) {
private makeCommand(tab: Crontab) {
let command = tab.command.trim();
if (!command.startsWith(TASK_PREFIX) && !command.startsWith(QL_PREFIX)) {
command = `${TASK_PREFIX}${tab.command}`;
}
const crontab_job_string = `ID=${tab.id} ${command}`;
let commandVariable = `ID=${tab.id} `
if (tab.task_before) {
commandVariable += `task_before='${tab.task_before.replace(/'/g, "'\\''")
.trim()}' `;
}
if (tab.task_after) {
commandVariable += `task_after='${tab.task_after.replace(/'/g, "'\\''")
.trim()}' `;
}
const crontab_job_string = `${commandVariable}${command}`;
return crontab_job_string;
}
@@ -519,16 +517,16 @@ export default class CronService {
var crontab_string = '';
tabs.data.forEach((tab) => {
const _schedule = tab.schedule && tab.schedule.split(/ +/);
if (tab.isDisabled === 1 || _schedule!.length !== 5) {
if (tab.isDisabled === 1 || _schedule!.length !== 5 || tab.extra_schedules?.length) {
crontab_string += '# ';
crontab_string += tab.schedule;
crontab_string += ' ';
crontab_string += this.make_command(tab);
crontab_string += this.makeCommand(tab);
crontab_string += '\n';
} else {
crontab_string += tab.schedule;
crontab_string += ' ';
crontab_string += this.make_command(tab);
crontab_string += this.makeCommand(tab);
crontab_string += '\n';
}
});
@@ -582,7 +580,8 @@ export default class CronService {
.map((doc) => ({
id: String(doc.id),
schedule: doc.schedule!,
command: doc.command,
command: this.makeCommand(doc),
extraSchedules: doc.extra_schedules || []
}));
await cronClient.addCron(sixCron);
}
+2 -2
View File
@@ -195,7 +195,7 @@ export default class NotificationService {
}
private async bark() {
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
let { barkPush, barkIcon, barkSound, barkGroup, barkLevel, barkUrl } = this.params;
if (!barkPush.startsWith('http')) {
barkPush = `https://api.day.app/${barkPush}`;
}
@@ -203,7 +203,7 @@ export default class NotificationService {
this.title,
)}/${encodeURIComponent(
this.content,
)}?icon=${barkIcon}&sound=${barkSound}&group=${barkGroup}`;
)}?icon=${barkIcon}&sound=${barkSound}&group=${barkGroup}&level=${barkLevel}&url=${barkUrl}`;
try {
const res: any = await got
+1 -1
View File
@@ -12,7 +12,7 @@ import dayjs from 'dayjs';
import taskLimit from '../shared/pLimit';
import { spawn } from 'cross-spawn';
interface ScheduleTaskType {
export interface ScheduleTaskType {
id: number;
command: string;
name?: string;
+1 -1
View File
@@ -7,7 +7,7 @@ import { SockMessage } from '../data/sock';
export default class SockService {
private clients: Connection[] = [];
constructor(@Inject('logger') private logger: winston.Logger) {}
constructor(@Inject('logger') private logger: winston.Logger) { }
public getClients() {
return this.clients;
+2 -2
View File
@@ -28,7 +28,7 @@ import taskLimit from '../shared/pLimit';
import tar from 'tar';
import path from 'path';
import fs from 'fs';
import { sum } from 'lodash';
import sum from 'lodash/sum';
@Service()
export default class SystemService {
@@ -84,7 +84,7 @@ export default class SystemService {
});
if (info.logRemoveFrequency) {
const cron = {
id: result.id,
id: result.id || NaN,
name: '删除日志',
command: `ql rmlog ${info.logRemoveFrequency}`,
};
+1 -1
View File
@@ -12,7 +12,7 @@ map $http_upgrade $connection_upgrade {
}
server {
listen 5700;
IPV4_CONFIG
IPV6_CONFIG
ssl_session_timeout 5m;
+1 -2
View File
@@ -1,6 +1,5 @@
{
"watch": ["back", ".env"],
"ext": "js,ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "ts-node --transpile-only ./back/app.ts"
"exec": "ts-node -P tsconfig.back.json ./back/app.ts"
}
+5 -5
View File
@@ -4,8 +4,8 @@
"start": "concurrently -n w: npm: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",
"start:public": "ts-node -P tsconfig.back.json ./back/public.ts",
"start:rpc": "ts-node -P tsconfig.back.json ./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",
@@ -69,11 +69,11 @@
"dotenv": "^16.0.0",
"express": "^4.17.3",
"express-jwt": "^6.1.1",
"express-rate-limit": "^6.7.0",
"express-rate-limit": "^7.0.0",
"express-urlrewrite": "^1.4.0",
"form-data": "^4.0.0",
"got": "^11.8.2",
"hpagent": "^0.1.2",
"hpagent": "^1.2.0",
"http-proxy-middleware": "^2.0.6",
"iconv-lite": "^0.6.3",
"js-yaml": "^4.1.0",
@@ -162,7 +162,7 @@
"ts-proto": "^1.146.0",
"tslib": "^2.4.0",
"tsx": "^3.12.3",
"typescript": "4.8.4",
"typescript": "5.2.2",
"vh-check": "^2.0.5",
"virtualizedtableforantd4": "1.3.0",
"webpack": "^5.70.0",
+68 -67
View File
@@ -44,8 +44,8 @@ dependencies:
specifier: ^6.1.1
version: 6.1.2
express-rate-limit:
specifier: ^6.7.0
version: 6.7.0(express@4.18.2)
specifier: ^7.0.0
version: 7.0.0(express@4.18.2)
express-urlrewrite:
specifier: ^1.4.0
version: 1.4.0
@@ -56,8 +56,8 @@ dependencies:
specifier: ^11.8.2
version: 11.8.6
hpagent:
specifier: ^0.1.2
version: 0.1.2
specifier: ^1.2.0
version: 1.2.0
http-proxy-middleware:
specifier: ^2.0.6
version: 2.0.6(@types/express@4.17.17)
@@ -224,7 +224,7 @@ devDependencies:
version: 4.21.9(@babel/runtime@7.22.3)(@codemirror/autocomplete@6.9.0)(@codemirror/language@6.9.0)(@codemirror/lint@6.4.0)(@codemirror/search@6.5.1)(@codemirror/state@6.2.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.16.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0)
'@umijs/max':
specifier: ^4.0.72
version: 4.0.72(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
version: 4.0.72(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@5.2.2)(webpack@5.85.1)
'@umijs/ssr-darkreader':
specifier: ^4.9.45
version: 4.9.45
@@ -308,7 +308,7 @@ devDependencies:
version: 1.6.1
ts-node:
specifier: ^10.6.0
version: 10.9.1(@types/node@17.0.45)(typescript@4.8.4)
version: 10.9.1(@types/node@17.0.45)(typescript@5.2.2)
ts-proto:
specifier: ^1.146.0
version: 1.148.2
@@ -319,8 +319,8 @@ devDependencies:
specifier: ^3.12.3
version: 3.12.7
typescript:
specifier: 4.8.4
version: 4.8.4
specifier: 5.2.2
version: 5.2.2
vh-check:
specifier: ^2.0.5
version: 2.0.5
@@ -5117,7 +5117,7 @@ packages:
'@types/yargs-parser': 21.0.0
dev: true
/@typescript-eslint/eslint-plugin@5.48.1(@typescript-eslint/parser@5.48.1)(eslint@8.35.0)(typescript@4.8.4):
/@typescript-eslint/eslint-plugin@5.48.1(@typescript-eslint/parser@5.48.1)(eslint@8.35.0)(typescript@5.2.2):
resolution: {integrity: sha512-9nY5K1Rp2ppmpb9s9S2aBiF3xo5uExCehMDmYmmFqqyxgenbHJ3qbarcLt4ITgaD6r/2ypdlcFRdcuVPnks+fQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -5130,23 +5130,23 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/parser': 5.48.1(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/parser': 5.48.1(eslint@8.35.0)(typescript@5.2.2)
'@typescript-eslint/scope-manager': 5.48.1
'@typescript-eslint/type-utils': 5.48.1(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/utils': 5.48.1(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/type-utils': 5.48.1(eslint@8.35.0)(typescript@5.2.2)
'@typescript-eslint/utils': 5.48.1(eslint@8.35.0)(typescript@5.2.2)
debug: 4.3.4
eslint: 8.35.0
ignore: 5.2.4
natural-compare-lite: 1.4.0
regexpp: 3.2.0
semver: 7.5.1
tsutils: 3.21.0(typescript@4.8.4)
typescript: 4.8.4
tsutils: 3.21.0(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@5.48.1(eslint@8.35.0)(typescript@4.8.4):
/@typescript-eslint/parser@5.48.1(eslint@8.35.0)(typescript@5.2.2):
resolution: {integrity: sha512-4yg+FJR/V1M9Xoq56SF9Iygqm+r5LMXvheo6DQ7/yUWynQ4YfCRnsKuRgqH4EQ5Ya76rVwlEpw4Xu+TgWQUcdA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -5160,10 +5160,10 @@ packages:
dependencies:
'@typescript-eslint/scope-manager': 5.48.1
'@typescript-eslint/types': 5.48.1
'@typescript-eslint/typescript-estree': 5.48.1(typescript@4.8.4)
'@typescript-eslint/typescript-estree': 5.48.1(typescript@5.2.2)
debug: 4.3.4
eslint: 8.35.0
typescript: 4.8.4
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -5184,7 +5184,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.59.9
dev: true
/@typescript-eslint/type-utils@5.48.1(eslint@8.35.0)(typescript@4.8.4):
/@typescript-eslint/type-utils@5.48.1(eslint@8.35.0)(typescript@5.2.2):
resolution: {integrity: sha512-Hyr8HU8Alcuva1ppmqSYtM/Gp0q4JOp1F+/JH5D1IZm/bUBrV0edoewQZiEc1r6I8L4JL21broddxK8HAcZiqQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -5196,12 +5196,12 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 5.48.1(typescript@4.8.4)
'@typescript-eslint/utils': 5.48.1(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/typescript-estree': 5.48.1(typescript@5.2.2)
'@typescript-eslint/utils': 5.48.1(eslint@8.35.0)(typescript@5.2.2)
debug: 4.3.4
eslint: 8.35.0
tsutils: 3.21.0(typescript@4.8.4)
typescript: 4.8.4
tsutils: 3.21.0(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -5216,7 +5216,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@typescript-eslint/typescript-estree@5.48.1(typescript@4.8.4):
/@typescript-eslint/typescript-estree@5.48.1(typescript@5.2.2):
resolution: {integrity: sha512-Hut+Osk5FYr+sgFh8J/FHjqX6HFcDzTlWLrFqGoK5kVUN3VBHF/QzZmAsIXCQ8T/W9nQNBTqalxi1P3LSqWnRA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -5231,13 +5231,13 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.5.1
tsutils: 3.21.0(typescript@4.8.4)
typescript: 4.8.4
tsutils: 3.21.0(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@5.59.9(typescript@4.8.4):
/@typescript-eslint/typescript-estree@5.59.9(typescript@5.2.2):
resolution: {integrity: sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -5252,13 +5252,13 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.5.1
tsutils: 3.21.0(typescript@4.8.4)
typescript: 4.8.4
tsutils: 3.21.0(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/utils@5.48.1(eslint@8.35.0)(typescript@4.8.4):
/@typescript-eslint/utils@5.48.1(eslint@8.35.0)(typescript@5.2.2):
resolution: {integrity: sha512-SmQuSrCGUOdmGMwivW14Z0Lj8dxG1mOFZ7soeJ0TQZEJcs3n5Ndgkg0A4bcMFzBELqLJ6GTHnEU+iIoaD6hFGA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -5271,7 +5271,7 @@ packages:
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 5.48.1
'@typescript-eslint/types': 5.48.1
'@typescript-eslint/typescript-estree': 5.48.1(typescript@4.8.4)
'@typescript-eslint/typescript-estree': 5.48.1(typescript@5.2.2)
eslint: 8.35.0
eslint-scope: 5.1.1
eslint-utils: 3.0.0(eslint@8.35.0)
@@ -5281,7 +5281,7 @@ packages:
- typescript
dev: true
/@typescript-eslint/utils@5.59.9(eslint@8.35.0)(typescript@4.8.4):
/@typescript-eslint/utils@5.59.9(eslint@8.35.0)(typescript@5.2.2):
resolution: {integrity: sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -5295,7 +5295,7 @@ packages:
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 5.59.9
'@typescript-eslint/types': 5.59.9
'@typescript-eslint/typescript-estree': 5.59.9(typescript@4.8.4)
'@typescript-eslint/typescript-estree': 5.59.9(typescript@5.2.2)
eslint: 8.35.0
eslint-scope: 5.1.1
semver: 7.5.1
@@ -5486,7 +5486,7 @@ packages:
- terser
dev: true
/@umijs/bundler-webpack@4.0.72(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
/@umijs/bundler-webpack@4.0.72(sockjs-client@1.6.1)(typescript@5.2.2)(webpack@5.85.1):
resolution: {integrity: sha512-0oTvna4AdMoSvRWeF0E8u8/aKASwll226DkLKvjYDs5FE9nhbxIHevsoSRX7TnlEDPNfV/a3kVRnRG1dR2oJnQ==}
hasBin: true
dependencies:
@@ -5503,7 +5503,7 @@ packages:
cors: 2.8.5
css-loader: 6.7.1(webpack@5.85.1)
es5-imcompatible-versions: 0.1.83
fork-ts-checker-webpack-plugin: 8.0.0(typescript@4.8.4)(webpack@5.85.1)
fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.2.2)(webpack@5.85.1)
jest-worker: 29.4.3
lightningcss: 1.19.0
node-libs-browser: 2.2.1
@@ -5648,16 +5648,16 @@ packages:
query-string: 6.14.1
dev: true
/@umijs/lint@4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4):
/@umijs/lint@4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.2.2):
resolution: {integrity: sha512-kH3L81Rex+jj5WeyJjR2G6yI1/0KFpr91ZtXeMy9Iyd4G7mEUJl3Fl/9iUEwZ2sgUa7kEJ+28H43eER5W4A6bg==}
dependencies:
'@babel/core': 7.21.0
'@babel/eslint-parser': 7.19.1(@babel/core@7.21.0)(eslint@8.35.0)
'@stylelint/postcss-css-in-js': 0.38.0(postcss-syntax@0.36.2)(postcss@8.4.24)
'@typescript-eslint/eslint-plugin': 5.48.1(@typescript-eslint/parser@5.48.1)(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/parser': 5.48.1(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/eslint-plugin': 5.48.1(@typescript-eslint/parser@5.48.1)(eslint@8.35.0)(typescript@5.2.2)
'@typescript-eslint/parser': 5.48.1(eslint@8.35.0)(typescript@5.2.2)
'@umijs/babel-preset-umi': 4.0.72
eslint-plugin-jest: 27.2.1(@typescript-eslint/eslint-plugin@5.48.1)(eslint@8.35.0)(typescript@4.8.4)
eslint-plugin-jest: 27.2.1(@typescript-eslint/eslint-plugin@5.48.1)(eslint@8.35.0)(typescript@5.2.2)
eslint-plugin-react: 7.32.2(eslint@8.35.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.35.0)
postcss: 8.4.24
@@ -5677,16 +5677,16 @@ packages:
- typescript
dev: true
/@umijs/max@4.0.72(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
/@umijs/max@4.0.72(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@5.2.2)(webpack@5.85.1):
resolution: {integrity: sha512-5e5BwaSBCdGWlj0PZ2I73+WLLe/8AibgDt2OtXc86YujRkhSKGY740pYgqy2PAszV8MjDMg8Sqs+gZG13C5z8w==}
hasBin: true
dependencies:
'@umijs/lint': 4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4)
'@umijs/lint': 4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.2.2)
'@umijs/plugins': 4.0.72(@types/react-dom@18.2.4)(@types/react@18.2.8)(antd@4.24.10)(react-dom@18.2.0)(react@18.2.0)
antd: 4.24.10(react-dom@18.2.0)(react@18.2.0)
eslint: 8.35.0
stylelint: 14.8.2
umi: 4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@4.8.4)(webpack@5.85.1)
umi: 4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@5.2.2)(webpack@5.85.1)
transitivePeerDependencies:
- '@babel/core'
- '@reduxjs/toolkit'
@@ -5794,7 +5794,7 @@ packages:
- supports-color
dev: true
/@umijs/preset-umi@4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
/@umijs/preset-umi@4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@5.2.2)(webpack@5.85.1):
resolution: {integrity: sha512-DDRzPCyP2K667YrOHrexAPcEacTdA9/TBn73erwreZpi+c4qhFZ2eYQo27SAGAhfZlYDYthL8JAfbgfhYmLo1A==}
dependencies:
'@iconify/utils': 2.1.1
@@ -5804,7 +5804,7 @@ packages:
'@umijs/bundler-esbuild': 4.0.72
'@umijs/bundler-utils': 4.0.72
'@umijs/bundler-vite': 4.0.72(@types/node@17.0.45)
'@umijs/bundler-webpack': 4.0.72(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/bundler-webpack': 4.0.72(sockjs-client@1.6.1)(typescript@5.2.2)(webpack@5.85.1)
'@umijs/core': 4.0.72
'@umijs/did-you-know': 1.0.3
'@umijs/es-module-parser': 0.0.7
@@ -8437,7 +8437,7 @@ packages:
source-map: 0.6.1
dev: true
/eslint-plugin-jest@27.2.1(@typescript-eslint/eslint-plugin@5.48.1)(eslint@8.35.0)(typescript@4.8.4):
/eslint-plugin-jest@27.2.1(@typescript-eslint/eslint-plugin@5.48.1)(eslint@8.35.0)(typescript@5.2.2):
resolution: {integrity: sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
@@ -8452,8 +8452,8 @@ packages:
jest:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 5.48.1(@typescript-eslint/parser@5.48.1)(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/utils': 5.59.9(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/eslint-plugin': 5.48.1(@typescript-eslint/parser@5.48.1)(eslint@8.35.0)(typescript@5.2.2)
'@typescript-eslint/utils': 5.59.9(eslint@8.35.0)(typescript@5.2.2)
eslint: 8.35.0
transitivePeerDependencies:
- supports-color
@@ -8724,9 +8724,9 @@ packages:
lodash: 4.17.21
dev: false
/express-rate-limit@6.7.0(express@4.18.2):
resolution: {integrity: sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==}
engines: {node: '>= 12.9.0'}
/express-rate-limit@7.0.0(express@4.18.2):
resolution: {integrity: sha512-zKMQ9meikj7j3ILeVvHIaBejAYljgDBtGuCfbzNS2d0VCW4s68ONdtEhBJnOGW/Ty1wGeNXgC4m/C1bBUIX0LA==}
engines: {node: '>= 16'}
peerDependencies:
express: ^4 || ^5
dependencies:
@@ -8986,7 +8986,7 @@ packages:
is-callable: 1.2.7
dev: true
/fork-ts-checker-webpack-plugin@8.0.0(typescript@4.8.4)(webpack@5.85.1):
/fork-ts-checker-webpack-plugin@8.0.0(typescript@5.2.2)(webpack@5.85.1):
resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==}
engines: {node: '>=12.13.0', yarn: '>=1.0.0'}
peerDependencies:
@@ -9008,7 +9008,7 @@ packages:
schema-utils: 3.1.2
semver: 7.5.1
tapable: 2.2.1
typescript: 4.8.4
typescript: 5.2.2
webpack: 5.85.1
dev: true
@@ -9468,8 +9468,9 @@ packages:
wbuf: 1.7.3
dev: true
/hpagent@0.1.2:
resolution: {integrity: sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==}
/hpagent@1.2.0:
resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==}
engines: {node: '>=14'}
dev: false
/htm@3.1.1:
@@ -12412,7 +12413,7 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/prettier-plugin-organize-imports@3.2.2(prettier@2.8.8)(typescript@4.8.4):
/prettier-plugin-organize-imports@3.2.2(prettier@2.8.8)(typescript@5.2.2):
resolution: {integrity: sha512-e97lE6odGSiHonHJMTYC0q0iLXQyw0u5z/PJpvP/3vRy6/Zi9kLBwFAbEGjDzIowpjQv8b+J04PDamoUSQbzGA==}
peerDependencies:
'@volar/vue-language-plugin-pug': ^1.0.4
@@ -12426,7 +12427,7 @@ packages:
optional: true
dependencies:
prettier: 2.8.8
typescript: 4.8.4
typescript: 5.2.2
dev: true
/prettier-plugin-packagejson@2.4.3(prettier@2.8.8):
@@ -15338,7 +15339,7 @@ packages:
resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
dev: false
/ts-node@10.9.1(@types/node@17.0.45)(typescript@4.8.4):
/ts-node@10.9.1(@types/node@17.0.45)(typescript@5.2.2):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
@@ -15364,7 +15365,7 @@ packages:
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 4.8.4
typescript: 5.2.2
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
@@ -15406,14 +15407,14 @@ packages:
resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==}
dev: true
/tsutils@3.21.0(typescript@4.8.4):
/tsutils@3.21.0(typescript@5.2.2):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies:
tslib: 1.14.1
typescript: 4.8.4
typescript: 5.2.2
dev: true
/tsx@3.12.7:
@@ -15523,28 +15524,28 @@ packages:
resolution: {integrity: sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==}
dev: false
/typescript@4.8.4:
resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==}
engines: {node: '>=4.2.0'}
/typescript@5.2.2:
resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
/umi@4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@4.8.4)(webpack@5.85.1):
/umi@4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@5.2.2)(webpack@5.85.1):
resolution: {integrity: sha512-VXXwhHtZAApRR02c2F+uDv84m/Bf5g56pMKrArtIUFsrWM8hqS3f7whzgpdjzh0H8EFjpXwan0kJrkXFr6dAPg==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@babel/runtime': 7.21.0
'@umijs/bundler-utils': 4.0.72
'@umijs/bundler-webpack': 4.0.72(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/bundler-webpack': 4.0.72(sockjs-client@1.6.1)(typescript@5.2.2)(webpack@5.85.1)
'@umijs/core': 4.0.72
'@umijs/lint': 4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4)
'@umijs/preset-umi': 4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/lint': 4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.2.2)
'@umijs/preset-umi': 4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@5.2.2)(webpack@5.85.1)
'@umijs/renderer-react': 4.0.72(react-dom@18.2.0)(react@18.2.0)
'@umijs/server': 4.0.72
'@umijs/test': 4.0.72
'@umijs/utils': 4.0.72
prettier-plugin-organize-imports: 3.2.2(prettier@2.8.8)(typescript@4.8.4)
prettier-plugin-organize-imports: 3.2.2(prettier@2.8.8)(typescript@5.2.2)
prettier-plugin-packagejson: 2.4.3(prettier@2.8.8)
transitivePeerDependencies:
- '@babel/core'
+11 -1
View File
@@ -55,6 +55,10 @@ let BARK_ICON = 'https://qn.whyour.cn/logo.png';
let BARK_SOUND = '';
//BARK app推送消息的分组, 默认为"QingLong"
let BARK_GROUP = 'QingLong';
//BARK app推送消息的时效性, 默认为"active"
let BARK_LEVEL = 'active';
//BARK app推送消息的跳转URL
let BARK_URL = '';
// =======================================telegram机器人通知设置区域===========================================
//此处填你telegram bot 的Tokentelegram机器人通知推送必填项.例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw
@@ -211,6 +215,12 @@ if (process.env.BARK_PUSH) {
if (process.env.BARK_GROUP) {
BARK_GROUP = process.env.BARK_GROUP;
}
if (process.env.BARK_LEVEL) {
BARK_LEVEL = process.env.BARK_LEVEL;
}
if (process.env.BARK_URL) {
BARK_URL = process.env.BARK_URL;
}
} else {
if (
BARK_PUSH &&
@@ -552,7 +562,7 @@ function BarkNotify(text, desp, params = {}) {
const options = {
url: `${BARK_PUSH}/${encodeURIComponent(text)}/${encodeURIComponent(
desp,
)}?icon=${BARK_ICON}&sound=${BARK_SOUND}&group=${BARK_GROUP}&${querystring.stringify(
)}?icon=${BARK_ICON}&sound=${BARK_SOUND}&group=${BARK_GROUP}&level=${BARK_LEVEL}&url=${BARK_URL}&${querystring.stringify(
params,
)}`,
headers: {
+4
View File
@@ -40,6 +40,8 @@ push_config = {
'BARK_GROUP': '', # bark 推送分组
'BARK_SOUND': '', # bark 推送声音
'BARK_ICON': '', # bark 推送图标
'BARK_LEVEL': '', # bark 推送时效性
'BARK_URL': '', # bark 推送跳转URL
'CONSOLE': True, # 控制台输出
@@ -130,6 +132,8 @@ def bark(title: str, content: str) -> None:
"BARK_GROUP": "group",
"BARK_SOUND": "sound",
"BARK_ICON": "icon",
"BARK_LEVEL": "level",
"BARK_URL": "url",
}
params = ""
for pair in filter(
+1 -1
View File
@@ -94,7 +94,7 @@ env_str_to_array() {
## 正常运行单个脚本,$1:传入参数
run_normal() {
local file_param=$1
if [[ $# -eq 1 ]] && [[ "$real_time" != "true" ]]; then
if [[ $# -eq 1 ]] && [[ "$real_time" != "true" ]] && [[ "$no_delay" != "true" ]]; then
random_delay "$file_param"
fi
+19 -3
View File
@@ -69,6 +69,7 @@ import_config() {
[[ -f $file_env ]] && . $file_env
ql_base_url=${QlBaseUrl:-"/"}
ql_port=${QlPort:-"5700"}
command_timeout_time=${CommandTimeoutTime:-""}
proxy_url=${ProxyUrl:-""}
file_extensions=${RepoFileExtensions:-"js py"}
@@ -416,12 +417,15 @@ init_nginx() {
sed -i "s,QL_BASE_URL_LOCATION,${location_url},g" /etc/nginx/conf.d/front.conf
sed -i "s,QL_BASE_URL,${ql_base_url},g" /etc/nginx/conf.d/front.conf
ipv6=$(ip a | grep inet6)
ipv6Str=""
local ipv6=$(ip a | grep inet6)
local ipv6Str=""
if [[ $ipv6 ]]; then
ipv6Str="listen [::]:5700 ipv6only=on;"
ipv6Str="listen [::]:${ql_port} ipv6only=on;"
fi
local ipv4Str="listen ${ql_port};"
sed -i "s,IPV6_CONFIG,${ipv6Str},g" /etc/nginx/conf.d/front.conf
sed -i "s,IPV4_CONFIG,${ipv4Str},g" /etc/nginx/conf.d/front.conf
}
handle_task_before() {
@@ -432,11 +436,23 @@ handle_task_before() {
[[ $is_macos -eq 0 ]] && check_server
. $file_task_before "$@"
if [[ $task_before ]]; then
echo -e "执行前置命令\n"
eval "$task_before"
echo -e "\n执行前置命令结束\n"
fi
}
handle_task_after() {
. $file_task_after "$@"
if [[ $task_after ]]; then
echo -e "\n执行后置命令\n"
eval "$task_after"
echo -e "\n执行后置命令结束"
fi
local etime=$(date "+$time_format")
local end_time=$(format_time "$time_format" "$etime")
local end_timestamp=$(format_timestamp "$time_format" "$etime")
+7 -6
View File
@@ -273,13 +273,13 @@ update_qinglong() {
exit_status=$?
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新青龙源文件成功...\n"
echo -e "更新青龙源文件成功...\n"
unzip -oq ${dir_tmp}/ql.zip -d ${dir_tmp}
update_qinglong_static
else
echo -e "\n更新青龙源文件失败,请检查网络...\n"
echo -e "更新青龙源文件失败,请检查网络...\n"
fi
}
@@ -288,28 +288,29 @@ update_qinglong_static() {
exit_status=$?
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新青龙静态资源成功...\n"
echo -e "更新青龙静态资源成功...\n"
unzip -oq ${dir_tmp}/static.zip -d ${dir_tmp}
check_update_dep
else
echo -e "\n更新青龙静态资源失败,请检查网络...\n"
echo -e "更新青龙静态资源失败,请检查网络...\n"
fi
}
check_update_dep() {
echo -e "\n开始检测依赖...\n"
if [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
cp -f $dir_sample/package.json $dir_scripts/package.json
npm_install_2 $dir_scripts
fi
if [[ $(diff $dir_root/package.json ${dir_tmp}/qinglong-${primary_branch}/package.json) ]]; then
npm_install_2 "${dir_tmp}/qinglong-${primary_branch}"
fi
if [[ $exit_status -eq 0 ]]; then
echo -e "\n依赖检测安装成功...\n"
echo -e "\n更新包下载成功...\n"
echo -e "更新包下载成功..."
if [[ "$needRestart" == 'true' ]]; then
cp -rf ${dir_tmp}/qinglong-${primary_branch}/* ${dir_root}/
+6
View File
@@ -451,3 +451,9 @@ body[data-mode='phone'] header {
.ant-table.ant-table-middle tfoot > tr > td {
padding: 12px 16px;
}
body[data-dark='true'] {
.ant-popover-arrow-content {
--antd-arrow-background-color: rgb(24, 26, 27);
}
}
+4 -27
View File
@@ -1,5 +1,5 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState, useRef } from 'react';
import React, { useEffect, useState } from 'react';
import ProLayout, { PageLoading } from '@ant-design/pro-layout';
import * as DarkReader from '@umijs/ssr-darkreader';
import defaultProps from './defaultProps';
@@ -29,9 +29,9 @@ import {
MenuProps,
} from 'antd';
// @ts-ignore
import SockJS from 'sockjs-client';
import * as Sentry from '@sentry/react';
import { init } from '../utils/init';
import WebSocketManager from '../utils/websocket';
export interface SharedContext {
headerStyle: React.CSSProperties;
@@ -40,7 +40,6 @@ export interface SharedContext {
user: any;
reloadUser: (needLoading?: boolean) => void;
reloadTheme: () => void;
socketMessage: any;
systemInfo: TSystemInfo;
}
@@ -60,8 +59,6 @@ export default function () {
const [user, setUser] = useState<any>({});
const [loading, setLoading] = useState<boolean>(true);
const [systemInfo, setSystemInfo] = useState<TSystemInfo>();
const ws = useRef<any>(null);
const [socketMessage, setSocketMessage] = useState<any>();
const [collapsed, setCollapsed] = useState(false);
const [initLoading, setInitLoading] = useState<boolean>(true);
const {
@@ -180,32 +177,14 @@ export default function () {
useEffect(() => {
if (!user || !user.username) return;
ws.current = new SockJS(
const ws = WebSocketManager.getInstance(
`${window.location.origin}/api/ws?token=${localStorage.getItem(
config.authKey,
)}`,
);
ws.current.onmessage = (e: any) => {
try {
const data = JSON.parse(e.data);
if (data.type === 'ping') {
if (data && data.message === 'hanhh') {
console.log('WS connection succeeded !!!');
} else {
console.log('WS connection Failed !!!', e);
}
}
setSocketMessage(data);
} catch (error) {
console.log('websocket连接失败', e);
}
};
const wsCurrent = ws.current;
return () => {
wsCurrent.close();
ws.close();
};
}, [user]);
@@ -246,7 +225,6 @@ export default function () {
user,
reloadUser,
reloadTheme,
ws: ws.current,
}}
/>
);
@@ -387,7 +365,6 @@ export default function () {
user,
reloadUser,
reloadTheme,
socketMessage,
systemInfo,
}}
/>
+10 -1
View File
@@ -352,6 +352,8 @@
"BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)": "BARK push icon, custom push icon (requires iOS 15 or above to display)",
"BARK推送铃声,铃声列表去APP查看复制填写": "BARK push ringtone, check and copy from the APP's ringtone list",
"BARK推送消息的分组,默认为qinglong": "BARK push message grouping, default is qinglong",
"BARK推送消息的时效性,默认为active": "BARK push message redirecting URL",
"BARK推送消息的跳转URL": "BARK push message grouping, default is qinglong",
"telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw": "Telegram Bot token, e.g., 1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw",
"telegram用户的id,例如:129xxx206": "Telegram user ID, e.g., 129xxx206",
"代理IP": "Proxy IP",
@@ -451,5 +453,12 @@
"登录已过期,请重新登录": "Login session has expired, please log in again",
"系统日志": "System Logs",
"主题": "Theme",
"语言": "Language"
"语言": "Language",
"中...": "ing...",
"请选择操作符": "Please select operator",
"新增定时规则": "Add Timing Rules",
"运行任务前执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js": "Run commands before executing the task, e.g., cp/mv/python3 xxx.py/node xxx.js",
"运行任务后执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js": "Run commands after executing the task, e.g., cp/mv/python3 xxx.py/node xxx.js",
"请输入运行任务前要执行的命令": "Please enter the command to run before executing the task",
"请输入运行任务后要执行的命令": "Please enter the command to run after executing the task"
}
+10 -1
View File
@@ -352,6 +352,8 @@
"BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)": "BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)",
"BARK推送铃声,铃声列表去APP查看复制填写": "BARK推送铃声,铃声列表去APP查看复制填写",
"BARK推送消息的分组,默认为qinglong": "BARK推送消息的分组,默认为qinglong",
"BARK推送消息的时效性,默认为active": "BARK推送消息的时效性,默认为active",
"BARK推送消息的跳转URL": "BARK推送消息的跳转URL",
"telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw": "telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw",
"telegram用户的id,例如:129xxx206": "telegram用户的id,例如:129xxx206",
"代理IP": "代理IP",
@@ -451,5 +453,12 @@
"登录已过期,请重新登录": "登录已过期,请重新登录",
"系统日志": "系统日志",
"主题": "主题",
"语言": "语言"
"语言": "语言",
"中...": "中...",
"请选择操作符": "请选择操作符",
"新增定时规则": "新增定时规则",
"运行任务前执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js": "运行任务前执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js",
"运行任务后执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js": "运行任务后执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js",
"请输入运行任务前要执行的命令": "请输入运行任务前要执行的命令",
"请输入运行任务后要执行的命令": "请输入运行任务后要执行的命令"
}
+2 -2
View File
@@ -94,7 +94,7 @@
.log-item {
cursor: pointer;
&:hover {
background: #fafafa;
background: #f2f2f2;
}
}
@@ -195,5 +195,5 @@ body[data-mode='desktop'] {
}
.cron.pinned-cron > td {
background: #fafafa;
background: #f2f2f2;
}
+14 -1
View File
@@ -43,7 +43,6 @@ import { request } from '@/utils/http';
import CronModal, { CronLabelModal } from './modal';
import CronLogModal from './logModal';
import CronDetailModal from './detail';
import cron_parser from 'cron-parser';
import { diffTime } from '@/utils/date';
import { history, useOutletContext } from '@umijs/max';
import './index.less';
@@ -183,6 +182,20 @@ const Crontab = () => {
sorter: {
compare: (a, b) => a.schedule.localeCompare(b.schedule),
},
render: (text, record) => {
return record.extra_schedules?.length ? (
<Popover
placement="right"
content={record.extra_schedules?.map((x) => (
<div>{x.schedule}</div>
))}
>
{text}
</Popover>
) : (
text
);
},
},
{
title: intl.get('最后运行时长'),
+4 -3
View File
@@ -82,12 +82,12 @@ const CronLogModal = ({
handleCancel();
};
const handleScroll = (e) => {
const sTop = e.target.scrollTop;
const handleScroll: React.UIEventHandler<HTMLDivElement> = (e) => {
const sTop = (e.target as HTMLDivElement).scrollTop;
if (scrollInfoRef.current.down) {
scrollInfoRef.current = {
value: sTop,
down: sTop > scrollInfoRef.current.value,
down: sTop > scrollInfoRef.current.value || !sTop,
};
}
};
@@ -105,6 +105,7 @@ const CronLogModal = ({
useEffect(() => {
if (cron && cron.id && visible) {
getCronLog(true);
scrollInfoRef.current.down = true;
}
}, [cron, visible]);
+60 -1
View File
@@ -1,10 +1,11 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Button } from 'antd';
import { Modal, message, Input, Form, Button, Space } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import cronParse from 'cron-parser';
import EditableTagGroup from '@/components/tag';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
const CronModal = ({
cron,
@@ -107,9 +108,67 @@ const CronModal = ({
>
<Input placeholder={intl.get('秒(可选) 分 时 天 月 周')} />
</Form.Item>
<Form.List name="extra_schedules">
{(fields, { add, remove }, { errors }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Form.Item key={key} noStyle>
<Space className="view-create-modal-sorts" align="baseline">
<Form.Item
{...restField}
name={[name, 'schedule']}
rules={[{ required: true }]}
>
<Input
placeholder={intl.get('秒(可选) 分 时 天 月 周')}
/>
</Form.Item>
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(name)}
/>
</Space>
</Form.Item>
))}
<Form.Item>
<a onClick={() => add({ schedule: '' })}>
<PlusOutlined />
{intl.get('新增定时规则')}
</a>
</Form.Item>
<Form.ErrorList errors={errors} />
</>
)}
</Form.List>
<Form.Item name="labels" label={intl.get('标签')}>
<EditableTagGroup />
</Form.Item>
<Form.Item
name="task_before"
label={intl.get('执行前')}
tooltip={intl.get(
'运行任务前执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js',
)}
>
<Input.TextArea
rows={4}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入运行任务前要执行的命令')}
/>
</Form.Item>
<Form.Item
name="task_after"
label={intl.get('执行后')}
tooltip={intl.get(
'运行任务后执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js',
)}
>
<Input.TextArea
rows={4}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入运行任务后要执行的命令')}
/>
</Form.Item>
</Form>
</Modal>
);
+2
View File
@@ -35,4 +35,6 @@ export interface ICrontab {
last_running_time?: number;
last_execution_time?: number;
nextRunTime: Date;
sub_id: number;
extra_schedules?: Array<{ schedule: string; }>;
}
+27 -14
View File
@@ -107,20 +107,29 @@ const ViewCreateModal = ({
}
form.setFieldsValue(
view || {
filters: [{ property: 'command', operation: 'Reg' }],
filters: [{ property: 'command' }],
},
);
}, [view, visible]);
const operationElement = (
<Select style={{ width: 120 }}>
{OPERATIONS.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
const OperationElement = ({ name, ...others }: { name: number }) => {
const property = form.getFieldValue(['filters', name, 'property']);
return (
<Select
style={{ width: 120 }}
placeholder={intl.get('请选择操作符')}
{...others}
>
{OPERATIONS.filter((x) =>
STATUS_MAP[property as 'status' | 'sub_id'] ? x.type === 'select' : x,
).map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
};
const propertyElement = (props: any, style: React.CSSProperties = {}) => {
return (
@@ -190,7 +199,7 @@ const ViewCreateModal = ({
<Input placeholder={intl.get('请输入视图名称')} />
</Form.Item>
<Form.List name="filters">
{(fields, { add, remove }) => (
{(fields, { add, remove }, { errors }) => (
<div
style={{ position: 'relative' }}
className={`view-filters-container ${
@@ -256,9 +265,11 @@ const ViewCreateModal = ({
<Form.Item
{...restField}
name={[name, 'operation']}
rules={[{ required: true }]}
rules={[
{ required: true, message: intl.get('请选择操作符') },
]}
>
{operationElement}
<OperationElement name={name} />
</Form.Item>
<Form.Item
{...restField}
@@ -290,12 +301,13 @@ const ViewCreateModal = ({
{intl.get('新增筛选条件')}
</a>
</Form.Item>
<Form.ErrorList errors={errors} />
</div>
</div>
)}
</Form.List>
<Form.List name="sorts">
{(fields, { add, remove }) => (
{(fields, { add, remove }, { errors }) => (
<div
style={{ position: 'relative' }}
className={`view-filters-container ${
@@ -365,6 +377,7 @@ const ViewCreateModal = ({
{intl.get('新增排序方式')}
</a>
</Form.Item>
<Form.ErrorList errors={errors} />
</div>
</div>
)}
+69 -68
View File
@@ -35,6 +35,7 @@ import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import dayjs from 'dayjs';
import WebSocketManager from '@/utils/websocket';
const { Text } = Typography;
const { Search } = Input;
@@ -87,8 +88,7 @@ const StatusMap: Record<number, { icon: React.ReactNode; color: string }> = {
};
const Dependence = () => {
const { headerStyle, isPhone, socketMessage } =
useOutletContext<SharedContext>();
const { headerStyle, isPhone } = useOutletContext<SharedContext>();
const columns: any = [
{
title: intl.get('序号'),
@@ -100,7 +100,7 @@ const Dependence = () => {
{
title: intl.get('名称'),
dataIndex: 'name',
width: 120,
width: 180,
key: 'name',
},
{
@@ -109,11 +109,12 @@ const Dependence = () => {
width: 120,
dataIndex: 'status',
render: (text: string, record: any, index: number) => {
console.log(record.status);
return (
<Space size="middle" style={{ cursor: 'text' }}>
<Tag
color={StatusMap[record.status].color}
icon={StatusMap[record.status].icon}
color={StatusMap[record.status]?.color}
icon={StatusMap[record.status]?.icon}
style={{ marginRight: 0 }}
>
{intl.get(Status[record.status])}
@@ -125,7 +126,7 @@ const Dependence = () => {
{
title: intl.get('备注'),
dataIndex: 'remark',
width: 120,
width: 100,
key: 'remark',
},
{
@@ -149,7 +150,7 @@ const Dependence = () => {
{
title: intl.get('操作'),
key: 'action',
width: 150,
width: 140,
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
return (
@@ -395,63 +396,62 @@ const Dependence = () => {
}
}, [logDependence]);
useEffect(() => {
if (!socketMessage) return;
const { type, message, references } = socketMessage;
if (
type === 'installDependence' &&
message.includes('开始时间') &&
references.length > 0
) {
const result = [...value];
for (let i = 0; i < references.length; i++) {
const index = value.findIndex((x) => x.id === references[i]);
if (index !== -1) {
result.splice(index, 1, {
...value[index],
status: message.includes('安装') ? Status.安装中 : Status.删除中,
});
}
}
setValue(result);
const handleMessage = useCallback((payload: any) => {
const { message, references } = payload;
let status: number | undefined = undefined;
if (message.includes('开始时间') && references.length > 0) {
status = message.includes('安装') ? Status.安装中 : Status.删除中;
}
if (
type === 'installDependence' &&
message.includes('结束时间') &&
references.length > 0
) {
let status;
if (message.includes('结束时间') && references.length > 0) {
if (message.includes('安装')) {
status = message.includes('成功') ? Status.已安装 : Status.安装失败;
} else {
status = message.includes('成功') ? Status.已删除 : Status.删除失败;
}
const result = [...value];
for (let i = 0; i < references.length; i++) {
const index = value.findIndex((x) => x.id === references[i]);
if (index !== -1) {
result.splice(index, 1, {
...value[index],
status,
});
}
}
setValue(result);
if (status === Status.) {
setTimeout(() => {
const _result = [...value];
for (let i = 0; i < references.length; i++) {
const index = value.findIndex((x) => x.id === references[i]);
if (index !== -1) {
_result.splice(index, 1);
setValue((p) => {
const _result = [...p];
for (let i = 0; i < references.length; i++) {
const index = p.findIndex((x) => x.id === references[i]);
if (index !== -1) {
_result.splice(index, 1);
}
}
}
setValue(_result);
return _result;
});
}, 5000);
return;
}
}
}, [socketMessage]);
if (typeof status === 'number') {
setValue((p) => {
const result = [...p];
for (let i = 0; i < references.length; i++) {
const index = p.findIndex((x) => x.id === references[i]);
if (index !== -1) {
result.splice(index, 1, {
...p[index],
status,
});
}
}
return result;
});
}
}, []);
useEffect(() => {
const ws = WebSocketManager.getInstance();
ws.subscribe('installDependence', handleMessage);
ws.subscribe('uninstallDependence', handleMessage);
return () => {
ws.unsubscribe('installDependence', handleMessage);
ws.unsubscribe('uninstallDependence', handleMessage);
};
}, []);
const onTabChange = (activeKey: string) => {
setSelectedRowIds([]);
@@ -548,24 +548,25 @@ const Dependence = () => {
dependence={editedDependence}
defaultType={type}
/>
<DependenceLogModal
visible={isLogModalVisible}
handleCancel={(needRemove?: boolean) => {
setIsLogModalVisible(false);
if (needRemove) {
const index = value.findIndex((x) => x.id === logDependence.id);
const result = [...value];
if (index !== -1) {
result.splice(index, 1);
setValue(result);
{logDependence && (
<DependenceLogModal
visible={isLogModalVisible}
handleCancel={(needRemove?: boolean) => {
setIsLogModalVisible(false);
if (needRemove) {
const index = value.findIndex((x) => x.id === logDependence.id);
const result = [...value];
if (index !== -1) {
result.splice(index, 1);
setValue(result);
}
} else if ([...value].map((x) => x.id).includes(logDependence.id)) {
getDependenceDetail(logDependence);
}
} else if ([...value].map((x) => x.id).includes(logDependence.id)) {
getDependenceDetail(logDependence);
}
}}
socketMessage={socketMessage}
dependence={logDependence}
/>
}}
dependence={logDependence}
/>
)}
</PageContainer>
);
};
+16 -13
View File
@@ -9,17 +9,16 @@ import {
} from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-layout';
import Ansi from 'ansi-to-react';
import WebSocketManager from '@/utils/websocket';
const DependenceLogModal = ({
dependence,
handleCancel,
visible,
socketMessage,
}: {
dependence?: any;
visible: boolean;
handleCancel: (needRemove?: boolean) => void;
socketMessage: any;
}) => {
const [value, setValue] = useState<string>('');
const [executing, setExecuting] = useState<any>(true);
@@ -54,7 +53,7 @@ const DependenceLogModal = ({
code === 200 &&
localStorage.getItem('logDependence') === String(dependence.id)
) {
const log = (data.log || []).join('') as string;
const log = (data?.log || []).join('') as string;
setValue(log);
setExecuting(!log.includes('结束时间'));
setIsRemoveFailed(log.includes('删除失败'));
@@ -95,21 +94,25 @@ const DependenceLogModal = ({
}
}, [dependence]);
useEffect(() => {
if (!socketMessage || !dependence) return;
const { type, message, references } = socketMessage;
if (
type === 'installDependence' &&
references.length > 0 &&
references.includes(dependence.id)
) {
const handleMessage = (payload: any) => {
const { message, references } = payload;
if (references.length > 0 && references.includes(dependence.id)) {
if (message.includes('结束时间')) {
setExecuting(false);
setIsRemoveFailed(message.includes('删除失败'));
}
setValue(`${value}${message}`);
setValue((p) => `${p}${message}`);
}
}, [socketMessage]);
};
useEffect(() => {
const ws = WebSocketManager.getInstance();
ws.subscribe('installDependence', handleMessage);
return () => {
ws.unsubscribe('installDependence', handleMessage);
};
}, []);
useEffect(() => {
setIsPhone(document.body.clientWidth < 768);
+10
View File
@@ -82,6 +82,16 @@ const Env = () => {
dataIndex: 'name',
key: 'name',
sorter: (a: any, b: any) => a.name.localeCompare(b.name),
render: (text: string, record: any) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Tooltip title={text} placement="topLeft">
<div className="text-ellipsis">{text}</div>
</Tooltip>
<Copy text={text} />
</div>
);
},
},
{
title: intl.get('值'),
+22 -21
View File
@@ -1,5 +1,11 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState, useRef } from 'react';
import React, {
useEffect,
useState,
useRef,
useCallback,
useReducer,
} from 'react';
import { Drawer, Button, Tabs, Badge, Select, TreeSelect } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
@@ -9,6 +15,7 @@ import SaveModal from './saveModal';
import SettingModal from './setting';
import { useTheme } from '@/utils/hooks';
import { getEditorMode, logEnded } from '@/utils';
import WebSocketManager from '@/utils/websocket';
const { Option } = Select;
@@ -18,12 +25,10 @@ const EditModal = ({
content,
handleCancel,
visible,
socketMessage,
}: {
treeData?: any;
content?: string;
visible: boolean;
socketMessage: any;
currentNode: any;
handleCancel: () => void;
}) => {
@@ -34,12 +39,11 @@ const EditModal = ({
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
const [settingModalVisible, setSettingModalVisible] =
useState<boolean>(false);
const [log, setLog] = useState<string>('');
const [log, setLog] = useState('');
const { theme } = useTheme();
const editorRef = useRef<any>(null);
const [isRunning, setIsRunning] = useState(false);
const [currentPid, setCurrentPid] = useState(null);
const cancel = () => {
handleCancel();
};
@@ -104,28 +108,25 @@ const EditModal = ({
});
};
useEffect(() => {
if (!socketMessage) {
return;
}
let { type, message: _message, references } = socketMessage;
if (type !== 'manuallyRunScript') {
return;
}
const handleMessage = useCallback((payload: any) => {
let { message: _message } = payload;
if (logEnded(_message)) {
setTimeout(() => {
setIsRunning(false);
}, 300);
}
if (log) {
_message = `\n${_message}`;
}
setLog(`${log}${_message}`);
}, [socketMessage]);
setLog(p=>`${p}${_message}`);
}, []);
useEffect(() => {
const ws = WebSocketManager.getInstance();
ws.subscribe('manuallyRunScript', handleMessage);
return () => {
ws.unsubscribe('manuallyRunScript', handleMessage);
};
}, []);
useEffect(() => {
setLog('');
+3 -4
View File
@@ -48,7 +48,7 @@ import { langs } from '@uiw/codemirror-extensions-langs';
const { Text } = Typography;
const Script = () => {
const { headerStyle, isPhone, theme, socketMessage } =
const { headerStyle, isPhone, theme } =
useOutletContext<SharedContext>();
const [value, setValue] = useState(intl.get('请选择脚本文件'));
const [select, setSelect] = useState<string>('');
@@ -591,16 +591,15 @@ const Script = () => {
}}
/>
)}
<EditModal
{isLogModalVisible && <EditModal
visible={isLogModalVisible}
treeData={data}
currentNode={currentNode}
content={value}
socketMessage={socketMessage}
handleCancel={() => {
setIsLogModalVisible(false);
}}
/>
/>}
<EditScriptNameModal
visible={isAddFileModalVisible}
treeData={data}
+4 -7
View File
@@ -7,12 +7,6 @@ import dayjs from 'dayjs';
const { Link } = Typography;
enum TVersion {
'develop' = '开发版',
'master' = '正式版',
'debian' = '正式版'
}
const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
return (
<div className={styles.container}>
@@ -30,7 +24,10 @@ const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
</span>
<Descriptions>
<Descriptions.Item label={intl.get('版本')} span={3}>
{intl.get(TVersion[systemInfo.branch])} v{systemInfo.version}
{systemInfo?.branch === 'develop'
? intl.get('开发版')
: intl.get('正式版')}{' '}
v{systemInfo.version}
</Descriptions.Item>
<Descriptions.Item label={intl.get('更新时间')} span={3}>
{dayjs(systemInfo.publishTime * 1000).format('YYYY-MM-DD HH:mm')}
+31 -21
View File
@@ -1,12 +1,14 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState, useRef } from 'react';
import React, { useEffect, useState, useRef, useCallback } from 'react';
import { Statistic, Modal, Tag, Button, Spin, message } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import WebSocketManager from '@/utils/websocket';
import Ansi from 'ansi-to-react';
const { Countdown } = Statistic;
const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
const CheckUpdate = ({ systemInfo }: any) => {
const [updateLoading, setUpdateLoading] = useState(false);
const [value, setValue] = useState('');
const modalRef = useRef<any>();
@@ -149,17 +151,8 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
};
useEffect(() => {
if (!modalRef.current || !socketMessage) {
return;
}
const { type, message: _message, references } = socketMessage;
if (type !== 'updateSystemVersion') {
return;
}
const newMessage = `${value}${_message}`;
const updateFailed = newMessage.includes('失败');
if (!value) return;
const updateFailed = value.includes('失败,请检查');
modalRef.current.update({
maskClosable: updateFailed,
@@ -167,29 +160,46 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
okButtonProps: { disabled: !updateFailed },
content: (
<>
<pre>{newMessage}</pre>
<pre>
<Ansi>{value}</Ansi>
</pre>
<div id="log-identifier" style={{ paddingBottom: 5 }}></div>
</>
),
});
}, [value]);
if (updateFailed && !value.includes('失败,请检查')) {
const handleMessage = useCallback((payload: any) => {
let { message: _message } = payload;
const updateFailed = _message.includes('失败,请检查');
if (updateFailed) {
message.error(intl.get('更新失败,请检查网络及日志或稍后再试'));
}
setValue(newMessage);
document.getElementById('log-identifier') &&
setTimeout(() => {
document
.getElementById('log-identifier')!
.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
.querySelector('#log-identifier')!
.scrollIntoView({ behavior: 'smooth' });
}, 600);
if (_message.includes('更新包下载成功')) {
setTimeout(() => {
showReloadModal();
}, 1000);
}
}, [socketMessage]);
setValue((p) => `${p}${_message}`);
}, []);
useEffect(() => {
const ws = WebSocketManager.getInstance();
ws.subscribe('updateSystemVersion', handleMessage);
return () => {
ws.unsubscribe('updateSystemVersion', handleMessage);
};
}, []);
return (
<>
-2
View File
@@ -46,7 +46,6 @@ const Setting = () => {
theme,
reloadUser,
reloadTheme,
socketMessage,
systemInfo,
} = useOutletContext<SharedContext>();
const columns = [
@@ -376,7 +375,6 @@ const Setting = () => {
children: (
<Other
reloadTheme={reloadTheme}
socketMessage={socketMessage}
systemInfo={systemInfo}
/>
),
+2 -3
View File
@@ -24,9 +24,8 @@ import useProgress from './progress';
const Other = ({
systemInfo,
socketMessage,
reloadTheme,
}: Pick<SharedContext, 'socketMessage' | 'reloadTheme' | 'systemInfo'>) => {
}: Pick<SharedContext, 'reloadTheme' | 'systemInfo'>) => {
const defaultTheme = localStorage.getItem('qinglong_dark_theme') || 'auto';
const [systemConfig, setSystemConfig] = useState<{
logRemoveFrequency?: number | null;
@@ -274,7 +273,7 @@ const Other = ({
</Upload>
</Form.Item>
<Form.Item label={intl.get('检查更新')} name="update">
<CheckUpdate systemInfo={systemInfo} socketMessage={socketMessage} />
<CheckUpdate systemInfo={systemInfo} />
</Form.Item>
</Form>
);
+3 -2
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import { Modal, Progress } from 'antd';
import { useRef } from 'react';
@@ -15,14 +16,14 @@ export default function useProgress(title: string) {
const showProgress = (percent: number) => {
if (modalRef.current) {
modalRef.current.update({
title: `${title}${percent >= 100 ? '成功' : '中...'}`,
title: `${title}${percent >= 100 ? intl.get('成功') : intl.get('中...')}`,
content: <ProgressElement percent={percent} />,
});
} else {
modalRef.current = Modal.info({
width: 600,
maskClosable: false,
title: `${title}${percent >= 100 ? '成功' : '中...'}`,
title: `${title}${percent >= 100 ? intl.get('成功') : intl.get('中...')}`,
centered: true,
content: <ProgressElement percent={percent} />,
});
+21 -13
View File
@@ -1,5 +1,5 @@
import intl from 'react-intl-universal';
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
Button,
message,
@@ -36,6 +36,7 @@ import './index.less';
import SubscriptionLogModal from './logModal';
import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import WebSocketManager from '@/utils/websocket';
const { Text, Paragraph } = Typography;
const { Search } = Input;
@@ -61,8 +62,7 @@ export enum SubscriptionType {
}
const Subscription = () => {
const { headerStyle, isPhone, socketMessage } =
useOutletContext<SharedContext>();
const { headerStyle, isPhone } = useOutletContext<SharedContext>();
const columns: any = [
{
@@ -508,23 +508,31 @@ const Subscription = () => {
: 'subscription';
};
useEffect(() => {
if (!socketMessage) return;
const { type, message, references } = socketMessage;
if (type === 'runSubscriptionEnd' && references.length > 0) {
const result = [...value];
const handleMessage = useCallback((payload: any) => {
const { message, references } = payload;
setValue((p) => {
const result = [...p];
for (let i = 0; i < references.length; i++) {
const index = value.findIndex((x) => x.id === references[i]);
const index = p.findIndex((x) => x.id === references[i]);
if (index !== -1) {
result.splice(index, 1, {
...value[index],
...p[index],
status: SubscriptionStatus.idle,
});
}
}
setValue(result);
}
}, [socketMessage]);
return result;
});
}, []);
useEffect(() => {
const ws = WebSocketManager.getInstance();
ws.subscribe('runSubscriptionEnd', handleMessage);
return () => {
ws.unsubscribe('runSubscriptionEnd', handleMessage);
};
}, []);
useEffect(() => {
if (logSubscription) {
+8
View File
@@ -176,6 +176,14 @@ export default {
label: 'barkGroup',
tip: intl.get('BARK推送消息的分组,默认为qinglong'),
},
{
label: 'barkLevel',
tip: intl.get('BARK推送消息的时效性,默认为active'),
},
{
label: 'barkUrl',
tip: intl.get('BARK推送消息的跳转URL'),
},
],
telegramBot: [
{
+8
View File
@@ -0,0 +1,8 @@
export type SockMessageType =
| 'ping'
| 'installDependence'
| 'uninstallDependence'
| 'updateSystemVersion'
| 'manuallyRunScript'
| 'runSubscriptionEnd'
| 'reloadSystem';
+154
View File
@@ -0,0 +1,154 @@
import SockJS from 'sockjs-client';
import { SockMessageType } from './type';
class WebSocketManager {
private static instance: WebSocketManager | null = null;
private url: string;
private socket: WebSocket | null = null;
private subscriptions: Map<SockMessageType, Set<(p: any) => void>> = new Map();
private options: {
maxReconnectAttempts: number;
reconnectInterval: number;
heartbeatInterval: number;
};
private reconnectAttempts: number = 0;
private heartbeatTimeout: NodeJS.Timeout | null = null;
private state: 'closed' | 'connecting' | 'open' = 'closed';
constructor(url: string, options: Partial<typeof WebSocketManager.prototype.options> = {}) {
this.url = url;
this.options = {
maxReconnectAttempts: options.maxReconnectAttempts || 5,
reconnectInterval: options.reconnectInterval || 3000,
heartbeatInterval: options.heartbeatInterval || 30000,
};
this.init();
}
public static getInstance(url: string = '', options?: Partial<typeof WebSocketManager.prototype.options>): WebSocketManager {
if (!WebSocketManager.instance) {
WebSocketManager.instance = new WebSocketManager(url, options);
}
return WebSocketManager.instance;
}
private async init() {
try {
this.state = 'connecting';
this.emit('connecting');
while (this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.socket = new SockJS(this.url);
this.setupEventListeners();
this.startHeartbeat();
await this.waitForClose();
this.stopHeartbeat();
this.socket = null;
this.reconnectAttempts++;
await new Promise((resolve) => setTimeout(resolve, this.options.reconnectInterval));
}
} catch (error) {
this.handleError(error);
}
}
private setupEventListeners() {
if (!this.socket) return;
this.socket.onopen = () => {
this.state = 'open';
this.emit('open');
};
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data);
this.dispatchMessage(message);
};
this.socket.onclose = () => {
this.state = 'closed';
this.emit('close');
};
}
private async waitForClose() {
while (this.socket?.readyState !== SockJS.CLOSED) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
public subscribe(topic: SockMessageType, callback: (v: any) => void) {
const topicSubscriptions = this.subscriptions.get(topic) || new Set();
if (!topicSubscriptions.has(callback)) {
topicSubscriptions.add(callback);
this.subscriptions.set(topic, topicSubscriptions);
const subscriptionMessage = { action: 'subscribe', topic };
this.send(subscriptionMessage);
}
}
public unsubscribe(topic: SockMessageType, callback: (v: any) => void) {
const topicSubscriptions = this.subscriptions.get(topic) || new Set();
if (topicSubscriptions.has(callback)) {
topicSubscriptions.delete(callback);
const unsubscribeMessage = { action: 'unsubscribe', topic };
this.send(unsubscribeMessage);
}
}
public send(message: any) {
if (this.socket?.readyState === SockJS.OPEN) {
this.socket.send(JSON.stringify(message));
}
}
private dispatchMessage(message: any) {
const { type, ...others } = message;
const topicSubscriptions = this.subscriptions.get(type) || new Set();
[...topicSubscriptions].forEach((callback) => callback(others));
}
private startHeartbeat() {
this.heartbeatTimeout = setInterval(() => {
if (this.socket?.readyState === SockJS.OPEN) {
this.socket.send(JSON.stringify({ type: 'heartbeat' }));
}
}, this.options.heartbeatInterval);
}
private stopHeartbeat() {
if (this.heartbeatTimeout) {
clearInterval(this.heartbeatTimeout);
}
}
public close() {
if (this.socket) {
this.state = 'closed';
this.stopHeartbeat();
this.socket.close();
this.emit('close');
}
}
private handleError(error: any) {
console.error('WebSocket错误:', error);
this.emit('error', error);
}
public on(event: string, listener: Function) {
// this.addListener(event, listener);
}
public emit(event: string, data?: any) {
// this.listeners(event).forEach((listener) => listener(data));
}
}
export default WebSocketManager;
+7 -8
View File
@@ -1,19 +1,17 @@
{
"compilerOptions": {
"target": "es2017",
"lib": ["es2021", "esnext.asynciterable", "DOM"],
"typeRoots": [
"./node_modules/@types",
"./src/types",
"./node_modules/celebrate/lib/index.d.ts"
],
"lib": ["ESNext"],
"typeRoots": ["./node_modules/celebrate/lib", "./node_modules/@types"],
"paths": {
"@/*": ["./back/*"],
"@/*": ["./back/*"]
},
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"moduleResolution": "node",
"module": "commonjs",
"pretty": true,
@@ -24,5 +22,6 @@
"esModuleInterop": true
},
"include": ["./back/**/*"],
"exclude": ["node_modules", "tests"]
"exclude": ["node_modules"],
"files": ["./back/index.d.ts"]
}
+3 -8
View File
@@ -1,8 +1,8 @@
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"target": "ESNext",
"module": "ES2015",
"moduleResolution": "Bundler",
"importHelpers": true,
"jsx": "react-jsx",
"esModuleInterop": true,
@@ -14,11 +14,6 @@
"@@/*": ["src/.umi/*"]
},
"lib": ["dom", "es2021", "esnext.asynciterable"],
"typeRoots": [
"./node_modules/@types",
"./back/types",
"./node_modules/celebrate/lib/index.d.ts"
],
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
-2
View File
@@ -9,8 +9,6 @@ declare module '*.svg' {
export default url;
}
declare module 'pstree.remy';
interface Window {
__ENV__QlBaseUrl: string;
__ENV__DeployEnv: string;
+10 -9
View File
@@ -1,10 +1,11 @@
version: 2.16.1
changeLogLink: https://t.me/jiao_long/392
publishTime: 2023-08-21 23:00
version: 2.16.3
changeLogLink: https://t.me/jiao_long/394
publishTime: 2023-09-23 12:00
changeLog: |
1. 系统设置增加系统日志,展示系统和任务运行日志
2. 修复 python 企业微信推送
3. 修复 debian 版本关于页面报错
4. 修复添加订阅粘贴 ql 命令链接参数可能错误
5. 修改日志弹框样式
6. 其他 bug 修复
1. 定时任务支持设置多定时规则、任务执行前和执行后命令
2. 增加启动环境变量 QlPort, 支持修改 hosts 模式青龙启动端口
3. 消息通知 Bark 增加额外参数: 时效性通知、跳转Url
4. 修复调试脚本日志丢失
5. 环境变量名称增加复制功能
6. 修复系统设置展示版本失败
7. 其他 bug 修复