@whyour/qinglong 2.19.2-1 → 2.20.0-1
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/.env.example +1 -2
- package/README-en.md +4 -2
- package/README.md +4 -2
- package/back/protos/api.proto +17 -0
- package/docker/310.Dockerfile +23 -6
- package/docker/Dockerfile +22 -6
- package/docker/docker-entrypoint.sh +27 -14
- package/package.json +8 -9
- package/sample/notify.js +18 -2
- package/sample/notify.py +15 -0
- package/sample/ql_sample.js +28 -0
- package/shell/api.sh +8 -48
- package/shell/check.sh +5 -22
- package/shell/preload/client.js +6 -1
- package/shell/pub.sh +4 -4
- package/shell/share.sh +32 -55
- package/shell/task.sh +19 -10
- package/shell/update.sh +1 -0
- package/static/build/api/dependence.js +7 -1
- package/static/build/api/env.js +30 -4
- package/static/build/api/script.js +47 -8
- package/static/build/api/subscription.js +3 -3
- package/static/build/api/system.js +21 -28
- package/static/build/api/user.js +2 -1
- package/static/build/app.js +96 -18
- package/static/build/config/index.js +2 -2
- package/static/build/config/util.js +24 -1
- package/static/build/data/cron.js +4 -0
- package/static/build/data/env.js +3 -1
- package/static/build/data/notify.js +1 -0
- package/static/build/loaders/db.js +29 -35
- package/static/build/loaders/deps.js +22 -5
- package/static/build/loaders/express.js +19 -10
- package/static/build/loaders/initData.js +25 -1
- package/static/build/loaders/initTask.js +6 -0
- package/static/build/loaders/sock.js +10 -12
- package/static/build/protos/api.js +336 -1
- package/static/build/schedule/addCron.js +2 -2
- package/static/build/schedule/api.js +100 -1
- package/static/build/schedule/delCron.js +1 -1
- package/static/build/schedule/health.js +2 -3
- package/static/build/services/cron.js +55 -21
- package/static/build/services/dependence.js +6 -5
- package/static/build/services/env.js +9 -2
- package/static/build/services/notify.js +17 -5
- package/static/build/services/schedule.js +4 -4
- package/static/build/services/sshKey.js +24 -4
- package/static/build/services/subscription.js +11 -8
- package/static/build/services/system.js +18 -2
- package/static/build/services/user.js +83 -4
- package/static/build/shared/auth.js +40 -0
- package/static/build/shared/logStreamManager.js +104 -0
- package/static/build/shared/runCron.js +23 -0
- package/static/build/validation/schedule.js +39 -2
- package/static/dist/1147.856bb861.async.js +1 -0
- package/static/dist/1379.f91563a1.async.js +1 -0
- package/static/dist/{2208.3bc521b1.async.js → 2208.7bf7e296.async.js} +1 -1
- package/static/dist/3191.da7f3e07.async.js +1 -0
- package/static/dist/5691.931f59c5.async.js +1 -0
- package/static/dist/7571.4f6240b1.async.js +1 -0
- package/static/dist/{8826.3ab4ad84.async.js → 8826.5f289c4d.async.js} +1 -1
- package/static/dist/index.html +2 -2
- package/static/dist/preload_helper.17d7028f.js +1 -0
- package/static/dist/{src__pages__crontab__detail.64ec3a2b.async.js → src__pages__crontab__detail.b07f0c0a.async.js} +1 -1
- package/static/dist/src__pages__crontab__index.6b90d8c5.async.js +1 -0
- package/static/dist/{src__pages__crontab__logModal.5fa54b25.async.js → src__pages__crontab__logModal.5e6a4bf2.async.js} +1 -1
- package/static/dist/src__pages__crontab__modal.2d3d4953.async.js +1 -0
- package/static/dist/src__pages__dependence__modal.86604072.async.js +1 -0
- package/static/dist/{src__pages__env__editNameModal.665393cd.async.js → src__pages__env__editNameModal.79b7cf83.async.js} +1 -1
- package/static/dist/src__pages__env__index.a0a2fece.async.js +1 -0
- package/static/dist/{src__pages__env__modal.168498f9.async.js → src__pages__env__modal.b84c1173.async.js} +1 -1
- package/static/dist/{src__pages__error__index.d9beeda3.async.js → src__pages__error__index.01fac00e.async.js} +1 -1
- package/static/dist/{src__pages__initialization__index.2403c031.async.js → src__pages__initialization__index.2e49cf43.async.js} +1 -1
- package/static/dist/{src__pages__script__editNameModal.e36cd111.async.js → src__pages__script__editNameModal.05441c89.async.js} +1 -1
- package/static/dist/{src__pages__script__index.82b42e11.async.js → src__pages__script__index.6a23d7b5.async.js} +1 -1
- package/static/dist/{src__pages__script__renameModal.f9756f26.async.js → src__pages__script__renameModal.3bb00014.async.js} +1 -1
- package/static/dist/{src__pages__script__saveModal.e885e133.async.js → src__pages__script__saveModal.03cc698b.async.js} +1 -1
- package/static/dist/{src__pages__script__setting.8c2727b4.async.js → src__pages__script__setting.5a2a2a2c.async.js} +1 -1
- package/static/dist/{src__pages__setting__appModal.5a39121e.async.js → src__pages__setting__appModal.7f763fa7.async.js} +1 -1
- package/static/dist/src__pages__setting__checkUpdate.46efe057.async.js +1 -0
- package/static/dist/src__pages__setting__dependence.e64c4554.async.js +1 -0
- package/static/dist/src__pages__setting__index.3a220288.async.js +1 -0
- package/static/dist/src__pages__setting__notification.49003b2f.async.js +1 -0
- package/static/dist/src__pages__setting__other.0d931d6f.async.js +1 -0
- package/static/dist/src__pages__setting__security.a916e056.async.js +1 -0
- package/static/dist/{src__pages__setting__systemLog.fc5bdc78.async.js → src__pages__setting__systemLog.cbb0a3bb.async.js} +1 -1
- package/static/dist/src__pages__subscription__modal.ade477c1.async.js +1 -0
- package/static/dist/umi.ba9d6227.js +1 -0
- package/version.yaml +46 -9
- package/docker/front.conf +0 -61
- package/docker/nginx.conf +0 -45
- package/static/dist/2995.2eb218b3.async.js +0 -1
- package/static/dist/3191.cc1e31cd.async.js +0 -1
- package/static/dist/4046.7fbcfa02.async.js +0 -1
- package/static/dist/5713.8519f547.async.js +0 -1
- package/static/dist/8851.503b1e64.async.js +0 -1
- package/static/dist/preload_helper.0d173b72.js +0 -1
- package/static/dist/src__pages__crontab__index.74e7828f.async.js +0 -1
- package/static/dist/src__pages__crontab__modal.21258e08.async.js +0 -1
- package/static/dist/src__pages__dependence__modal.6639424a.async.js +0 -1
- package/static/dist/src__pages__env__index.70340ba7.async.js +0 -1
- package/static/dist/src__pages__setting__checkUpdate.c152dfaf.async.js +0 -1
- package/static/dist/src__pages__setting__dependence.a46e873d.async.js +0 -1
- package/static/dist/src__pages__setting__index.32b260ce.async.js +0 -1
- package/static/dist/src__pages__setting__notification.299f6b96.async.js +0 -1
- package/static/dist/src__pages__setting__other.8b77afcd.async.js +0 -1
- package/static/dist/src__pages__setting__security.e7371daa.async.js +0 -1
- package/static/dist/src__pages__subscription__modal.a7fd6a3c.async.js +0 -1
- package/static/dist/umi.35a646c6.js +0 -1
|
@@ -21,7 +21,7 @@ const config_1 = __importDefault(require("../config"));
|
|
|
21
21
|
const cron_1 = require("../data/cron");
|
|
22
22
|
const child_process_1 = require("child_process");
|
|
23
23
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
24
|
-
const cron_parser_1 =
|
|
24
|
+
const cron_parser_1 = require("cron-parser");
|
|
25
25
|
const util_1 = require("../config/util");
|
|
26
26
|
const sequelize_1 = require("sequelize");
|
|
27
27
|
const path_1 = __importDefault(require("path"));
|
|
@@ -34,6 +34,7 @@ const pickBy_1 = __importDefault(require("lodash/pickBy"));
|
|
|
34
34
|
const omit_1 = __importDefault(require("lodash/omit"));
|
|
35
35
|
const utils_1 = require("../shared/utils");
|
|
36
36
|
const schedule_1 = require("../interface/schedule");
|
|
37
|
+
const logStreamManager_1 = require("../shared/logStreamManager");
|
|
37
38
|
let CronService = class CronService {
|
|
38
39
|
constructor(logger) {
|
|
39
40
|
this.logger = logger;
|
|
@@ -54,9 +55,28 @@ let CronService = class CronService {
|
|
|
54
55
|
isSpecialSchedule(schedule) {
|
|
55
56
|
return this.isOnceSchedule(schedule) || this.isBootSchedule(schedule);
|
|
56
57
|
}
|
|
58
|
+
async getLogName(cron) {
|
|
59
|
+
const { log_name, command, id } = cron;
|
|
60
|
+
if (log_name === '/dev/null') {
|
|
61
|
+
return log_name;
|
|
62
|
+
}
|
|
63
|
+
let uniqPath = await (0, util_1.getUniqPath)(command, `${id}`);
|
|
64
|
+
if (log_name) {
|
|
65
|
+
const normalizedLogName = log_name.startsWith('/')
|
|
66
|
+
? log_name
|
|
67
|
+
: path_1.default.join(config_1.default.logPath, log_name);
|
|
68
|
+
if (normalizedLogName.startsWith(config_1.default.logPath)) {
|
|
69
|
+
uniqPath = log_name;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const logDirPath = path_1.default.resolve(config_1.default.logPath, `${uniqPath}`);
|
|
73
|
+
await promises_1.default.mkdir(logDirPath, { recursive: true });
|
|
74
|
+
return uniqPath;
|
|
75
|
+
}
|
|
57
76
|
async create(payload) {
|
|
58
77
|
const tab = new cron_1.Crontab(payload);
|
|
59
78
|
tab.saved = false;
|
|
79
|
+
tab.log_name = await this.getLogName(tab);
|
|
60
80
|
const doc = await this.insert(tab);
|
|
61
81
|
if ((0, util_1.isDemoEnv)()) {
|
|
62
82
|
return doc;
|
|
@@ -82,6 +102,7 @@ let CronService = class CronService {
|
|
|
82
102
|
const doc = await this.getDb({ id: payload.id });
|
|
83
103
|
const tab = new cron_1.Crontab(Object.assign(Object.assign({}, doc), payload));
|
|
84
104
|
tab.saved = false;
|
|
105
|
+
tab.log_name = await this.getLogName(tab);
|
|
85
106
|
const newDoc = await this.updateDb(tab);
|
|
86
107
|
if (doc.isDisabled === 1 || (0, util_1.isDemoEnv)()) {
|
|
87
108
|
return newDoc;
|
|
@@ -384,13 +405,14 @@ let CronService = class CronService {
|
|
|
384
405
|
async stop(ids) {
|
|
385
406
|
const docs = await cron_1.CrontabModel.findAll({ where: { id: ids } });
|
|
386
407
|
for (const doc of docs) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
408
|
+
// Kill all running instances of this task
|
|
409
|
+
try {
|
|
410
|
+
const command = this.makeCommand(doc);
|
|
411
|
+
await (0, util_1.killAllTasks)(command);
|
|
412
|
+
this.logger.info(`[panel][停止所有运行中的任务实例] 任务ID: ${doc.id}, 命令: ${command}`);
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
this.logger.error(`[panel][停止任务失败] 任务ID: ${doc.id}, 错误: ${error}`);
|
|
394
416
|
}
|
|
395
417
|
}
|
|
396
418
|
await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id: ids } });
|
|
@@ -398,7 +420,6 @@ let CronService = class CronService {
|
|
|
398
420
|
async runSingle(cronId) {
|
|
399
421
|
return pLimit_1.default.manualRunWithCronLimit(() => {
|
|
400
422
|
return new Promise(async (resolve) => {
|
|
401
|
-
var _a;
|
|
402
423
|
const cron = await this.getDb({ id: cronId });
|
|
403
424
|
const params = {
|
|
404
425
|
name: cron.name,
|
|
@@ -411,30 +432,32 @@ let CronService = class CronService {
|
|
|
411
432
|
return;
|
|
412
433
|
}
|
|
413
434
|
this.logger.info(`[panel][开始执行任务] 参数: ${JSON.stringify(params)}`);
|
|
414
|
-
let { id, command,
|
|
415
|
-
const uniqPath =
|
|
435
|
+
let { id, command, log_name } = cron;
|
|
436
|
+
const uniqPath = log_name === '/dev/null' || !log_name
|
|
437
|
+
? await (0, util_1.getUniqPath)(command, `${id}`)
|
|
438
|
+
: log_name;
|
|
416
439
|
const logTime = (0, dayjs_1.default)().format('YYYY-MM-DD-HH-mm-ss-SSS');
|
|
417
440
|
const logDirPath = path_1.default.resolve(config_1.default.logPath, `${uniqPath}`);
|
|
418
|
-
|
|
419
|
-
await promises_1.default.mkdir(logDirPath, { recursive: true });
|
|
420
|
-
}
|
|
441
|
+
await promises_1.default.mkdir(logDirPath, { recursive: true });
|
|
421
442
|
const logPath = `${uniqPath}/${logTime}.log`;
|
|
422
443
|
const absolutePath = path_1.default.resolve(config_1.default.logPath, `${logPath}`);
|
|
423
444
|
const cp = (0, cross_spawn_1.spawn)(`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron, true)}`, { shell: '/bin/bash' });
|
|
424
445
|
await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.running, pid: cp.pid, log_path: logPath }, { where: { id } });
|
|
425
446
|
cp.stdout.on('data', async (data) => {
|
|
426
|
-
await
|
|
447
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, data.toString());
|
|
427
448
|
});
|
|
428
449
|
cp.stderr.on('data', async (data) => {
|
|
429
450
|
this.logger.info('[panel][执行任务失败] 命令: %s, 错误信息: %j', command, data.toString());
|
|
430
|
-
await
|
|
451
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, data.toString());
|
|
431
452
|
});
|
|
432
453
|
cp.on('error', async (err) => {
|
|
433
454
|
this.logger.error('[panel][创建任务失败] 命令: %s, 错误信息: %j', command, err);
|
|
434
|
-
await
|
|
455
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, JSON.stringify(err));
|
|
435
456
|
});
|
|
436
457
|
cp.on('exit', async (code) => {
|
|
437
458
|
this.logger.info('[panel][执行任务结束] 参数: %s, 退出码: %j', JSON.stringify(params), code);
|
|
459
|
+
// Close the stream after task completion
|
|
460
|
+
await logStreamManager_1.logStreamManager.closeStream(absolutePath);
|
|
438
461
|
await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id } });
|
|
439
462
|
resolve(Object.assign(Object.assign({}, params), { pid: cp.pid, code }));
|
|
440
463
|
});
|
|
@@ -467,6 +490,9 @@ let CronService = class CronService {
|
|
|
467
490
|
if (!doc) {
|
|
468
491
|
return '';
|
|
469
492
|
}
|
|
493
|
+
if (doc.log_name === '/dev/null') {
|
|
494
|
+
return '日志设置为忽略';
|
|
495
|
+
}
|
|
470
496
|
const absolutePath = path_1.default.resolve(config_1.default.logPath, `${doc.log_path}`);
|
|
471
497
|
const logFileExist = doc.log_path && (await (0, util_1.fileExist)(absolutePath));
|
|
472
498
|
if (logFileExist) {
|
|
@@ -476,7 +502,7 @@ let CronService = class CronService {
|
|
|
476
502
|
return typeof doc.status === 'number' &&
|
|
477
503
|
[cron_1.CrontabStatus.queued, cron_1.CrontabStatus.running].includes(doc.status)
|
|
478
504
|
? '运行中...'
|
|
479
|
-
: '
|
|
505
|
+
: '日志不存在...';
|
|
480
506
|
}
|
|
481
507
|
}
|
|
482
508
|
async logs(id) {
|
|
@@ -492,7 +518,7 @@ let CronService = class CronService {
|
|
|
492
518
|
return (await Promise.all(files.map(async (x) => ({
|
|
493
519
|
filename: x,
|
|
494
520
|
directory: relativeDir.replace(config_1.default.logPath, ''),
|
|
495
|
-
time: (await promises_1.default.lstat(`${dir}/${x}`)).
|
|
521
|
+
time: (await promises_1.default.lstat(`${dir}/${x}`)).birthtimeMs,
|
|
496
522
|
})))).sort((a, b) => b.time - a.time);
|
|
497
523
|
}
|
|
498
524
|
else {
|
|
@@ -505,6 +531,10 @@ let CronService = class CronService {
|
|
|
505
531
|
command = `${const_1.TASK_PREFIX}${tab.command}`;
|
|
506
532
|
}
|
|
507
533
|
let commandVariable = `real_time=${Boolean(realTime)} no_tee=true ID=${tab.id} `;
|
|
534
|
+
// Only include log_name if it has a truthy value to avoid passing null/undefined to shell
|
|
535
|
+
if (tab.log_name) {
|
|
536
|
+
commandVariable += `log_name=${tab.log_name} `;
|
|
537
|
+
}
|
|
508
538
|
if (tab.task_before) {
|
|
509
539
|
commandVariable += `task_before='${tab.task_before
|
|
510
540
|
.replace(/'/g, "'\\''")
|
|
@@ -544,7 +574,11 @@ let CronService = class CronService {
|
|
|
544
574
|
await cron_1.CrontabModel.update({ saved: true }, { where: {} });
|
|
545
575
|
}
|
|
546
576
|
importCrontab() {
|
|
547
|
-
(0, child_process_1.exec)('crontab -l', (error, stdout
|
|
577
|
+
(0, child_process_1.exec)('crontab -l', (error, stdout) => {
|
|
578
|
+
if (error) {
|
|
579
|
+
const errorMsg = error.message || String(error);
|
|
580
|
+
this.logger.error('[crontab] Failed to read system crontab:', errorMsg);
|
|
581
|
+
}
|
|
548
582
|
const lines = stdout.split('\n');
|
|
549
583
|
const namePrefix = new Date().getTime();
|
|
550
584
|
lines.reverse().forEach(async (line, index) => {
|
|
@@ -554,7 +588,7 @@ let CronService = class CronService {
|
|
|
554
588
|
const schedule = line.replace(command, '').trim();
|
|
555
589
|
if (command &&
|
|
556
590
|
schedule &&
|
|
557
|
-
cron_parser_1.
|
|
591
|
+
cron_parser_1.CronExpressionParser.parse(schedule).hasNext()) {
|
|
558
592
|
const name = namePrefix + '_' + index;
|
|
559
593
|
const _crontab = await cron_1.CrontabModel.findOne({
|
|
560
594
|
where: { command, schedule },
|
|
@@ -86,23 +86,24 @@ let DependenceService = class DependenceService {
|
|
|
86
86
|
await dependence_1.DependenceModel.destroy({ where: { id: ids } });
|
|
87
87
|
}
|
|
88
88
|
async dependencies({ searchValue, type, status, }, sort = [], query = {}) {
|
|
89
|
-
let condition =
|
|
89
|
+
let condition = query;
|
|
90
|
+
if (dependence_1.DependenceTypes[type]) {
|
|
91
|
+
condition.type = dependence_1.DependenceTypes[type];
|
|
92
|
+
}
|
|
90
93
|
if (status) {
|
|
91
94
|
condition.status = status.split(',').map(Number);
|
|
92
95
|
}
|
|
93
96
|
if (searchValue) {
|
|
94
97
|
const encodeText = encodeURI(searchValue);
|
|
95
|
-
|
|
98
|
+
condition.name = {
|
|
96
99
|
[sequelize_1.Op.or]: [
|
|
97
100
|
{ [sequelize_1.Op.like]: `%${searchValue}%` },
|
|
98
101
|
{ [sequelize_1.Op.like]: `%${encodeText}%` },
|
|
99
102
|
],
|
|
100
103
|
};
|
|
101
|
-
condition = Object.assign(Object.assign({}, condition), { name: reg });
|
|
102
104
|
}
|
|
103
105
|
try {
|
|
104
|
-
|
|
105
|
-
return result;
|
|
106
|
+
return await this.find(condition, sort);
|
|
106
107
|
}
|
|
107
108
|
catch (error) {
|
|
108
109
|
throw error;
|
|
@@ -15,12 +15,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const groupBy_1 = __importDefault(require("lodash/groupBy"));
|
|
19
|
+
const sequelize_1 = require("sequelize");
|
|
18
20
|
const typedi_1 = require("typedi");
|
|
19
21
|
const winston_1 = __importDefault(require("winston"));
|
|
20
22
|
const config_1 = __importDefault(require("../config"));
|
|
21
23
|
const env_1 = require("../data/env");
|
|
22
|
-
const groupBy_1 = __importDefault(require("lodash/groupBy"));
|
|
23
|
-
const sequelize_1 = require("sequelize");
|
|
24
24
|
const utils_1 = require("../shared/utils");
|
|
25
25
|
let EnvService = class EnvService {
|
|
26
26
|
constructor(logger) {
|
|
@@ -128,6 +128,7 @@ let EnvService = class EnvService {
|
|
|
128
128
|
}
|
|
129
129
|
try {
|
|
130
130
|
const result = await this.find(condition, [
|
|
131
|
+
['isPinned', 'DESC'],
|
|
131
132
|
['position', 'DESC'],
|
|
132
133
|
['createdAt', 'ASC'],
|
|
133
134
|
]);
|
|
@@ -163,6 +164,12 @@ let EnvService = class EnvService {
|
|
|
163
164
|
await env_1.EnvModel.update({ name }, { where: { id: ids } });
|
|
164
165
|
await this.set_envs();
|
|
165
166
|
}
|
|
167
|
+
async pin(ids) {
|
|
168
|
+
await env_1.EnvModel.update({ isPinned: 1 }, { where: { id: ids } });
|
|
169
|
+
}
|
|
170
|
+
async unPin(ids) {
|
|
171
|
+
await env_1.EnvModel.update({ isPinned: 0 }, { where: { id: ids } });
|
|
172
|
+
}
|
|
166
173
|
async set_envs() {
|
|
167
174
|
const envs = await this.envs('', {
|
|
168
175
|
name: { [sequelize_1.Op.not]: null },
|
|
@@ -454,15 +454,27 @@ let NotificationService = class NotificationService {
|
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
async lark() {
|
|
457
|
-
let { larkKey } = this.params;
|
|
457
|
+
let { larkKey, larkSecret } = this.params;
|
|
458
458
|
if (!larkKey.startsWith('http')) {
|
|
459
459
|
larkKey = `https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`;
|
|
460
460
|
}
|
|
461
|
+
const body = {
|
|
462
|
+
msg_type: 'text',
|
|
463
|
+
content: { text: `${this.title}\n\n${this.content}` },
|
|
464
|
+
};
|
|
465
|
+
// Add signature if secret is provided
|
|
466
|
+
// Note: Feishu's signature algorithm uses timestamp+"\n"+secret as the HMAC key
|
|
467
|
+
// and signs an empty message, which differs from typical HMAC usage
|
|
468
|
+
if (larkSecret) {
|
|
469
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
470
|
+
const stringToSign = `${timestamp}\n${larkSecret}`;
|
|
471
|
+
const hmac = crypto_1.default.createHmac('sha256', stringToSign);
|
|
472
|
+
const sign = hmac.digest('base64');
|
|
473
|
+
body.timestamp = timestamp;
|
|
474
|
+
body.sign = sign;
|
|
475
|
+
}
|
|
461
476
|
try {
|
|
462
|
-
const res = await http_1.httpClient.post(larkKey, Object.assign(Object.assign({}, this.gotOption), { json: {
|
|
463
|
-
msg_type: 'text',
|
|
464
|
-
content: { text: `${this.title}\n\n${this.content}` },
|
|
465
|
-
}, headers: { 'Content-Type': 'application/json' } }));
|
|
477
|
+
const res = await http_1.httpClient.post(larkKey, Object.assign(Object.assign({}, this.gotOption), { json: body, headers: { 'Content-Type': 'application/json' } }));
|
|
466
478
|
if (res.StatusCode === 0 || res.code === 0) {
|
|
467
479
|
return true;
|
|
468
480
|
}
|
|
@@ -87,7 +87,7 @@ let ScheduleService = class ScheduleService {
|
|
|
87
87
|
}
|
|
88
88
|
async createCronTask({ id = 0, command, name, schedule = '', runOrigin }, callbacks, runImmediately = false) {
|
|
89
89
|
const _id = this.formatId(id);
|
|
90
|
-
this.logger.info('[panel][创建cron任务]
|
|
90
|
+
this.logger.info('[panel][创建cron任务] 任务ID: %s, cron: %s, 任务名: %s, 执行命令: %s', _id, schedule, name, command);
|
|
91
91
|
this.scheduleStacks.set(_id, node_schedule_1.default.scheduleJob(_id, schedule, async () => {
|
|
92
92
|
this.runTask(command, callbacks, {
|
|
93
93
|
name,
|
|
@@ -110,7 +110,7 @@ let ScheduleService = class ScheduleService {
|
|
|
110
110
|
async cancelCronTask({ id = 0, name }) {
|
|
111
111
|
var _a;
|
|
112
112
|
const _id = this.formatId(id);
|
|
113
|
-
this.logger.info('[panel][取消定时任务]
|
|
113
|
+
this.logger.info('[panel][取消定时任务] 任务名: %s', name);
|
|
114
114
|
if (this.scheduleStacks.has(_id)) {
|
|
115
115
|
(_a = this.scheduleStacks.get(_id)) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
116
116
|
this.scheduleStacks.delete(_id);
|
|
@@ -118,7 +118,7 @@ let ScheduleService = class ScheduleService {
|
|
|
118
118
|
}
|
|
119
119
|
async createIntervalTask({ id = 0, command, name = '', runOrigin }, schedule, runImmediately = true, callbacks) {
|
|
120
120
|
const _id = this.formatId(id);
|
|
121
|
-
this.logger.info('[panel][创建interval任务]
|
|
121
|
+
this.logger.info('[panel][创建interval任务] 任务ID: %s, 任务名: %s, 执行命令: %s', _id, name, command);
|
|
122
122
|
const task = new toad_scheduler_1.Task(name, () => {
|
|
123
123
|
this.runTask(command, callbacks, {
|
|
124
124
|
name,
|
|
@@ -142,7 +142,7 @@ let ScheduleService = class ScheduleService {
|
|
|
142
142
|
}
|
|
143
143
|
async cancelIntervalTask({ id = 0, name }) {
|
|
144
144
|
const _id = this.formatId(id);
|
|
145
|
-
this.logger.info('[panel][取消interval任务]
|
|
145
|
+
this.logger.info('[panel][取消interval任务] 任务ID: %s, 任务名: %s', _id, name);
|
|
146
146
|
this.intervalSchedule.removeById(_id);
|
|
147
147
|
}
|
|
148
148
|
formatId(id) {
|
|
@@ -40,16 +40,15 @@ let SshKeyService = class SshKeyService {
|
|
|
40
40
|
config = await promises_1.default.readFile(this.sshConfigFilePath, { encoding: 'utf-8' });
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
|
-
await (0, utils_1.writeFileWithLock)(this.sshConfigFilePath, '');
|
|
43
|
+
await (0, utils_1.writeFileWithLock)(this.sshConfigFilePath, '', { mode: '600' });
|
|
44
44
|
}
|
|
45
45
|
if (!config.includes(this.sshConfigHeader)) {
|
|
46
|
-
await (0, utils_1.writeFileWithLock)(this.sshConfigFilePath, `${this.sshConfigHeader}\n\n${config}
|
|
46
|
+
await (0, utils_1.writeFileWithLock)(this.sshConfigFilePath, `${this.sshConfigHeader}\n\n${config}`, { mode: '600' });
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
async generatePrivateKeyFile(alias, key) {
|
|
50
50
|
try {
|
|
51
51
|
await (0, utils_1.writeFileWithLock)(path_1.default.join(this.sshPath, alias), `${key}${os_1.default.EOL}`, {
|
|
52
|
-
encoding: 'utf8',
|
|
53
52
|
mode: '400',
|
|
54
53
|
});
|
|
55
54
|
}
|
|
@@ -74,7 +73,10 @@ let SshKeyService = class SshKeyService {
|
|
|
74
73
|
? ` ProxyCommand nc -v -x ${proxy} %h %p 2>/dev/null\n`
|
|
75
74
|
: '';
|
|
76
75
|
const config = `Host ${alias}\n Hostname ${host}\n IdentityFile ${path_1.default.join(this.sshPath, alias)}\n StrictHostKeyChecking no\n${proxyStr}`;
|
|
77
|
-
await (0, utils_1.writeFileWithLock)(`${path_1.default.join(this.sshPath, `${alias}.config`)}`, config
|
|
76
|
+
await (0, utils_1.writeFileWithLock)(`${path_1.default.join(this.sshPath, `${alias}.config`)}`, config, {
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
mode: '600',
|
|
79
|
+
});
|
|
78
80
|
}
|
|
79
81
|
async removeSshConfig(alias) {
|
|
80
82
|
try {
|
|
@@ -105,6 +107,24 @@ let SshKeyService = class SshKeyService {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
}
|
|
110
|
+
async addGlobalSSHKey(key, alias) {
|
|
111
|
+
await this.generatePrivateKeyFile(`~global_${alias}`, key);
|
|
112
|
+
// Create a global SSH config entry that matches all hosts
|
|
113
|
+
// This allows the key to be used for any Git repository
|
|
114
|
+
await this.generateGlobalSshConfig(`~global_${alias}`);
|
|
115
|
+
}
|
|
116
|
+
async removeGlobalSSHKey(alias) {
|
|
117
|
+
await this.removePrivateKeyFile(`~global_${alias}`);
|
|
118
|
+
await this.removeSshConfig(`~global_${alias}`);
|
|
119
|
+
}
|
|
120
|
+
async generateGlobalSshConfig(alias) {
|
|
121
|
+
// Create a config that matches all hosts, making this key globally available
|
|
122
|
+
const config = `Host *\n IdentityFile ${path_1.default.join(this.sshPath, alias)}\n StrictHostKeyChecking no\n`;
|
|
123
|
+
await (0, utils_1.writeFileWithLock)(`${path_1.default.join(this.sshPath, `${alias}.config`)}`, config, {
|
|
124
|
+
encoding: 'utf8',
|
|
125
|
+
mode: '600',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
108
128
|
};
|
|
109
129
|
SshKeyService = __decorate([
|
|
110
130
|
(0, typedi_1.Service)(),
|
|
@@ -53,6 +53,7 @@ const const_1 = require("../config/const");
|
|
|
53
53
|
const subscription_2 = require("../config/subscription");
|
|
54
54
|
const cron_1 = require("../data/cron");
|
|
55
55
|
const cron_2 = __importDefault(require("./cron"));
|
|
56
|
+
const logStreamManager_1 = require("../shared/logStreamManager");
|
|
56
57
|
let SubscriptionService = class SubscriptionService {
|
|
57
58
|
constructor(logger, scheduleService, sockService, sshKeyService, crontabService) {
|
|
58
59
|
this.logger = logger;
|
|
@@ -129,7 +130,7 @@ let SubscriptionService = class SubscriptionService {
|
|
|
129
130
|
let beforeStr = '';
|
|
130
131
|
try {
|
|
131
132
|
if (doc.sub_before) {
|
|
132
|
-
await
|
|
133
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, `\n## 执行before命令...\n\n`);
|
|
133
134
|
beforeStr = await (0, util_1.promiseExec)(doc.sub_before);
|
|
134
135
|
}
|
|
135
136
|
}
|
|
@@ -138,7 +139,7 @@ let SubscriptionService = class SubscriptionService {
|
|
|
138
139
|
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
|
139
140
|
}
|
|
140
141
|
if (beforeStr) {
|
|
141
|
-
await
|
|
142
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, `${beforeStr}\n`);
|
|
142
143
|
}
|
|
143
144
|
},
|
|
144
145
|
onStart: async (cp, startTime) => {
|
|
@@ -153,7 +154,7 @@ let SubscriptionService = class SubscriptionService {
|
|
|
153
154
|
let afterStr = '';
|
|
154
155
|
try {
|
|
155
156
|
if (sub.sub_after) {
|
|
156
|
-
await
|
|
157
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, `\n\n## 执行after命令...\n\n`);
|
|
157
158
|
afterStr = await (0, util_1.promiseExec)(sub.sub_after);
|
|
158
159
|
}
|
|
159
160
|
}
|
|
@@ -162,9 +163,11 @@ let SubscriptionService = class SubscriptionService {
|
|
|
162
163
|
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
|
163
164
|
}
|
|
164
165
|
if (afterStr) {
|
|
165
|
-
await
|
|
166
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, `${afterStr}\n`);
|
|
166
167
|
}
|
|
167
|
-
await
|
|
168
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, `\n## 执行结束... ${endTime.format('YYYY-MM-DD HH:mm:ss')} 耗时 ${diff} 秒${const_1.LOG_END_SYMBOL}`);
|
|
169
|
+
// Close the stream after task completion
|
|
170
|
+
await logStreamManager_1.logStreamManager.closeStream(absolutePath);
|
|
168
171
|
await subscription_1.SubscriptionModel.update({ status: subscription_1.SubscriptionStatus.idle, pid: undefined }, { where: { id: sub.id } });
|
|
169
172
|
this.sockService.sendMessage({
|
|
170
173
|
type: 'runSubscriptionEnd',
|
|
@@ -175,12 +178,12 @@ let SubscriptionService = class SubscriptionService {
|
|
|
175
178
|
onError: async (message) => {
|
|
176
179
|
const sub = await this.getDb({ id: doc.id });
|
|
177
180
|
const absolutePath = await (0, util_1.handleLogPath)(sub.log_path);
|
|
178
|
-
await
|
|
181
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, `\n${message}`);
|
|
179
182
|
},
|
|
180
183
|
onLog: async (message) => {
|
|
181
184
|
const sub = await this.getDb({ id: doc.id });
|
|
182
185
|
const absolutePath = await (0, util_1.handleLogPath)(sub.log_path);
|
|
183
|
-
await
|
|
186
|
+
await logStreamManager_1.logStreamManager.write(absolutePath, `\n${message}`);
|
|
184
187
|
},
|
|
185
188
|
};
|
|
186
189
|
}
|
|
@@ -317,7 +320,7 @@ let SubscriptionService = class SubscriptionService {
|
|
|
317
320
|
return (await Promise.all(files.map(async (x) => ({
|
|
318
321
|
filename: x,
|
|
319
322
|
directory: relativeDir.replace(config_1.default.logPath, ''),
|
|
320
|
-
time: (await promises_1.default.lstat(`${dir}/${x}`)).
|
|
323
|
+
time: (await promises_1.default.lstat(`${dir}/${x}`)).birthtimeMs,
|
|
321
324
|
})))).sort((a, b) => b.time - a.time);
|
|
322
325
|
}
|
|
323
326
|
}
|
|
@@ -308,11 +308,12 @@ let SystemService = class SystemService {
|
|
|
308
308
|
return { code: 400, message: '通知发送失败,请检查系统设置/通知配置' };
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
|
-
async run({ command }, callback) {
|
|
311
|
+
async run({ command, logPath }, callback) {
|
|
312
312
|
if (!command.startsWith(const_1.TASK_COMMAND)) {
|
|
313
313
|
command = `${const_1.TASK_COMMAND} ${command}`;
|
|
314
314
|
}
|
|
315
|
-
|
|
315
|
+
const logPathPrefix = logPath ? `real_log_path=${logPath}` : '';
|
|
316
|
+
this.scheduleService.runTask(`${logPathPrefix} real_time=true ${command}`, callback, {
|
|
316
317
|
command,
|
|
317
318
|
id: command.replace(/ /g, '-'),
|
|
318
319
|
runOrigin: 'system',
|
|
@@ -413,6 +414,21 @@ let SystemService = class SystemService {
|
|
|
413
414
|
return { code: 400, message: '设置时区失败' };
|
|
414
415
|
}
|
|
415
416
|
}
|
|
417
|
+
async updateGlobalSshKey(info) {
|
|
418
|
+
const oDoc = await this.getSystemConfig();
|
|
419
|
+
const result = await this.updateAuthDb(Object.assign(Object.assign({}, oDoc), { info: Object.assign(Object.assign({}, oDoc.info), info) }));
|
|
420
|
+
// Apply the global SSH key
|
|
421
|
+
const SshKeyService = require('./sshKey').default;
|
|
422
|
+
const Container = require('typedi').Container;
|
|
423
|
+
const sshKeyService = Container.get(SshKeyService);
|
|
424
|
+
if (info.globalSshKey) {
|
|
425
|
+
await sshKeyService.addGlobalSSHKey(info.globalSshKey, 'global');
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
await sshKeyService.removeGlobalSSHKey('global');
|
|
429
|
+
}
|
|
430
|
+
return { code: 200, data: result };
|
|
431
|
+
}
|
|
416
432
|
async cleanDependence(type) {
|
|
417
433
|
if (!type || !['node', 'python3'].includes(type)) {
|
|
418
434
|
return { code: 400, message: '参数错误' };
|
|
@@ -79,9 +79,17 @@ let UserService = class UserService {
|
|
|
79
79
|
expiresIn: config_1.default.jwt.expiresIn || expiration,
|
|
80
80
|
algorithm: 'HS384',
|
|
81
81
|
});
|
|
82
|
+
const tokenInfo = {
|
|
83
|
+
value: token,
|
|
84
|
+
timestamp,
|
|
85
|
+
ip,
|
|
86
|
+
address,
|
|
87
|
+
platform: req.platform,
|
|
88
|
+
};
|
|
89
|
+
const updatedTokens = this.addTokenToList(tokens, req.platform, tokenInfo);
|
|
82
90
|
await this.updateAuthInfo(content, {
|
|
83
91
|
token,
|
|
84
|
-
tokens:
|
|
92
|
+
tokens: updatedTokens,
|
|
85
93
|
lastlogon: timestamp,
|
|
86
94
|
retries: 0,
|
|
87
95
|
lastip: ip,
|
|
@@ -146,11 +154,23 @@ let UserService = class UserService {
|
|
|
146
154
|
}
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
|
-
async logout(platform) {
|
|
157
|
+
async logout(platform, tokenValue) {
|
|
158
|
+
if (!platform || !tokenValue) {
|
|
159
|
+
this.logger.warn('Invalid logout parameters - empty platform or token');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
150
162
|
const authInfo = await this.getAuthInfo();
|
|
163
|
+
// Verify the token exists before attempting to remove it
|
|
164
|
+
const tokenExists = this.findTokenInList(authInfo.tokens, platform, tokenValue);
|
|
165
|
+
if (!tokenExists && authInfo.token !== tokenValue) {
|
|
166
|
+
// Token not found, but don't throw error - user may have already logged out
|
|
167
|
+
this.logger.info(`Logout attempted for non-existent token on platform: ${platform}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const updatedTokens = this.removeTokenFromList(authInfo.tokens, platform, tokenValue);
|
|
151
171
|
await this.updateAuthInfo(authInfo, {
|
|
152
|
-
token: '',
|
|
153
|
-
tokens:
|
|
172
|
+
token: authInfo.token === tokenValue ? '' : authInfo.token,
|
|
173
|
+
tokens: updatedTokens,
|
|
154
174
|
});
|
|
155
175
|
}
|
|
156
176
|
async getLoginLog() {
|
|
@@ -299,6 +319,65 @@ let UserService = class UserService {
|
|
|
299
319
|
return { code: 400, message: '通知发送失败,请检查参数' };
|
|
300
320
|
}
|
|
301
321
|
}
|
|
322
|
+
normalizeTokens(tokens) {
|
|
323
|
+
const normalized = {};
|
|
324
|
+
for (const [platform, value] of Object.entries(tokens)) {
|
|
325
|
+
if (typeof value === 'string') {
|
|
326
|
+
// Legacy format: convert string token to TokenInfo array
|
|
327
|
+
if (value) {
|
|
328
|
+
normalized[platform] = [
|
|
329
|
+
{
|
|
330
|
+
value,
|
|
331
|
+
timestamp: Date.now(),
|
|
332
|
+
ip: '',
|
|
333
|
+
address: '',
|
|
334
|
+
platform,
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
normalized[platform] = [];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Already in new format
|
|
344
|
+
normalized[platform] = value || [];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return normalized;
|
|
348
|
+
}
|
|
349
|
+
addTokenToList(tokens, platform, tokenInfo, maxTokensPerPlatform = config_1.default.maxTokensPerPlatform) {
|
|
350
|
+
// Validate maxTokensPerPlatform parameter
|
|
351
|
+
if (!Number.isInteger(maxTokensPerPlatform) || maxTokensPerPlatform < 1) {
|
|
352
|
+
this.logger.warn(`Invalid maxTokensPerPlatform value: ${maxTokensPerPlatform}, using default`);
|
|
353
|
+
maxTokensPerPlatform = config_1.default.maxTokensPerPlatform;
|
|
354
|
+
}
|
|
355
|
+
const normalized = this.normalizeTokens(tokens);
|
|
356
|
+
if (!normalized[platform]) {
|
|
357
|
+
normalized[platform] = [];
|
|
358
|
+
}
|
|
359
|
+
// Add new token
|
|
360
|
+
normalized[platform].unshift(tokenInfo);
|
|
361
|
+
// Limit the number of active tokens per platform
|
|
362
|
+
if (normalized[platform].length > maxTokensPerPlatform) {
|
|
363
|
+
normalized[platform] = normalized[platform].slice(0, maxTokensPerPlatform);
|
|
364
|
+
}
|
|
365
|
+
return normalized;
|
|
366
|
+
}
|
|
367
|
+
removeTokenFromList(tokens, platform, tokenValue) {
|
|
368
|
+
const normalized = this.normalizeTokens(tokens);
|
|
369
|
+
if (normalized[platform]) {
|
|
370
|
+
normalized[platform] = normalized[platform].filter((t) => t.value !== tokenValue);
|
|
371
|
+
}
|
|
372
|
+
return normalized;
|
|
373
|
+
}
|
|
374
|
+
findTokenInList(tokens, platform, tokenValue) {
|
|
375
|
+
const normalized = this.normalizeTokens(tokens);
|
|
376
|
+
if (normalized[platform]) {
|
|
377
|
+
return normalized[platform].find((t) => t.value === tokenValue);
|
|
378
|
+
}
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
302
381
|
async resetAuthInfo(info) {
|
|
303
382
|
const { retries, twoFactorActivated, password, username } = info;
|
|
304
383
|
const authInfo = await this.getAuthInfo();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isValidToken = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Validates if a token exists in the authentication info.
|
|
6
|
+
* Supports both legacy string tokens and new TokenInfo array format.
|
|
7
|
+
*
|
|
8
|
+
* @param authInfo - The authentication information
|
|
9
|
+
* @param headerToken - The token to validate
|
|
10
|
+
* @param platform - The platform (desktop, mobile)
|
|
11
|
+
* @returns true if the token is valid, false otherwise
|
|
12
|
+
*/
|
|
13
|
+
function isValidToken(authInfo, headerToken, platform) {
|
|
14
|
+
if (!authInfo || !headerToken) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const { token = '', tokens = {} } = authInfo;
|
|
18
|
+
// Check legacy token field
|
|
19
|
+
if (headerToken === token) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
// Check platform-specific tokens (support both legacy string and new TokenInfo[] format)
|
|
23
|
+
const platformTokens = tokens[platform];
|
|
24
|
+
// Handle null/undefined platformTokens
|
|
25
|
+
if (platformTokens === null || platformTokens === undefined) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (typeof platformTokens === 'string') {
|
|
29
|
+
// Legacy format: single string token
|
|
30
|
+
return headerToken === platformTokens;
|
|
31
|
+
}
|
|
32
|
+
else if (Array.isArray(platformTokens)) {
|
|
33
|
+
// New format: array of TokenInfo objects
|
|
34
|
+
return platformTokens.some((t) => t && t.value === headerToken);
|
|
35
|
+
}
|
|
36
|
+
// Unexpected type - log warning and reject
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
exports.isValidToken = isValidToken;
|
|
40
|
+
//# sourceMappingURL=auth.js.map
|