@whyour/qinglong 2.20.2-3 → 2.21.0-2

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.
Files changed (227) hide show
  1. package/.env.example +5 -0
  2. package/AGENTS.md +43 -0
  3. package/CLAUDE.md +43 -0
  4. package/README-en.md +11 -0
  5. package/README.md +11 -0
  6. package/docker/Dockerfile +51 -69
  7. package/docker/Dockerfile.310 +94 -0
  8. package/docker/Dockerfile.debian +120 -0
  9. package/docker/{310.Dockerfile → Dockerfile.debian310} +13 -5
  10. package/docker/docker-entrypoint.sh +95 -3
  11. package/docs/PROJECT_ARCHITECTURE.md +550 -0
  12. package/package.json +28 -25
  13. package/sample/config.sample.sh +12 -1
  14. package/sample/notify.js +81 -9
  15. package/sample/notify.py +54 -11
  16. package/shell/api.sh +44 -20
  17. package/shell/bot.sh +30 -16
  18. package/shell/check.sh +21 -19
  19. package/shell/lang/en.sh +105 -0
  20. package/shell/lang/zh.sh +105 -0
  21. package/shell/otask.sh +117 -28
  22. package/shell/preload/client.js +20 -2
  23. package/shell/preload/esm-loader.mjs +41 -0
  24. package/shell/preload/sitecustomize.js +36 -0
  25. package/shell/pub.sh +8 -8
  26. package/shell/rmlog.sh +5 -5
  27. package/shell/share.sh +49 -30
  28. package/shell/start.sh +40 -27
  29. package/shell/task.sh +10 -2
  30. package/shell/update.sh +22 -22
  31. package/static/build/api/config.js +17 -7
  32. package/static/build/api/cron.js +40 -3
  33. package/static/build/api/dashboard.js +344 -0
  34. package/static/build/api/env.js +40 -2
  35. package/static/build/api/index.js +2 -0
  36. package/static/build/api/log.js +5 -4
  37. package/static/build/api/script.js +34 -9
  38. package/static/build/api/system.js +16 -1
  39. package/static/build/api/user.js +6 -5
  40. package/static/build/app.js +3 -0
  41. package/static/build/config/const.js +7 -6
  42. package/static/build/config/container.js +12 -0
  43. package/static/build/config/grpcCerts.js +150 -0
  44. package/static/build/config/index.js +6 -4
  45. package/static/build/config/util.js +69 -18
  46. package/static/build/data/cron.js +3 -1
  47. package/static/build/data/cronStats.js +59 -0
  48. package/static/build/data/env.js +2 -0
  49. package/static/build/data/notify.js +11 -1
  50. package/static/build/data/runningInstance.js +57 -0
  51. package/static/build/data/sock.js +1 -0
  52. package/static/build/loaders/app.js +1 -1
  53. package/static/build/loaders/db.js +6 -0
  54. package/static/build/loaders/deps.js +1 -22
  55. package/static/build/loaders/express.js +2 -1
  56. package/static/build/loaders/initData.js +19 -2
  57. package/static/build/loaders/initFile.js +2 -0
  58. package/static/build/loaders/initTask.js +3 -2
  59. package/static/build/schedule/api.js +1 -1
  60. package/static/build/schedule/client.js +9 -1
  61. package/static/build/schedule/health.js +1 -1
  62. package/static/build/services/config.js +19 -5
  63. package/static/build/services/cron.js +126 -13
  64. package/static/build/services/dependence.js +19 -18
  65. package/static/build/services/env.js +31 -3
  66. package/static/build/services/grpc.js +32 -6
  67. package/static/build/services/http.js +33 -18
  68. package/static/build/services/notify.js +45 -8
  69. package/static/build/services/open.js +2 -1
  70. package/static/build/services/sshKey.js +6 -3
  71. package/static/build/services/subscription.js +2 -1
  72. package/static/build/services/system.js +34 -9
  73. package/static/build/services/user.js +36 -13
  74. package/static/build/shared/i18n.js +145 -0
  75. package/static/build/shared/pLimit.js +15 -3
  76. package/static/build/shared/runCron.js +5 -0
  77. package/static/build/validation/schedule.js +1 -0
  78. package/static/dist/1089.0ecc5383.async.js +1 -0
  79. package/static/dist/1147.c420132e.async.js +1 -0
  80. package/static/dist/1192.4e5740f2.async.js +1 -0
  81. package/static/dist/1214.c6469b53.async.js +1 -0
  82. package/static/dist/134.54df9e38.async.js +1 -0
  83. package/static/dist/1352.96a77e1d.async.js +1 -0
  84. package/static/dist/{1765.d8e002d7.async.js → 1765.5ac01a93.async.js} +1 -1
  85. package/static/dist/1836.f0b74cf1.async.js +1 -0
  86. package/static/dist/{1885.e0d00d2d.async.js → 1885.e1ea09f6.async.js} +1 -1
  87. package/static/dist/1967.3f3945d0.async.js +1 -0
  88. package/static/dist/{2096.383c1047.async.js → 2096.6d98fd12.async.js} +1 -1
  89. package/static/dist/2208.f7ba3dfa.async.js +1 -0
  90. package/static/dist/2286.164bb089.async.js +1 -0
  91. package/static/dist/2476.4e9b0992.async.js +1 -0
  92. package/static/dist/2537.2b262ee0.async.js +1 -0
  93. package/static/dist/{255.12f03ab2.async.js → 255.8a80b983.async.js} +1 -1
  94. package/static/dist/2635.d4c59d23.async.js +1 -0
  95. package/static/dist/2808.cdc0995c.async.js +1 -0
  96. package/static/dist/{2821.be3dc88e.async.js → 2821.651f31e5.async.js} +1 -1
  97. package/static/dist/2986.4c49eef7.async.js +1 -0
  98. package/static/dist/300.08ac9875.async.js +1 -0
  99. package/static/dist/{3191.70bc19db.async.js → 3191.63263871.async.js} +1 -1
  100. package/static/dist/3714.8d6dba9e.async.js +1 -0
  101. package/static/dist/3906.a5eee612.async.js +1 -0
  102. package/static/dist/{6541.a6d499de.async.js → 3948.4fe809fd.async.js} +1 -1
  103. package/static/dist/{5171.7fc6d0a2.async.js → 4573.16f19278.async.js} +1 -1
  104. package/static/dist/{4859.93e63ea6.async.js → 4859.6592cebb.async.js} +1 -1
  105. package/static/dist/4887.8e3ae573.async.js +1 -0
  106. package/static/dist/{4902.54ecbdb5.async.js → 4902.555f92ab.async.js} +1 -1
  107. package/static/dist/{4934.1ca6b6b0.async.js → 4934.f3539d08.async.js} +1 -1
  108. package/static/dist/5077.cbd111cd.async.js +1 -0
  109. package/static/dist/{6247.b550d996.async.js → 5157.7c5af144.async.js} +1 -1
  110. package/static/dist/{540.481d4708.async.js → 540.19ddf020.async.js} +1 -1
  111. package/static/dist/{5970.10ac4f16.async.js → 5970.d3a6e7bd.async.js} +1 -1
  112. package/static/dist/{6013.2d7bb12a.async.js → 6013.4e38de36.async.js} +1 -1
  113. package/static/dist/{6016.9c379049.async.js → 6016.1e6574b6.async.js} +1 -1
  114. package/static/dist/{6035.5889ddc7.async.js → 6035.32ebfad9.async.js} +1 -1
  115. package/static/dist/6339.ab305e96.async.js +1 -0
  116. package/static/dist/6569.55177f6a.async.js +1 -0
  117. package/static/dist/6610.c111af7e.async.js +1 -0
  118. package/static/dist/{3034.6413c38e.async.js → 6638.87a163d2.async.js} +1 -1
  119. package/static/dist/{6646.5fc37228.async.js → 6646.95e92533.async.js} +1 -1
  120. package/static/dist/6665.1d099ad1.async.js +1 -0
  121. package/static/dist/{7355.b7c0562f.async.js → 7355.f9f263d8.async.js} +1 -1
  122. package/static/dist/{7384.065ccae2.async.js → 7384.1bb449d5.async.js} +1 -1
  123. package/static/dist/7441.ee1bf2bb.async.js +1 -0
  124. package/static/dist/{7508.a31662a3.async.js → 7508.7dcee91c.async.js} +1 -1
  125. package/static/dist/{7802.6b73f16a.async.js → 7802.34255805.async.js} +1 -1
  126. package/static/dist/{7984.e6bb9378.async.js → 7984.594296a5.async.js} +1 -1
  127. package/static/dist/{5653.4fce7ce8.async.js → 8033.5cb31493.async.js} +1 -1
  128. package/static/dist/8147.2fb55202.async.js +1 -0
  129. package/static/dist/8187.48c130d6.async.js +1 -0
  130. package/static/dist/8495.d53e15ca.async.js +1 -0
  131. package/static/dist/8587.315b06c0.async.js +1 -0
  132. package/static/dist/8826.6879b271.async.js +1 -0
  133. package/static/dist/{2742.4852aac8.async.js → 8865.ed665d31.async.js} +1 -1
  134. package/static/dist/901.7ee5c6d3.async.js +1 -0
  135. package/static/dist/9271.231db2ce.async.js +1 -0
  136. package/static/dist/9323.a33f47da.async.js +1 -0
  137. package/static/dist/{9730.30083c91.async.js → 9730.801665a3.async.js} +1 -1
  138. package/static/dist/{9761.627ca3b5.async.js → 9761.360a19d8.async.js} +1 -1
  139. package/static/dist/index.html +2 -2
  140. package/static/dist/layouts__index.a7a2bfe0.async.js +1 -0
  141. package/static/dist/layouts__index.adf0692f.chunk.css +1 -0
  142. package/static/dist/preload_helper.c1c91aaa.js +1 -0
  143. package/static/dist/{src__pages__config__index.622b6ee8.async.js → src__pages__config__index.a22ff7dc.async.js} +1 -1
  144. package/static/dist/{src__pages__crontab__const.323d5124.async.js → src__pages__crontab__const.aba07deb.async.js} +1 -1
  145. package/static/dist/src__pages__crontab__detail.cf4a6aaf.async.js +1 -0
  146. package/static/dist/src__pages__crontab__index.5b67b1a2.async.js +1 -0
  147. package/static/dist/{src__pages__crontab__logModal.5e6a4bf2.async.js → src__pages__crontab__logModal.831d5e4b.async.js} +1 -1
  148. package/static/dist/src__pages__crontab__modal.f7041c7c.async.js +1 -0
  149. package/static/dist/src__pages__crontab__type.d7af36e5.async.js +1 -0
  150. package/static/dist/{src__pages__crontab__viewCreateModal.ffcf7a24.async.js → src__pages__crontab__viewCreateModal.4d589f66.async.js} +1 -1
  151. package/static/dist/{src__pages__crontab__viewManageModal.c2724575.async.js → src__pages__crontab__viewManageModal.0e317746.async.js} +1 -1
  152. package/static/dist/src__pages__dashboard__index.58177357.async.js +1 -0
  153. package/static/dist/src__pages__dependence__index.b7bbc795.async.js +1 -0
  154. package/static/dist/{src__pages__dependence__logModal.f123e2ac.async.js → src__pages__dependence__logModal.0681830b.async.js} +1 -1
  155. package/static/dist/src__pages__dependence__modal.11124896.async.js +1 -0
  156. package/static/dist/src__pages__env__editNameModal.5d264b25.async.js +1 -0
  157. package/static/dist/src__pages__env__index.9a565d2b.async.js +1 -0
  158. package/static/dist/src__pages__env__modal.7f2ef1bc.async.js +1 -0
  159. package/static/dist/src__pages__error__index.f156b45e.async.js +1 -0
  160. package/static/dist/src__pages__initialization__index.e96d4ba8.async.js +1 -0
  161. package/static/dist/src__pages__log__index.9c66639f.async.js +1 -0
  162. package/static/dist/{src__pages__login__index.cd6e3152.async.js → src__pages__login__index.8c813eb1.async.js} +1 -1
  163. package/static/dist/{src__pages__script__components__UnsupportedFilePreview__index.39074c68.async.js → src__pages__script__components__UnsupportedFilePreview__index.c347a13a.async.js} +1 -1
  164. package/static/dist/src__pages__script__editNameModal.3e7e100d.async.js +1 -0
  165. package/static/dist/src__pages__script__index.e869812f.async.js +1 -0
  166. package/static/dist/src__pages__script__renameModal.5e987ef5.async.js +1 -0
  167. package/static/dist/src__pages__script__saveModal.3f9d23d6.async.js +1 -0
  168. package/static/dist/src__pages__script__setting.a535793a.async.js +1 -0
  169. package/static/dist/src__pages__setting__appModal.251cd14f.async.js +1 -0
  170. package/static/dist/src__pages__setting__checkUpdate.34d773f2.async.js +1 -0
  171. package/static/dist/src__pages__setting__dependence.29f13e8c.async.js +1 -0
  172. package/static/dist/src__pages__setting__index.7a39d415.async.js +1 -0
  173. package/static/dist/src__pages__setting__notification.390fc905.async.js +1 -0
  174. package/static/dist/src__pages__setting__other.707cfafe.async.js +1 -0
  175. package/static/dist/src__pages__setting__security.598720a8.async.js +1 -0
  176. package/static/dist/src__pages__setting__systemLog.1a5c855f.async.js +1 -0
  177. package/static/dist/{src__pages__subscription__logModal.0caa7283.async.js → src__pages__subscription__logModal.3864b37f.async.js} +1 -1
  178. package/static/dist/src__pages__subscription__modal.3562c670.async.js +1 -0
  179. package/static/dist/{umi.ef8199a4.js → umi.97fb5a40.js} +1 -1
  180. package/version.yaml +33 -4
  181. package/sample/notify.py.save +0 -1010
  182. package/static/dist/105.85a5c47a.async.js +0 -1
  183. package/static/dist/1083.f86ce804.async.js +0 -1
  184. package/static/dist/1147.32f41a88.async.js +0 -1
  185. package/static/dist/1352.ab6da08e.async.js +0 -1
  186. package/static/dist/1690.f0290540.async.js +0 -1
  187. package/static/dist/1742.6cbe5aca.async.js +0 -1
  188. package/static/dist/2208.8e3a7325.async.js +0 -1
  189. package/static/dist/5312.74b95311.async.js +0 -1
  190. package/static/dist/5691.931f59c5.async.js +0 -1
  191. package/static/dist/6159.55cb068a.async.js +0 -1
  192. package/static/dist/7025.f4080d63.async.js +0 -1
  193. package/static/dist/739.6be5552a.async.js +0 -1
  194. package/static/dist/7571.4f6240b1.async.js +0 -1
  195. package/static/dist/786.59fc381c.async.js +0 -1
  196. package/static/dist/8317.c44c1ebd.async.js +0 -1
  197. package/static/dist/8826.0291edfd.async.js +0 -1
  198. package/static/dist/955.3c9481f7.async.js +0 -1
  199. package/static/dist/layouts__index.1fce90e0.chunk.css +0 -1
  200. package/static/dist/layouts__index.8dcf1576.async.js +0 -1
  201. package/static/dist/preload_helper.116a62f6.js +0 -1
  202. package/static/dist/src__pages__crontab__detail.b07f0c0a.async.js +0 -1
  203. package/static/dist/src__pages__crontab__index.2e2e1096.async.js +0 -1
  204. package/static/dist/src__pages__crontab__modal.4d8c2a22.async.js +0 -1
  205. package/static/dist/src__pages__crontab__type.db7c1858.async.js +0 -1
  206. package/static/dist/src__pages__dependence__index.3d87ceac.async.js +0 -1
  207. package/static/dist/src__pages__dependence__modal.631ffb5b.async.js +0 -1
  208. package/static/dist/src__pages__env__editNameModal.ff85ef8c.async.js +0 -1
  209. package/static/dist/src__pages__env__index.a0a2fece.async.js +0 -1
  210. package/static/dist/src__pages__env__modal.d1004662.async.js +0 -1
  211. package/static/dist/src__pages__error__index.1bc3c90b.async.js +0 -1
  212. package/static/dist/src__pages__initialization__index.8b1cbaf9.async.js +0 -1
  213. package/static/dist/src__pages__log__index.cf00c9af.async.js +0 -1
  214. package/static/dist/src__pages__script__editNameModal.53424d49.async.js +0 -1
  215. package/static/dist/src__pages__script__index.e65df827.async.js +0 -1
  216. package/static/dist/src__pages__script__renameModal.4bbe7fb1.async.js +0 -1
  217. package/static/dist/src__pages__script__saveModal.cf449f3c.async.js +0 -1
  218. package/static/dist/src__pages__script__setting.b345d59a.async.js +0 -1
  219. package/static/dist/src__pages__setting__appModal.03faec89.async.js +0 -1
  220. package/static/dist/src__pages__setting__checkUpdate.46efe057.async.js +0 -1
  221. package/static/dist/src__pages__setting__dependence.4495c7b6.async.js +0 -1
  222. package/static/dist/src__pages__setting__index.6919c399.async.js +0 -1
  223. package/static/dist/src__pages__setting__notification.d6a3884f.async.js +0 -1
  224. package/static/dist/src__pages__setting__other.0d931d6f.async.js +0 -1
  225. package/static/dist/src__pages__setting__security.91cb545f.async.js +0 -1
  226. package/static/dist/src__pages__setting__systemLog.1d433a48.async.js +0 -1
  227. package/static/dist/src__pages__subscription__modal.0b84f6cf.async.js +0 -1
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RunningInstanceModel = exports.RunningInstance = exports.InstanceStatus = void 0;
4
+ const _1 = require(".");
5
+ const sequelize_1 = require("sequelize");
6
+ var InstanceStatus;
7
+ (function (InstanceStatus) {
8
+ InstanceStatus[InstanceStatus["running"] = 0] = "running";
9
+ InstanceStatus[InstanceStatus["finished"] = 1] = "finished";
10
+ InstanceStatus[InstanceStatus["stopped"] = 2] = "stopped";
11
+ InstanceStatus[InstanceStatus["error"] = 3] = "error";
12
+ })(InstanceStatus || (exports.InstanceStatus = InstanceStatus = {}));
13
+ class RunningInstance {
14
+ constructor(options) {
15
+ this.id = options.id;
16
+ this.cron_id = options.cron_id;
17
+ this.pid = options.pid;
18
+ this.log_path = options.log_path;
19
+ this.started_at = options.started_at;
20
+ this.finished_at = options.finished_at;
21
+ this.status = options.status;
22
+ this.exit_code = options.exit_code;
23
+ }
24
+ }
25
+ exports.RunningInstance = RunningInstance;
26
+ exports.RunningInstanceModel = _1.sequelize.define('RunningInstance', {
27
+ cron_id: {
28
+ type: sequelize_1.DataTypes.NUMBER,
29
+ allowNull: false,
30
+ },
31
+ pid: {
32
+ type: sequelize_1.DataTypes.NUMBER,
33
+ allowNull: true,
34
+ },
35
+ log_path: {
36
+ type: sequelize_1.DataTypes.STRING,
37
+ allowNull: true,
38
+ },
39
+ started_at: {
40
+ type: sequelize_1.DataTypes.NUMBER,
41
+ allowNull: false,
42
+ },
43
+ finished_at: {
44
+ type: sequelize_1.DataTypes.NUMBER,
45
+ allowNull: true,
46
+ },
47
+ status: {
48
+ type: sequelize_1.DataTypes.NUMBER,
49
+ allowNull: false,
50
+ defaultValue: InstanceStatus.running,
51
+ },
52
+ exit_code: {
53
+ type: sequelize_1.DataTypes.NUMBER,
54
+ allowNull: true,
55
+ },
56
+ });
57
+ //# sourceMappingURL=runningInstance.js.map
@@ -6,6 +6,7 @@ class SockMessage {
6
6
  this.type = options.type;
7
7
  this.message = options.message;
8
8
  this.references = options.references;
9
+ this.status = options.status;
9
10
  }
10
11
  }
11
12
  exports.SockMessage = SockMessage;
@@ -15,7 +15,7 @@ exports.default = async ({ app }) => {
15
15
  logger_1.default.info('✌️ Dependency loaded');
16
16
  await (0, deps_1.default)();
17
17
  logger_1.default.info('✌️ Link deps loaded');
18
- (0, initFile_1.default)();
18
+ await (0, initFile_1.default)();
19
19
  logger_1.default.info('✌️ Init file loaded');
20
20
  await (0, initData_1.default)();
21
21
  logger_1.default.info('✌️ Init data loaded');
@@ -11,6 +11,8 @@ const open_1 = require("../data/open");
11
11
  const system_1 = require("../data/system");
12
12
  const subscription_1 = require("../data/subscription");
13
13
  const cronView_1 = require("../data/cronView");
14
+ const cronStats_1 = require("../data/cronStats");
15
+ const runningInstance_1 = require("../data/runningInstance");
14
16
  const data_1 = require("../data");
15
17
  exports.default = async () => {
16
18
  try {
@@ -21,6 +23,8 @@ exports.default = async () => {
21
23
  await env_1.EnvModel.sync();
22
24
  await subscription_1.SubscriptionModel.sync();
23
25
  await cronView_1.CrontabViewModel.sync();
26
+ await cronStats_1.CrontabStatModel.sync();
27
+ await runningInstance_1.RunningInstanceModel.sync();
24
28
  // 初始化新增字段
25
29
  const migrations = [
26
30
  {
@@ -42,7 +46,9 @@ exports.default = async () => {
42
46
  column: 'allow_multiple_instances',
43
47
  type: 'NUMBER',
44
48
  },
49
+ { table: 'Crontabs', column: 'work_dir', type: 'VARCHAR(255)' },
45
50
  { table: 'Envs', column: 'isPinned', type: 'NUMBER' },
51
+ { table: 'Envs', column: 'labels', type: 'JSON' },
46
52
  ];
47
53
  for (const migration of migrations) {
48
54
  try {
@@ -6,20 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const path_1 = __importDefault(require("path"));
7
7
  const promises_1 = __importDefault(require("fs/promises"));
8
8
  const os_1 = __importDefault(require("os"));
9
- const chokidar_1 = __importDefault(require("chokidar"));
10
9
  const index_1 = __importDefault(require("../config/index"));
11
10
  const logger_1 = __importDefault(require("./logger"));
12
- async function linkToNodeModule(src, dst) {
13
- const target = path_1.default.join(index_1.default.rootPath, 'node_modules', dst || src);
14
- const source = path_1.default.join(index_1.default.rootPath, src);
15
- try {
16
- const stats = await promises_1.default.lstat(target);
17
- if (!stats) {
18
- await promises_1.default.symlink(source, target, 'dir');
19
- }
20
- }
21
- catch (error) { }
22
- }
23
11
  async function linkCommand() {
24
12
  const homeDir = os_1.default.homedir();
25
13
  let userBinDir = path_1.default.join(homeDir, 'bin');
@@ -59,16 +47,7 @@ async function linkCommandToDir(commandDir) {
59
47
  await promises_1.default.rename(tmpTarget, target);
60
48
  }
61
49
  }
62
- exports.default = async (src = 'deps') => {
50
+ exports.default = async () => {
63
51
  await linkCommand();
64
- await linkToNodeModule(src);
65
- const source = path_1.default.join(index_1.default.rootPath, src);
66
- const watcher = chokidar_1.default.watch(source, {
67
- ignored: /(^|[\/\\])\../,
68
- persistent: true,
69
- });
70
- watcher
71
- .on('add', () => linkToNodeModule(src))
72
- .on('change', () => linkToNodeModule(src));
73
52
  };
74
53
  //# sourceMappingURL=deps.js.map
@@ -16,6 +16,7 @@ const serverEnv_1 = require("../config/serverEnv");
16
16
  const store_1 = require("../shared/store");
17
17
  const auth_1 = require("../shared/auth");
18
18
  const path_1 = __importDefault(require("path"));
19
+ const i18n_1 = require("../shared/i18n");
19
20
  exports.default = ({ app }) => {
20
21
  // Security: Enable strict routing to prevent case-insensitive path bypass
21
22
  app.set('case sensitive routing', true);
@@ -119,7 +120,7 @@ exports.default = ({ app }) => {
119
120
  isInitialized = false;
120
121
  }
121
122
  if (isInitialized) {
122
- return res.send({ code: 450, message: '未知错误' });
123
+ return res.send({ code: 450, message: (0, i18n_1.t)('未知错误') });
123
124
  }
124
125
  else {
125
126
  return next();
@@ -46,8 +46,10 @@ const open_1 = __importDefault(require("../services/open"));
46
46
  const store_1 = require("../shared/store");
47
47
  const logger_1 = __importDefault(require("./logger"));
48
48
  const open_2 = require("../data/open");
49
+ const runningInstance_1 = require("../data/runningInstance");
50
+ const i18n_1 = require("../shared/i18n");
49
51
  exports.default = async () => {
50
- var _a, _b, _c, _d, _e, _f, _g;
52
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
51
53
  const cronService = typedi_1.Container.get(cron_2.default);
52
54
  const envService = typedi_1.Container.get(env_1.default);
53
55
  const dependenceService = typedi_1.Container.get(dependence_1.default);
@@ -61,11 +63,14 @@ exports.default = async () => {
61
63
  if (!systemApp) {
62
64
  systemApp = await open_2.AppModel.create({
63
65
  name: 'system',
64
- scopes: ['crons', 'system'],
66
+ scopes: ['crons', 'system', 'dashboard'],
65
67
  client_id: (0, util_1.createRandomString)(12, 12),
66
68
  client_secret: (0, util_1.createRandomString)(24, 24),
67
69
  });
68
70
  }
71
+ else if (!systemApp.scopes.includes('dashboard')) {
72
+ await open_2.AppModel.update({ scopes: [...systemApp.scopes, 'dashboard'] }, { where: { name: 'system' } });
73
+ }
69
74
  const [systemConfig] = await system_1.SystemModel.findOrCreate({
70
75
  where: { type: system_1.AuthDataType.systemConfig },
71
76
  });
@@ -146,6 +151,8 @@ exports.default = async () => {
146
151
  });
147
152
  // 初始化更新所有任务状态为空闲
148
153
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle }, { where: {} });
154
+ // 清空所有运行中的实例记录(服务重启后进程已不存在)
155
+ await runningInstance_1.RunningInstanceModel.update({ status: runningInstance_1.InstanceStatus.stopped }, { where: { status: runningInstance_1.InstanceStatus.running } });
149
156
  // 初始化时执行一次所有的 ql repo 任务
150
157
  cron_1.CrontabModel.findAll({
151
158
  where: {
@@ -201,6 +208,16 @@ exports.default = async () => {
201
208
  });
202
209
  // 初始化保存一次ck和定时任务数据
203
210
  await cronService.autosave_crontab();
211
+ // 确保 lang_env.sh 存在,提供默认 QL_LANG
212
+ try {
213
+ const langEnvExist = await (0, util_1.fileExist)(config_1.default.langEnvFile);
214
+ if (!langEnvExist) {
215
+ const lang = ((_h = systemConfig.info) === null || _h === void 0 ? void 0 : _h.lang) || 'zh';
216
+ await (0, promises_1.writeFile)(config_1.default.langEnvFile, `export QL_LANG='${lang}'\n`);
217
+ }
218
+ }
219
+ catch (_k) { }
220
+ (0, i18n_1.setLang)(((_j = systemConfig.info) === null || _j === void 0 ? void 0 : _j.lang) || 'zh');
204
221
  await envService.set_envs();
205
222
  const authInfo = await userService.getAuthInfo();
206
223
  const apps = await openService.findApps();
@@ -22,6 +22,7 @@ const uploadPath = path_1.default.join(dataPath, 'upload/');
22
22
  const bakPath = path_1.default.join(dataPath, 'bak/');
23
23
  const samplePath = path_1.default.join(rootPath, 'sample/');
24
24
  const tmpPath = path_1.default.join(logPath, '.tmp/');
25
+ const rootTmpPath = path_1.default.join(rootPath, '.tmp/');
25
26
  const confFile = path_1.default.join(configPath, 'config.sh');
26
27
  const sampleConfigFile = path_1.default.join(samplePath, 'config.sample.sh');
27
28
  const sampleTaskShellFile = path_1.default.join(samplePath, 'task.sample.sh');
@@ -45,6 +46,7 @@ const directories = [
45
46
  preloadPath,
46
47
  logPath,
47
48
  tmpPath,
49
+ rootTmpPath,
48
50
  uploadPath,
49
51
  sshPath,
50
52
  bakPath,
@@ -11,6 +11,7 @@ const sshKey_1 = __importDefault(require("../services/sshKey"));
11
11
  const config_1 = __importDefault(require("../config"));
12
12
  const util_1 = require("../config/util");
13
13
  const path_1 = require("path");
14
+ const i18n_1 = require("../shared/i18n");
14
15
  exports.default = async () => {
15
16
  const systemService = typedi_1.Container.get(system_1.default);
16
17
  const scheduleService = typedi_1.Container.get(schedule_1.default);
@@ -24,7 +25,7 @@ exports.default = async () => {
24
25
  }
25
26
  const cron = {
26
27
  id: NaN,
27
- name: '生成token',
28
+ name: (0, i18n_1.t)('生成token'),
28
29
  command: tokenCommand,
29
30
  runOrigin: 'system',
30
31
  };
@@ -38,7 +39,7 @@ exports.default = async () => {
38
39
  if (data.info.logRemoveFrequency) {
39
40
  const rmlogCron = {
40
41
  id: data.id,
41
- name: '删除日志',
42
+ name: (0, i18n_1.t)('删除日志'),
42
43
  command: `ql rmlog ${data.info.logRemoveFrequency}`,
43
44
  runOrigin: 'system',
44
45
  };
@@ -203,7 +203,7 @@ const normalizeCronData = (data) => {
203
203
  var _a, _b, _c, _d, _e;
204
204
  if (!data)
205
205
  return undefined;
206
- return Object.assign(Object.assign({}, data), { sub_id: (_a = data.sub_id) !== null && _a !== void 0 ? _a : undefined, extra_schedules: (_b = data.extra_schedules) !== null && _b !== void 0 ? _b : undefined, pid: (_c = data.pid) !== null && _c !== void 0 ? _c : undefined, task_before: (_d = data.task_before) !== null && _d !== void 0 ? _d : undefined, task_after: (_e = data.task_after) !== null && _e !== void 0 ? _e : undefined });
206
+ return Object.assign(Object.assign({}, data), { sub_id: (_a = data.sub_id) !== null && _a !== void 0 ? _a : undefined, extra_schedules: (_b = data.extra_schedules) !== null && _b !== void 0 ? _b : [], pid: (_c = data.pid) !== null && _c !== void 0 ? _c : undefined, task_before: (_d = data.task_before) !== null && _d !== void 0 ? _d : undefined, task_after: (_e = data.task_after) !== null && _e !== void 0 ? _e : undefined });
207
207
  };
208
208
  const getCronDetail = async (call, callback) => {
209
209
  try {
@@ -6,9 +6,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const grpc_js_1 = require("@grpc/grpc-js");
7
7
  const cron_1 = require("../protos/cron");
8
8
  const config_1 = __importDefault(require("../config"));
9
+ const grpcCerts_1 = require("../config/grpcCerts");
9
10
  class Client {
10
11
  constructor() {
11
- this.client = new cron_1.CronClient(`0.0.0.0:${config_1.default.grpcPort}`, grpc_js_1.credentials.createInsecure(), { 'grpc.enable_http_proxy': 0 });
12
+ this._client = null;
13
+ }
14
+ get client() {
15
+ if (!this._client) {
16
+ const tlsConfig = (0, grpcCerts_1.getGrpcCerts)();
17
+ this._client = new cron_1.CronClient(`localhost:${config_1.default.grpcPort}`, grpc_js_1.credentials.createSsl(Buffer.from(tlsConfig.caCert), Buffer.from(tlsConfig.clientKey), Buffer.from(tlsConfig.clientCert)), { 'grpc.enable_http_proxy': 0 });
18
+ }
19
+ return this._client;
12
20
  }
13
21
  addCron(request) {
14
22
  return new Promise((resolve, reject) => {
@@ -9,7 +9,7 @@ const util_1 = require("../config/util");
9
9
  const check = async (call, callback) => {
10
10
  switch (call.request.service) {
11
11
  case 'cron':
12
- const res = await (0, util_1.promiseExec)(`curl -s --noproxy '*' http://0.0.0.0:${config_1.default.port}/api/system`);
12
+ const res = await (0, util_1.promiseExec)(`curl -s --noproxy '*' http://localhost:${config_1.default.port}/api/system`);
13
13
  if (res.includes('200')) {
14
14
  return callback(null, { status: 1 });
15
15
  }
@@ -39,16 +39,30 @@ const typedi_1 = require("typedi");
39
39
  const path_1 = __importStar(require("path"));
40
40
  const config_1 = __importDefault(require("../config"));
41
41
  const util_1 = require("../config/util");
42
+ const i18n_1 = require("../shared/i18n");
42
43
  const undici_1 = require("undici");
43
44
  let ConfigService = class ConfigService {
44
45
  constructor() { }
45
46
  async getFile(filePath, res) {
46
47
  let content = '';
47
- const avaliablePath = [config_1.default.rootPath, config_1.default.configPath].map((x) => path_1.default.resolve(x, filePath));
48
- if (config_1.default.blackFileList.includes(filePath) ||
49
- avaliablePath.every((x) => !x.startsWith(config_1.default.scriptPath) && !x.startsWith(config_1.default.configPath)) ||
50
- !filePath) {
51
- return res.send({ code: 403, message: '文件无法访问' });
48
+ if (!filePath) {
49
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
50
+ }
51
+ const normalized = path_1.default.normalize(filePath);
52
+ if (normalized.startsWith('..') || path_1.default.isAbsolute(normalized)) {
53
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
54
+ }
55
+ const resolvedRoot = path_1.default.resolve(config_1.default.rootPath, normalized);
56
+ const resolvedConfig = path_1.default.resolve(config_1.default.configPath, normalized);
57
+ const isValidPath = resolvedRoot.startsWith(config_1.default.scriptPath) ||
58
+ resolvedRoot.startsWith(config_1.default.configPath) ||
59
+ resolvedConfig.startsWith(config_1.default.scriptPath) ||
60
+ resolvedConfig.startsWith(config_1.default.configPath);
61
+ if (!isValidPath) {
62
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
63
+ }
64
+ if (config_1.default.blackFileList.includes(path_1.default.basename(normalized))) {
65
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
52
66
  }
53
67
  if (filePath.startsWith('sample/')) {
54
68
  const res = await (0, undici_1.request)(`https://gitlab.com/whyour/qinglong/-/raw/master/${filePath}`);
@@ -19,6 +19,7 @@ const typedi_1 = require("typedi");
19
19
  const winston_1 = __importDefault(require("winston"));
20
20
  const config_1 = __importDefault(require("../config"));
21
21
  const cron_1 = require("../data/cron");
22
+ const runningInstance_1 = require("../data/runningInstance");
22
23
  const child_process_1 = require("child_process");
23
24
  const promises_1 = __importDefault(require("fs/promises"));
24
25
  const cron_parser_1 = __importDefault(require("cron-parser"));
@@ -33,8 +34,10 @@ const dayjs_1 = __importDefault(require("dayjs"));
33
34
  const pickBy_1 = __importDefault(require("lodash/pickBy"));
34
35
  const omit_1 = __importDefault(require("lodash/omit"));
35
36
  const utils_1 = require("../shared/utils");
37
+ const i18n_1 = require("../shared/i18n");
36
38
  const schedule_1 = require("../interface/schedule");
37
39
  const logStreamManager_1 = require("../shared/logStreamManager");
40
+ const lodash_1 = require("lodash");
38
41
  let CronService = class CronService {
39
42
  constructor(logger) {
40
43
  this.logger = logger;
@@ -46,6 +49,26 @@ let CronService = class CronService {
46
49
  }
47
50
  return false;
48
51
  }
52
+ get schedulerMode() {
53
+ const env = process.env.QL_SCHEDULER;
54
+ if (env === 'system')
55
+ return 'system';
56
+ if (env === 'node')
57
+ return 'node';
58
+ try {
59
+ (0, child_process_1.execSync)('which crond', { stdio: 'ignore' });
60
+ return 'system';
61
+ }
62
+ catch (_a) {
63
+ return 'node';
64
+ }
65
+ }
66
+ shouldUseCronClient(cron) {
67
+ if (this.schedulerMode === 'node') {
68
+ return !this.isSpecialSchedule(cron.schedule);
69
+ }
70
+ return this.isNodeCron(cron) && !this.isSpecialSchedule(cron.schedule);
71
+ }
49
72
  isOnceSchedule(schedule) {
50
73
  return schedule === null || schedule === void 0 ? void 0 : schedule.startsWith(schedule_1.ScheduleType.ONCE);
51
74
  }
@@ -81,7 +104,7 @@ let CronService = class CronService {
81
104
  if ((0, util_1.isDemoEnv)()) {
82
105
  return doc;
83
106
  }
84
- if (!this.isSpecialSchedule(doc.schedule)) {
107
+ if (this.shouldUseCronClient(doc)) {
85
108
  await client_1.default.addCron([
86
109
  {
87
110
  name: doc.name || '',
@@ -108,7 +131,7 @@ let CronService = class CronService {
108
131
  return newDoc;
109
132
  }
110
133
  await client_1.default.delCron([String(newDoc.id)]);
111
- if (!this.isSpecialSchedule(newDoc.schedule)) {
134
+ if (this.shouldUseCronClient(newDoc)) {
112
135
  await client_1.default.addCron([
113
136
  {
114
137
  name: doc.name || '',
@@ -148,6 +171,31 @@ let CronService = class CronService {
148
171
  if (status === cron_1.CrontabStatus.idle && log_path !== cron.log_path) {
149
172
  options = (0, omit_1.default)(options, ['status', 'log_path', 'pid']);
150
173
  }
174
+ // Manage RunningInstance records for status transitions from shell scripts
175
+ if (status === cron_1.CrontabStatus.running) {
176
+ // Create a new running instance record
177
+ await runningInstance_1.RunningInstanceModel.create({
178
+ cron_id: id,
179
+ pid: pid || undefined,
180
+ log_path: log_path || undefined,
181
+ started_at: last_execution_time || (0, dayjs_1.default)().unix(),
182
+ status: runningInstance_1.InstanceStatus.running,
183
+ });
184
+ }
185
+ else if (status === cron_1.CrontabStatus.idle) {
186
+ // Mark the matching running instance as finished
187
+ const finishedAt = (0, dayjs_1.default)().unix();
188
+ await runningInstance_1.RunningInstanceModel.update({
189
+ finished_at: finishedAt,
190
+ status: runningInstance_1.InstanceStatus.finished,
191
+ }, {
192
+ where: {
193
+ cron_id: id,
194
+ pid: pid || undefined,
195
+ status: runningInstance_1.InstanceStatus.running,
196
+ },
197
+ });
198
+ }
151
199
  await cron_1.CrontabModel.update(Object.assign({}, (0, pickBy_1.default)(options, (v) => v === 0 || !!v)), { where: { id } });
152
200
  }
153
201
  }
@@ -308,7 +356,7 @@ let CronService = class CronService {
308
356
  }
309
357
  }
310
358
  formatFilterQuery(query, filterQuery) {
311
- if (filterQuery) {
359
+ if (!(0, lodash_1.isEmpty)(filterQuery)) {
312
360
  if (!query[sequelize_1.Op.and]) {
313
361
  query[sequelize_1.Op.and] = [];
314
362
  }
@@ -418,8 +466,36 @@ let CronService = class CronService {
418
466
  this.logger.error(`[panel][停止任务失败] 任务ID: ${doc.id}, 错误: ${error}`);
419
467
  }
420
468
  }
469
+ // Mark all running instances as stopped
470
+ const finishedAt = (0, dayjs_1.default)().unix();
471
+ await runningInstance_1.RunningInstanceModel.update({ status: runningInstance_1.InstanceStatus.stopped, finished_at: finishedAt }, { where: { cron_id: ids, status: runningInstance_1.InstanceStatus.running } });
421
472
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id: ids } });
422
473
  }
474
+ async stopInstance(instanceId) {
475
+ const instance = await runningInstance_1.RunningInstanceModel.findOne({
476
+ where: { id: instanceId, status: runningInstance_1.InstanceStatus.running },
477
+ });
478
+ if (!instance) {
479
+ return { code: 400, message: (0, i18n_1.t)('实例不存在或已停止') };
480
+ }
481
+ if (instance.pid) {
482
+ try {
483
+ await (0, util_1.killTask)(instance.pid);
484
+ }
485
+ catch (error) {
486
+ this.logger.error(`[panel][停止实例失败] 实例ID: ${instanceId}, PID: ${instance.pid}, 错误: ${error}`);
487
+ }
488
+ }
489
+ await runningInstance_1.RunningInstanceModel.update({ status: runningInstance_1.InstanceStatus.stopped, finished_at: (0, dayjs_1.default)().unix() }, { where: { id: instanceId } });
490
+ // Check if there are still other running instances for this cron
491
+ const otherRunning = await runningInstance_1.RunningInstanceModel.count({
492
+ where: { cron_id: instance.cron_id, status: runningInstance_1.InstanceStatus.running },
493
+ });
494
+ if (otherRunning === 0) {
495
+ await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id: instance.cron_id } });
496
+ }
497
+ return { code: 200, message: (0, i18n_1.t)('实例已停止') };
498
+ }
423
499
  async runSingle(cronId) {
424
500
  return pLimit_1.default.manualRunWithCronLimit(() => {
425
501
  return new Promise(async (resolve) => {
@@ -445,6 +521,14 @@ let CronService = class CronService {
445
521
  const logPath = `${uniqPath}/${logTime}.log`;
446
522
  const absolutePath = path_1.default.resolve(config_1.default.logPath, `${logPath}`);
447
523
  const cp = (0, cross_spawn_1.spawn)(`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron, true)}`, { shell: '/bin/bash' });
524
+ const startedAt = (0, dayjs_1.default)().unix();
525
+ const instance = await runningInstance_1.RunningInstanceModel.create({
526
+ cron_id: id,
527
+ pid: cp.pid,
528
+ log_path: logPath,
529
+ started_at: startedAt,
530
+ status: runningInstance_1.InstanceStatus.running,
531
+ });
448
532
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.running, pid: cp.pid, log_path: logPath }, { where: { id } });
449
533
  cp.stdout.on('data', async (data) => {
450
534
  await logStreamManager_1.logStreamManager.write(absolutePath, data.toString());
@@ -459,9 +543,20 @@ let CronService = class CronService {
459
543
  });
460
544
  cp.on('exit', async (code) => {
461
545
  this.logger.info('[panel][执行任务结束] 参数: %s, 退出码: %j', JSON.stringify(params), code);
462
- // Close the stream after task completion
463
546
  await logStreamManager_1.logStreamManager.closeStream(absolutePath);
464
- await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id } });
547
+ const finishedAt = (0, dayjs_1.default)().unix();
548
+ await runningInstance_1.RunningInstanceModel.update({
549
+ finished_at: finishedAt,
550
+ status: code === 0 ? runningInstance_1.InstanceStatus.finished : runningInstance_1.InstanceStatus.error,
551
+ exit_code: code !== null && code !== void 0 ? code : undefined,
552
+ }, { where: { id: instance.id } });
553
+ // Only set cron to idle if no other running instances exist
554
+ const otherRunning = await runningInstance_1.RunningInstanceModel.count({
555
+ where: { cron_id: id, status: runningInstance_1.InstanceStatus.running },
556
+ });
557
+ if (otherRunning === 0) {
558
+ await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id } });
559
+ }
465
560
  resolve(Object.assign(Object.assign({}, params), { pid: cp.pid, code }));
466
561
  });
467
562
  });
@@ -475,7 +570,9 @@ let CronService = class CronService {
475
570
  async enabled(ids) {
476
571
  await cron_1.CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
477
572
  const docs = await cron_1.CrontabModel.findAll({ where: { id: ids } });
478
- const crons = docs.map((doc) => ({
573
+ const crons = docs
574
+ .filter((x) => this.shouldUseCronClient(x))
575
+ .map((doc) => ({
479
576
  name: doc.name || '',
480
577
  id: String(doc.id),
481
578
  schedule: doc.schedule,
@@ -491,21 +588,24 @@ let CronService = class CronService {
491
588
  async log(id) {
492
589
  const doc = await this.getDb({ id });
493
590
  if (!doc) {
494
- return '';
591
+ return { content: '', status: 'empty' };
495
592
  }
496
593
  if (doc.log_name === '/dev/null') {
497
- return '日志设置为忽略';
594
+ return { content: (0, i18n_1.t)('日志设置为忽略'), status: 'ignored' };
498
595
  }
499
596
  const absolutePath = path_1.default.resolve(config_1.default.logPath, `${doc.log_path}`);
500
597
  const logFileExist = doc.log_path && (await (0, util_1.fileExist)(absolutePath));
501
598
  if (logFileExist) {
502
- return await (0, util_1.getFileContentByName)(`${absolutePath}`);
599
+ const content = await (0, util_1.getFileContentByName)(`${absolutePath}`);
600
+ const isRunning = typeof doc.status === 'number' &&
601
+ [cron_1.CrontabStatus.running, cron_1.CrontabStatus.queued].includes(doc.status);
602
+ return { content, status: isRunning ? 'running' : 'completed' };
503
603
  }
504
604
  else {
505
605
  return typeof doc.status === 'number' &&
506
606
  [cron_1.CrontabStatus.queued, cron_1.CrontabStatus.running].includes(doc.status)
507
- ? '运行中...'
508
- : '日志不存在...';
607
+ ? { content: (0, i18n_1.t)('运行中...'), status: 'running' }
608
+ : { content: (0, i18n_1.t)('日志不存在...'), status: 'notFound' };
509
609
  }
510
610
  }
511
611
  async logs(id) {
@@ -550,6 +650,9 @@ let CronService = class CronService {
550
650
  .replace(/;? *\n/g, ';')
551
651
  .trim()}' `;
552
652
  }
653
+ if (tab.work_dir) {
654
+ commandVariable += `work_dir='${tab.work_dir.replace(/'/g, "'\\''")}' `;
655
+ }
553
656
  const crontab_job_string = `${commandVariable}${command}`;
554
657
  return crontab_job_string;
555
658
  }
@@ -574,6 +677,15 @@ let CronService = class CronService {
574
677
  }
575
678
  });
576
679
  await (0, utils_1.writeFileWithLock)(config_1.default.crontabFile, crontab_string);
680
+ if (this.schedulerMode === 'system') {
681
+ try {
682
+ (0, child_process_1.execSync)(`crontab ${config_1.default.crontabFile}`);
683
+ }
684
+ catch (error) {
685
+ const errorMsg = error.message || String(error);
686
+ this.logger.error('[crontab] Failed to update system crontab:', errorMsg);
687
+ }
688
+ }
577
689
  await cron_1.CrontabModel.update({ saved: true }, { where: {} });
578
690
  }
579
691
  importCrontab() {
@@ -611,7 +723,8 @@ let CronService = class CronService {
611
723
  async autosave_crontab() {
612
724
  const tabs = await this.crontabs();
613
725
  const regularCrons = tabs.data
614
- .filter((x) => x.isDisabled !== 1 && !this.isSpecialSchedule(x.schedule))
726
+ .filter((x) => x.isDisabled !== 1 &&
727
+ this.shouldUseCronClient(x))
615
728
  .map((doc) => ({
616
729
  name: doc.name || '',
617
730
  id: String(doc.id),
@@ -632,7 +745,7 @@ let CronService = class CronService {
632
745
  if (bootTasks.length > 0) {
633
746
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.queued }, { where: { id: bootTasks.map((t) => t.id) } });
634
747
  for (const task of bootTasks) {
635
- await this.runSingle(task.id);
748
+ this.runSingle(task.id);
636
749
  }
637
750
  }
638
751
  }