@whyour/qinglong 2.21.0-6 → 2.21.0-8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/back/protos/api.proto +3 -0
- package/deploy/kubernetes/README.md +81 -0
- package/deploy/kubernetes/base/kustomization.yaml +5 -0
- package/deploy/kubernetes/base/qinglong.yaml +95 -0
- package/deploy/kubernetes/overlays/example/kustomization.yaml +10 -0
- package/package.json +1 -1
- package/sample/config.sample.sh +7 -2
- package/sample/notify.js +64 -0
- package/sample/notify.py +56 -0
- package/static/build/api/env.js +2 -2
- package/static/build/api/system.js +6 -7
- package/static/build/app.js +19 -19
- package/static/build/config/const.js +1 -0
- package/static/build/config/grpcCerts.js +4 -4
- package/static/build/data/notify.js +8 -1
- package/static/build/loaders/app.js +6 -6
- package/static/build/loaders/db.js +2 -2
- package/static/build/loaders/express.js +1 -6
- package/static/build/loaders/initFile.js +1 -1
- package/static/build/loaders/server.js +1 -1
- package/static/build/protos/api.js +23 -1
- package/static/build/schedule/addCron.js +67 -10
- package/static/build/schedule/delCron.js +11 -1
- package/static/build/services/cron.js +88 -24
- package/static/build/services/grpc.js +1 -1
- package/static/build/services/http.js +1 -1
- package/static/build/services/notify.js +42 -0
- package/static/build/services/user.js +3 -0
- package/static/build/shared/auth.js +7 -1
- package/static/build/shared/i18n.js +7 -0
- package/static/build/validation/schedule.js +9 -0
- package/static/dist/1147.62793604.async.js +1 -0
- package/static/dist/2208.85285476.async.js +1 -0
- package/static/dist/2286.f371c743.async.js +1 -0
- package/static/dist/3191.545ab873.async.js +1 -0
- package/static/dist/8826.db34ad8d.async.js +1 -0
- package/static/dist/index.html +2 -2
- package/static/dist/{preload_helper.e0bef63b.js → preload_helper.3048fe5b.js} +1 -1
- package/static/dist/{src__pages__crontab__index.6b63ca05.async.js → src__pages__crontab__index.68e78a33.async.js} +1 -1
- package/static/dist/src__pages__dependence__modal.f7bb47ba.async.js +1 -0
- package/static/dist/src__pages__env__editNameModal.48715576.async.js +1 -0
- package/static/dist/src__pages__env__index.452cdc47.async.js +1 -0
- package/static/dist/src__pages__env__modal.e1234834.async.js +1 -0
- package/static/dist/src__pages__error__index.dedb2b90.async.js +1 -0
- package/static/dist/src__pages__initialization__index.22620a77.async.js +1 -0
- package/static/dist/src__pages__script__editNameModal.532a3044.async.js +1 -0
- package/static/dist/src__pages__script__index.1fc20f22.async.js +1 -0
- package/static/dist/src__pages__script__renameModal.5cdde919.async.js +1 -0
- package/static/dist/src__pages__script__saveModal.5ffed35f.async.js +1 -0
- package/static/dist/src__pages__script__setting.1a82c6d7.async.js +1 -0
- package/static/dist/src__pages__setting__appModal.43b7758a.async.js +1 -0
- package/static/dist/src__pages__setting__dependence.9c04c79a.async.js +1 -0
- package/static/dist/src__pages__setting__index.7e070838.async.js +1 -0
- package/static/dist/src__pages__setting__notification.bac481c2.async.js +1 -0
- package/static/dist/src__pages__setting__security.b7235753.async.js +1 -0
- package/static/dist/src__pages__setting__systemLog.b81a8c1d.async.js +1 -0
- package/static/dist/src__pages__subscription__modal.562448e2.async.js +1 -0
- package/static/dist/{umi.025a31b3.js → umi.a54a59f6.js} +1 -1
- package/static/dist/1147.c420132e.async.js +0 -1
- package/static/dist/2208.f7ba3dfa.async.js +0 -1
- package/static/dist/2286.164bb089.async.js +0 -1
- package/static/dist/3191.37d489cf.async.js +0 -1
- package/static/dist/8826.6879b271.async.js +0 -1
- package/static/dist/src__pages__dependence__modal.11124896.async.js +0 -1
- package/static/dist/src__pages__env__editNameModal.5d264b25.async.js +0 -1
- package/static/dist/src__pages__env__index.9a565d2b.async.js +0 -1
- package/static/dist/src__pages__env__modal.7f2ef1bc.async.js +0 -1
- package/static/dist/src__pages__error__index.f156b45e.async.js +0 -1
- package/static/dist/src__pages__initialization__index.e96d4ba8.async.js +0 -1
- package/static/dist/src__pages__script__editNameModal.3e7e100d.async.js +0 -1
- package/static/dist/src__pages__script__index.e869812f.async.js +0 -1
- package/static/dist/src__pages__script__renameModal.5e987ef5.async.js +0 -1
- package/static/dist/src__pages__script__saveModal.3f9d23d6.async.js +0 -1
- package/static/dist/src__pages__script__setting.a535793a.async.js +0 -1
- package/static/dist/src__pages__setting__appModal.251cd14f.async.js +0 -1
- package/static/dist/src__pages__setting__dependence.29f13e8c.async.js +0 -1
- package/static/dist/src__pages__setting__index.7a39d415.async.js +0 -1
- package/static/dist/src__pages__setting__notification.390fc905.async.js +0 -1
- package/static/dist/src__pages__setting__security.598720a8.async.js +0 -1
- package/static/dist/src__pages__setting__systemLog.1a5c855f.async.js +0 -1
- package/static/dist/src__pages__subscription__modal.3562c670.async.js +0 -1
|
@@ -12,16 +12,16 @@ const initTask_1 = __importDefault(require("./initTask"));
|
|
|
12
12
|
const initFile_1 = __importDefault(require("./initFile"));
|
|
13
13
|
exports.default = async ({ app }) => {
|
|
14
14
|
(0, depInjector_1.default)();
|
|
15
|
-
logger_1.default.info('
|
|
15
|
+
logger_1.default.info('[boot] Dependency loaded');
|
|
16
16
|
await (0, deps_1.default)();
|
|
17
|
-
logger_1.default.info('
|
|
17
|
+
logger_1.default.info('[boot] Link deps loaded');
|
|
18
18
|
await (0, initFile_1.default)();
|
|
19
|
-
logger_1.default.info('
|
|
19
|
+
logger_1.default.info('[boot] Init file loaded');
|
|
20
20
|
await (0, initData_1.default)();
|
|
21
|
-
logger_1.default.info('
|
|
21
|
+
logger_1.default.info('[boot] Init data loaded');
|
|
22
22
|
(0, initTask_1.default)();
|
|
23
|
-
logger_1.default.info('
|
|
23
|
+
logger_1.default.info('[boot] Init task loaded');
|
|
24
24
|
(0, express_1.default)({ app });
|
|
25
|
-
logger_1.default.info('
|
|
25
|
+
logger_1.default.info('[boot] Express loaded');
|
|
26
26
|
};
|
|
27
27
|
//# sourceMappingURL=app.js.map
|
|
@@ -58,10 +58,10 @@ exports.default = async () => {
|
|
|
58
58
|
// Column already exists or other error, continue
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
logger_1.default.info('
|
|
61
|
+
logger_1.default.info('[boot] DB loaded');
|
|
62
62
|
}
|
|
63
63
|
catch (error) {
|
|
64
|
-
logger_1.default.error('
|
|
64
|
+
logger_1.default.error('[boot] DB load failed', error);
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
//# sourceMappingURL=db.js.map
|
|
@@ -121,12 +121,7 @@ exports.default = ({ app }) => {
|
|
|
121
121
|
return next();
|
|
122
122
|
}
|
|
123
123
|
const authInfo = (await store_1.shareStore.getAuthInfo()) || {};
|
|
124
|
-
let isInitialized =
|
|
125
|
-
if (Object.keys(authInfo).length === 2 &&
|
|
126
|
-
authInfo.username === 'admin' &&
|
|
127
|
-
authInfo.password === 'admin') {
|
|
128
|
-
isInitialized = false;
|
|
129
|
-
}
|
|
124
|
+
let isInitialized = !(0, auth_1.isDefaultAuthInfo)(authInfo);
|
|
130
125
|
if (isInitialized) {
|
|
131
126
|
return res.send({ code: 450, message: (0, i18n_1.t)('未知错误') });
|
|
132
127
|
}
|
|
@@ -7,7 +7,7 @@ const logger_1 = __importDefault(require("./logger"));
|
|
|
7
7
|
const sock_1 = __importDefault(require("./sock"));
|
|
8
8
|
exports.default = async ({ server }) => {
|
|
9
9
|
await (0, sock_1.default)({ server });
|
|
10
|
-
logger_1.default.info('
|
|
10
|
+
logger_1.default.info('[boot] Sock loaded');
|
|
11
11
|
process.on('uncaughtException', (error) => {
|
|
12
12
|
logger_1.default.error('Uncaught exception:', error);
|
|
13
13
|
});
|
|
@@ -33,6 +33,7 @@ var NotificationMode;
|
|
|
33
33
|
NotificationMode[NotificationMode["chronocat"] = 18] = "chronocat";
|
|
34
34
|
NotificationMode[NotificationMode["ntfy"] = 19] = "ntfy";
|
|
35
35
|
NotificationMode[NotificationMode["wxPusherBot"] = 20] = "wxPusherBot";
|
|
36
|
+
NotificationMode[NotificationMode["wxPusherSpt"] = 21] = "wxPusherSpt";
|
|
36
37
|
NotificationMode[NotificationMode["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
|
|
37
38
|
})(NotificationMode || (exports.NotificationMode = NotificationMode = {}));
|
|
38
39
|
function notificationModeFromJSON(object) {
|
|
@@ -100,6 +101,9 @@ function notificationModeFromJSON(object) {
|
|
|
100
101
|
case 20:
|
|
101
102
|
case "wxPusherBot":
|
|
102
103
|
return NotificationMode.wxPusherBot;
|
|
104
|
+
case 21:
|
|
105
|
+
case "wxPusherSpt":
|
|
106
|
+
return NotificationMode.wxPusherSpt;
|
|
103
107
|
case -1:
|
|
104
108
|
case "UNRECOGNIZED":
|
|
105
109
|
default:
|
|
@@ -151,6 +155,8 @@ function notificationModeToJSON(object) {
|
|
|
151
155
|
return "ntfy";
|
|
152
156
|
case NotificationMode.wxPusherBot:
|
|
153
157
|
return "wxPusherBot";
|
|
158
|
+
case NotificationMode.wxPusherSpt:
|
|
159
|
+
return "wxPusherSpt";
|
|
154
160
|
case NotificationMode.UNRECOGNIZED:
|
|
155
161
|
default:
|
|
156
162
|
return "UNRECOGNIZED";
|
|
@@ -2495,6 +2501,7 @@ function createBaseNotificationInfo() {
|
|
|
2495
2501
|
wxPusherBotAppToken: undefined,
|
|
2496
2502
|
wxPusherBotTopicIds: undefined,
|
|
2497
2503
|
wxPusherBotUids: undefined,
|
|
2504
|
+
wxPusherSptList: undefined,
|
|
2498
2505
|
};
|
|
2499
2506
|
}
|
|
2500
2507
|
exports.NotificationInfo = {
|
|
@@ -2706,6 +2713,9 @@ exports.NotificationInfo = {
|
|
|
2706
2713
|
if (message.wxPusherBotUids !== undefined) {
|
|
2707
2714
|
writer.uint32(546).string(message.wxPusherBotUids);
|
|
2708
2715
|
}
|
|
2716
|
+
if (message.wxPusherSptList !== undefined) {
|
|
2717
|
+
writer.uint32(562).string(message.wxPusherSptList);
|
|
2718
|
+
}
|
|
2709
2719
|
return writer;
|
|
2710
2720
|
},
|
|
2711
2721
|
decode(input, length) {
|
|
@@ -3198,6 +3208,13 @@ exports.NotificationInfo = {
|
|
|
3198
3208
|
message.wxPusherBotUids = reader.string();
|
|
3199
3209
|
continue;
|
|
3200
3210
|
}
|
|
3211
|
+
case 70: {
|
|
3212
|
+
if (tag !== 562) {
|
|
3213
|
+
break;
|
|
3214
|
+
}
|
|
3215
|
+
message.wxPusherSptList = reader.string();
|
|
3216
|
+
continue;
|
|
3217
|
+
}
|
|
3201
3218
|
}
|
|
3202
3219
|
if ((tag & 7) === 4 || tag === 0) {
|
|
3203
3220
|
break;
|
|
@@ -3289,6 +3306,7 @@ exports.NotificationInfo = {
|
|
|
3289
3306
|
? globalThis.String(object.wxPusherBotTopicIds)
|
|
3290
3307
|
: undefined,
|
|
3291
3308
|
wxPusherBotUids: isSet(object.wxPusherBotUids) ? globalThis.String(object.wxPusherBotUids) : undefined,
|
|
3309
|
+
wxPusherSptList: isSet(object.wxPusherSptList) ? globalThis.String(object.wxPusherSptList) : undefined,
|
|
3292
3310
|
};
|
|
3293
3311
|
},
|
|
3294
3312
|
toJSON(message) {
|
|
@@ -3500,13 +3518,16 @@ exports.NotificationInfo = {
|
|
|
3500
3518
|
if (message.wxPusherBotUids !== undefined) {
|
|
3501
3519
|
obj.wxPusherBotUids = message.wxPusherBotUids;
|
|
3502
3520
|
}
|
|
3521
|
+
if (message.wxPusherSptList !== undefined) {
|
|
3522
|
+
obj.wxPusherSptList = message.wxPusherSptList;
|
|
3523
|
+
}
|
|
3503
3524
|
return obj;
|
|
3504
3525
|
},
|
|
3505
3526
|
create(base) {
|
|
3506
3527
|
return exports.NotificationInfo.fromPartial(base !== null && base !== void 0 ? base : {});
|
|
3507
3528
|
},
|
|
3508
3529
|
fromPartial(object) {
|
|
3509
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44;
|
|
3530
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45;
|
|
3510
3531
|
const message = createBaseNotificationInfo();
|
|
3511
3532
|
message.type = (_a = object.type) !== null && _a !== void 0 ? _a : 0;
|
|
3512
3533
|
message.gotifyUrl = (_b = object.gotifyUrl) !== null && _b !== void 0 ? _b : undefined;
|
|
@@ -3577,6 +3598,7 @@ exports.NotificationInfo = {
|
|
|
3577
3598
|
message.wxPusherBotAppToken = (_42 = object.wxPusherBotAppToken) !== null && _42 !== void 0 ? _42 : undefined;
|
|
3578
3599
|
message.wxPusherBotTopicIds = (_43 = object.wxPusherBotTopicIds) !== null && _43 !== void 0 ? _43 : undefined;
|
|
3579
3600
|
message.wxPusherBotUids = (_44 = object.wxPusherBotUids) !== null && _44 !== void 0 ? _44 : undefined;
|
|
3601
|
+
message.wxPusherSptList = (_45 = object.wxPusherSptList) !== null && _45 !== void 0 ? _45 : undefined;
|
|
3580
3602
|
return message;
|
|
3581
3603
|
},
|
|
3582
3604
|
};
|
|
@@ -4,14 +4,59 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.addCron = void 0;
|
|
7
|
+
const grpc_js_1 = require("@grpc/grpc-js");
|
|
7
8
|
const node_schedule_1 = __importDefault(require("node-schedule"));
|
|
8
9
|
const data_1 = require("./data");
|
|
9
10
|
const runCron_1 = require("../shared/runCron");
|
|
10
11
|
const logger_1 = __importDefault(require("../loaders/logger"));
|
|
12
|
+
const i18n_1 = require("../shared/i18n");
|
|
13
|
+
/**
|
|
14
|
+
* 预校验 cron 表达式,检测 node-schedule 会拒绝但 cron-parser 会接受的 pattern。
|
|
15
|
+
* node-schedule 对 bare /N(字段以 / 开头,如前无星号/数字前缀的 /6)返回 null,
|
|
16
|
+
* 提前拦截避免走 scheduleJob 后才发现无效。
|
|
17
|
+
*/
|
|
18
|
+
const isValidCronField = (cron) => {
|
|
19
|
+
// 检测 bare /N 模式:字段以 / 开头如 "/6",或空格后紧跟 "/6"
|
|
20
|
+
// node-schedule 会对这种字段返回 null
|
|
21
|
+
if (/\s\/\d/.test(cron) || /^\/\d/.test(cron)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// 检测 ? 字符:Quartz cron 语法,node-schedule 在大多数位置返回 null
|
|
25
|
+
// cron-parser 接受但 node-schedule 拒绝,提前拦截
|
|
26
|
+
if (/\?/.test(cron)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
11
31
|
const addCron = (call, callback) => {
|
|
12
32
|
var _a;
|
|
33
|
+
// ===== 第一遍:预校验所有 cron 表达式 =====
|
|
34
|
+
const validationErrors = [];
|
|
35
|
+
for (const item of call.request.crons) {
|
|
36
|
+
const { id, schedule, extra_schedules } = item;
|
|
37
|
+
if (!isValidCronField(schedule)) {
|
|
38
|
+
validationErrors.push((0, i18n_1.tf)('任务ID %s: 无效的 cron 表达式 "%s"(不支持裸 /N 步长和 ? 字符)', String(id), schedule));
|
|
39
|
+
}
|
|
40
|
+
if (extra_schedules === null || extra_schedules === void 0 ? void 0 : extra_schedules.length) {
|
|
41
|
+
extra_schedules.forEach((x) => {
|
|
42
|
+
if (!isValidCronField(x.schedule)) {
|
|
43
|
+
validationErrors.push((0, i18n_1.tf)('任务ID %s (extra_schedule): 无效的 cron 表达式 "%s"(不支持裸 /N 步长和 ? 字符)', String(id), x.schedule));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (validationErrors.length > 0) {
|
|
49
|
+
const details = validationErrors.join('\n');
|
|
50
|
+
const err = new Error(details);
|
|
51
|
+
err.code = grpc_js_1.status.INVALID_ARGUMENT;
|
|
52
|
+
err.details = details;
|
|
53
|
+
callback(err, null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// ===== 第二遍:注册所有任务 =====
|
|
13
57
|
for (const item of call.request.crons) {
|
|
14
58
|
const { id, schedule, command, extra_schedules, name } = item;
|
|
59
|
+
// 取消该 id 已有的旧任务
|
|
15
60
|
if (data_1.scheduleStacks.has(id)) {
|
|
16
61
|
(_a = data_1.scheduleStacks.get(id)) === null || _a === void 0 ? void 0 : _a.forEach((x) => x.cancel());
|
|
17
62
|
}
|
|
@@ -21,18 +66,30 @@ const addCron = (call, callback) => {
|
|
|
21
66
|
logger_1.default.info('[schedule][创建定时任务] 任务ID: %s, 名称: %s, cron: %s, 执行命令: %s', id, name, x.schedule, command);
|
|
22
67
|
});
|
|
23
68
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
69
|
+
const mainJob = node_schedule_1.default.scheduleJob(id, schedule, async () => {
|
|
70
|
+
logger_1.default.info(`[schedule][准备运行任务] 命令: ${command}`);
|
|
71
|
+
(0, runCron_1.runCron)(command, item);
|
|
72
|
+
});
|
|
73
|
+
if (!mainJob) {
|
|
74
|
+
logger_1.default.warn('[schedule][创建定时任务] scheduleJob 返回 null(不符合预期,已通过预校验): 任务ID: %s, cron: %s', id, schedule);
|
|
75
|
+
}
|
|
76
|
+
const extraJobs = (extra_schedules === null || extra_schedules === void 0 ? void 0 : extra_schedules.length)
|
|
77
|
+
? extra_schedules.map((x) => {
|
|
78
|
+
const job = node_schedule_1.default.scheduleJob(id, x.schedule, async () => {
|
|
31
79
|
logger_1.default.info(`[schedule][准备运行任务] 命令: ${command}`);
|
|
32
80
|
(0, runCron_1.runCron)(command, item);
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
|
|
81
|
+
});
|
|
82
|
+
if (!job) {
|
|
83
|
+
logger_1.default.warn('[schedule][创建定时任务] scheduleJob 返回 null(不符合预期,已通过预校验): 任务ID: %s, cron: %s', id, x.schedule);
|
|
84
|
+
}
|
|
85
|
+
return job;
|
|
86
|
+
})
|
|
87
|
+
: [];
|
|
88
|
+
// 过滤 null(兜底保护,正常情况下预校验已拦截)
|
|
89
|
+
const jobs = [mainJob, ...extraJobs].filter((x) => x != null);
|
|
90
|
+
if (jobs.length > 0) {
|
|
91
|
+
data_1.scheduleStacks.set(id, jobs);
|
|
92
|
+
}
|
|
36
93
|
}
|
|
37
94
|
callback(null, null);
|
|
38
95
|
};
|
|
@@ -11,7 +11,17 @@ const delCron = (call, callback) => {
|
|
|
11
11
|
for (const id of call.request.ids) {
|
|
12
12
|
if (data_1.scheduleStacks.has(id)) {
|
|
13
13
|
logger_1.default.info('[schedule][取消定时任务] 任务ID: %s', id);
|
|
14
|
-
|
|
14
|
+
// 过滤掉 nodeSchedule.scheduleJob() 对无效表达式返回的 null,
|
|
15
|
+
// 否则对 null 调 cancel() 会让整个取消流程抛出 UNKNOWN 错误,
|
|
16
|
+
// 进而导致 HTTP 端的 remove() 跳过 setCrontab(),造成 crontab.list 残留。
|
|
17
|
+
(_a = data_1.scheduleStacks.get(id)) === null || _a === void 0 ? void 0 : _a.filter((x) => x != null).forEach((x) => {
|
|
18
|
+
try {
|
|
19
|
+
x.cancel();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
logger_1.default.warn('[schedule][取消任务失败] 任务ID: %s, 错误: %s', id, (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
15
25
|
data_1.scheduleStacks.delete(id);
|
|
16
26
|
}
|
|
17
27
|
}
|
|
@@ -105,15 +105,24 @@ let CronService = class CronService {
|
|
|
105
105
|
return doc;
|
|
106
106
|
}
|
|
107
107
|
if (this.shouldUseCronClient(doc)) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
try {
|
|
109
|
+
await client_1.default.addCron([
|
|
110
|
+
{
|
|
111
|
+
name: doc.name || '',
|
|
112
|
+
id: String(doc.id),
|
|
113
|
+
schedule: doc.schedule,
|
|
114
|
+
command: this.makeCommand(doc),
|
|
115
|
+
extra_schedules: doc.extra_schedules || [],
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
// gRPC 注册失败时回滚 DB 记录,避免产生"僵尸任务"
|
|
121
|
+
// (DB 和 crontab.list 有记录但调度器永远不会执行)
|
|
122
|
+
await cron_1.CrontabModel.destroy({ where: { id: doc.id } });
|
|
123
|
+
this.logger.error('[crontab] Failed to register cron job in scheduler, task creation rolled back:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
124
|
+
throw new Error(`${(0, i18n_1.t)('调度器注册失败,任务创建已回滚')}: ${(error === null || error === void 0 ? void 0 : error.details) || (error === null || error === void 0 ? void 0 : error.message)}`);
|
|
125
|
+
}
|
|
117
126
|
}
|
|
118
127
|
await this.setCrontab();
|
|
119
128
|
return doc;
|
|
@@ -130,17 +139,46 @@ let CronService = class CronService {
|
|
|
130
139
|
if (doc.isDisabled === 1 || (0, util_1.isDemoEnv)()) {
|
|
131
140
|
return newDoc;
|
|
132
141
|
}
|
|
133
|
-
|
|
142
|
+
try {
|
|
143
|
+
await client_1.default.delCron([String(newDoc.id)]);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.logger.warn('[crontab] Failed to unregister cron job in scheduler:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
147
|
+
}
|
|
134
148
|
if (this.shouldUseCronClient(newDoc)) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
try {
|
|
150
|
+
await client_1.default.addCron([
|
|
151
|
+
{
|
|
152
|
+
name: doc.name || '',
|
|
153
|
+
id: String(newDoc.id),
|
|
154
|
+
schedule: newDoc.schedule,
|
|
155
|
+
command: this.makeCommand(newDoc),
|
|
156
|
+
extra_schedules: newDoc.extra_schedules || [],
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// gRPC 注册新任务失败 → 回滚 DB 到旧数据,并尝试恢复旧调度注册
|
|
162
|
+
await cron_1.CrontabModel.update(doc, { where: { id: doc.id } });
|
|
163
|
+
if (this.shouldUseCronClient(doc)) {
|
|
164
|
+
try {
|
|
165
|
+
await client_1.default.addCron([
|
|
166
|
+
{
|
|
167
|
+
name: doc.name || '',
|
|
168
|
+
id: String(doc.id),
|
|
169
|
+
schedule: doc.schedule,
|
|
170
|
+
command: this.makeCommand(doc),
|
|
171
|
+
extra_schedules: doc.extra_schedules || [],
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
catch (_recoveryError) {
|
|
176
|
+
this.logger.warn('[crontab] Failed to restore old cron job in scheduler after rollback:', (_recoveryError === null || _recoveryError === void 0 ? void 0 : _recoveryError.message) || _recoveryError);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this.logger.error('[crontab] Failed to register updated cron job in scheduler, update rolled back:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
180
|
+
throw new Error(`${(0, i18n_1.t)('调度器注册失败,任务更新已回滚')}: ${(error === null || error === void 0 ? void 0 : error.details) || (error === null || error === void 0 ? void 0 : error.message)}`);
|
|
181
|
+
}
|
|
144
182
|
}
|
|
145
183
|
await this.setCrontab();
|
|
146
184
|
return newDoc;
|
|
@@ -205,7 +243,12 @@ let CronService = class CronService {
|
|
|
205
243
|
}
|
|
206
244
|
async remove(ids) {
|
|
207
245
|
await cron_1.CrontabModel.destroy({ where: { id: ids } });
|
|
208
|
-
|
|
246
|
+
try {
|
|
247
|
+
await client_1.default.delCron(ids.map(String));
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
this.logger.warn('[crontab] Failed to unregister cron job in scheduler:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
251
|
+
}
|
|
209
252
|
await this.setCrontab();
|
|
210
253
|
}
|
|
211
254
|
async pin(ids) {
|
|
@@ -547,7 +590,12 @@ let CronService = class CronService {
|
|
|
547
590
|
}
|
|
548
591
|
async disabled(ids) {
|
|
549
592
|
await cron_1.CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
|
550
|
-
|
|
593
|
+
try {
|
|
594
|
+
await client_1.default.delCron(ids.map(String));
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
this.logger.warn('[crontab] Failed to unregister cron job in scheduler:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
598
|
+
}
|
|
551
599
|
await this.setCrontab();
|
|
552
600
|
}
|
|
553
601
|
async enabled(ids) {
|
|
@@ -565,7 +613,15 @@ let CronService = class CronService {
|
|
|
565
613
|
if ((0, util_1.isDemoEnv)()) {
|
|
566
614
|
return;
|
|
567
615
|
}
|
|
568
|
-
|
|
616
|
+
try {
|
|
617
|
+
await client_1.default.addCron(crons);
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
// gRPC 注册失败 → 回滚启用状态,避免 DB 显示已启用但调度器未注册
|
|
621
|
+
await cron_1.CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
|
622
|
+
this.logger.error('[crontab] Failed to register cron job in scheduler, enable rolled back:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
623
|
+
throw new Error(`${(0, i18n_1.t)('调度器注册失败,任务启用已回滚')}: ${(error === null || error === void 0 ? void 0 : error.details) || (error === null || error === void 0 ? void 0 : error.message)}`);
|
|
624
|
+
}
|
|
569
625
|
await this.setCrontab();
|
|
570
626
|
}
|
|
571
627
|
async log(id) {
|
|
@@ -719,8 +775,16 @@ let CronService = class CronService {
|
|
|
719
775
|
await (0, utils_1.writeFileWithLock)(config_1.default.crontabFile, '');
|
|
720
776
|
return;
|
|
721
777
|
}
|
|
722
|
-
|
|
723
|
-
|
|
778
|
+
// 先同步 crontab.list 与系统 crontab,确保其始终反映数据库真实状态。
|
|
779
|
+
// gRPC 调度注册为尽力而为:失败时不阻断文件同步,调度器重启后会重新注册。
|
|
780
|
+
// 这避免了因调度器短暂不可用导致 crontab.list 与数据库脱节(订阅更新误判任务已存在)。
|
|
781
|
+
await this.setCrontab(tabs);
|
|
782
|
+
try {
|
|
783
|
+
await client_1.default.addCron(regularCrons);
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
this.logger.warn('[crontab] Failed to register cron job in scheduler:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
787
|
+
}
|
|
724
788
|
}
|
|
725
789
|
async bootTask() {
|
|
726
790
|
const tabs = await this.crontabs();
|
|
@@ -75,7 +75,7 @@ let GrpcServerService = class GrpcServerService {
|
|
|
75
75
|
try {
|
|
76
76
|
const address = this.formatGrpcAddress(host, grpcPort);
|
|
77
77
|
await bindAsync(address, credentials);
|
|
78
|
-
logger_1.default.debug(
|
|
78
|
+
logger_1.default.debug(`[boot] gRPC service started successfully on ${address}`);
|
|
79
79
|
metrics_1.metricsService.record('grpc_service_start', 1, {
|
|
80
80
|
port: grpcPort.toString(),
|
|
81
81
|
host
|
|
@@ -27,7 +27,7 @@ let HttpServerService = class HttpServerService {
|
|
|
27
27
|
for (const host of hostsToTry) {
|
|
28
28
|
try {
|
|
29
29
|
const server = await this.tryListen(expressApp, port, host);
|
|
30
|
-
logger_1.default.debug(
|
|
30
|
+
logger_1.default.debug(`[boot] HTTP service started successfully on ${host}:${port}`);
|
|
31
31
|
metrics_1.metricsService.record('http_service_start', 1, {
|
|
32
32
|
port: port.toString(),
|
|
33
33
|
host
|
|
@@ -55,6 +55,7 @@ let NotificationService = class NotificationService {
|
|
|
55
55
|
['chronocat', this.chronocat],
|
|
56
56
|
['ntfy', this.ntfy],
|
|
57
57
|
['wxPusherBot', this.wxPusherBot],
|
|
58
|
+
['wxPusherSpt', this.wxPusherSpt],
|
|
58
59
|
['openiLink', this.openiLink],
|
|
59
60
|
]);
|
|
60
61
|
this.title = '';
|
|
@@ -620,6 +621,47 @@ let NotificationService = class NotificationService {
|
|
|
620
621
|
throw new Error(error.response ? error.response.body : error);
|
|
621
622
|
}
|
|
622
623
|
}
|
|
624
|
+
async wxPusherSpt() {
|
|
625
|
+
const { wxPusherSptList } = this.params;
|
|
626
|
+
// 处理 SPT,将逗号分隔的字符串转为数组
|
|
627
|
+
const spts = wxPusherSptList
|
|
628
|
+
? wxPusherSptList
|
|
629
|
+
.split(',')
|
|
630
|
+
.map((spt) => spt.trim())
|
|
631
|
+
.filter((spt) => spt)
|
|
632
|
+
: [];
|
|
633
|
+
if (!spts.length) {
|
|
634
|
+
throw new Error((0, i18n_1.t)('wxPusher SPT 不能为空'));
|
|
635
|
+
}
|
|
636
|
+
if (spts.length > 10) {
|
|
637
|
+
throw new Error((0, i18n_1.t)('wxPusher SPT 最多支持 10 个'));
|
|
638
|
+
}
|
|
639
|
+
const url = `https://wxpusher.zjiecode.com/api/send/message/simple-push`;
|
|
640
|
+
const json = {
|
|
641
|
+
content: `<h1>${this.title}</h1><br/><div style='white-space: pre-wrap;'>${this.content}</div>`,
|
|
642
|
+
summary: this.title,
|
|
643
|
+
contentType: 2,
|
|
644
|
+
};
|
|
645
|
+
// 单个 SPT 用 spt,多个用 sptList
|
|
646
|
+
if (spts.length === 1) {
|
|
647
|
+
json.spt = spts[0];
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
json.sptList = spts;
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const res = await http_1.httpClient.post(url, Object.assign(Object.assign({}, this.gotOption), { json }));
|
|
654
|
+
if (res.code === 1000) {
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
throw new Error(JSON.stringify(res));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
catch (error) {
|
|
662
|
+
throw new Error(error.response ? error.response.body : error);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
623
665
|
async chronocat() {
|
|
624
666
|
var _a, _b;
|
|
625
667
|
const { chronocatURL, chronocatQQ, chronocatToken } = this.params;
|
|
@@ -403,6 +403,9 @@ let UserService = class UserService {
|
|
|
403
403
|
}
|
|
404
404
|
async resetAuthInfo(info) {
|
|
405
405
|
const { retries, twoFactorActivated, password, username } = info;
|
|
406
|
+
if (password === 'admin') {
|
|
407
|
+
return { code: 400, message: (0, i18n_1.t)('密码不能设置为admin') };
|
|
408
|
+
}
|
|
406
409
|
const authInfo = await this.getAuthInfo();
|
|
407
410
|
const payload = (0, pickBy_1.default)({
|
|
408
411
|
retries,
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isValidToken = void 0;
|
|
3
|
+
exports.isValidToken = exports.isDefaultAuthInfo = void 0;
|
|
4
|
+
function isDefaultAuthInfo(authInfo) {
|
|
5
|
+
return (Object.keys(authInfo).length === 2 &&
|
|
6
|
+
authInfo.username === 'admin' &&
|
|
7
|
+
authInfo.password === 'admin');
|
|
8
|
+
}
|
|
9
|
+
exports.isDefaultAuthInfo = isDefaultAuthInfo;
|
|
4
10
|
/**
|
|
5
11
|
* Validates if a token exists in the authentication info.
|
|
6
12
|
* Supports both legacy string tokens and new TokenInfo array format.
|
|
@@ -82,6 +82,8 @@ const messages = {
|
|
|
82
82
|
'client_id 或 client_seret 有误': 'Invalid client_id or client_secret',
|
|
83
83
|
'订阅执行完成': 'Subscription completed',
|
|
84
84
|
'wxPusher 服务的 TopicIds 和 Uids 至少配置一个才行': 'wxPusher requires at least one of TopicIds or Uids',
|
|
85
|
+
'wxPusher SPT 不能为空': 'wxPusher SPT cannot be empty',
|
|
86
|
+
'wxPusher SPT 最多支持 10 个': 'wxPusher SPT supports at most 10 tokens',
|
|
85
87
|
'Url 或者 Body 中必须包含 $title': 'Url or Body must contain $title',
|
|
86
88
|
'绝对路径必须在日志目录内或使用 /dev/null': 'Absolute path must be within log directory or use /dev/null',
|
|
87
89
|
'请先登录': 'Please login first',
|
|
@@ -123,6 +125,11 @@ const messages = {
|
|
|
123
125
|
'执行before命令...': 'Execute before command...',
|
|
124
126
|
'执行after命令...': 'Execute after command...',
|
|
125
127
|
'## 执行结束... %s 耗时 %s 秒': '## Execution finished... %s elapsed %s seconds',
|
|
128
|
+
'调度器注册失败,任务创建已回滚': 'Scheduler registration failed, task creation rolled back',
|
|
129
|
+
'调度器注册失败,任务更新已回滚': 'Scheduler registration failed, task update rolled back',
|
|
130
|
+
'调度器注册失败,任务启用已回滚': 'Scheduler registration failed, task enable rolled back',
|
|
131
|
+
'任务ID %s: 无效的 cron 表达式 "%s"(不支持裸 /N 步长和 ? 字符)': 'Task ID %s: invalid cron expression "%s" (bare /N steps and ? character not supported)',
|
|
132
|
+
'任务ID %s (extra_schedule): 无效的 cron 表达式 "%s"(不支持裸 /N 步长和 ? 字符)': 'Task ID %s (extra_schedule): invalid cron expression "%s" (bare /N steps and ? character not supported)',
|
|
126
133
|
},
|
|
127
134
|
};
|
|
128
135
|
let currentLang = 'zh';
|
|
@@ -14,6 +14,15 @@ const validateSchedule = (value, helpers) => {
|
|
|
14
14
|
value.startsWith(schedule_1.ScheduleType.BOOT)) {
|
|
15
15
|
return value;
|
|
16
16
|
}
|
|
17
|
+
// 检测裸 /N 模式:cron-parser 会接受,但 node-schedule 会返回 null
|
|
18
|
+
// 提前拦截,避免任务入库后调度器注册失败
|
|
19
|
+
if (/\s\/\d/.test(value) || /^\/\d/.test(value)) {
|
|
20
|
+
return helpers.error('any.invalid');
|
|
21
|
+
}
|
|
22
|
+
// 检测 ? 字符:Quartz cron 语法,node-schedule 在大多数字段上返回 null
|
|
23
|
+
if (/\?/.test(value)) {
|
|
24
|
+
return helpers.error('any.invalid');
|
|
25
|
+
}
|
|
17
26
|
try {
|
|
18
27
|
if (cron_parser_1.default.parse(value).hasNext()) {
|
|
19
28
|
return value;
|