@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.
Files changed (81) hide show
  1. package/back/protos/api.proto +3 -0
  2. package/deploy/kubernetes/README.md +81 -0
  3. package/deploy/kubernetes/base/kustomization.yaml +5 -0
  4. package/deploy/kubernetes/base/qinglong.yaml +95 -0
  5. package/deploy/kubernetes/overlays/example/kustomization.yaml +10 -0
  6. package/package.json +1 -1
  7. package/sample/config.sample.sh +7 -2
  8. package/sample/notify.js +64 -0
  9. package/sample/notify.py +56 -0
  10. package/static/build/api/env.js +2 -2
  11. package/static/build/api/system.js +6 -7
  12. package/static/build/app.js +19 -19
  13. package/static/build/config/const.js +1 -0
  14. package/static/build/config/grpcCerts.js +4 -4
  15. package/static/build/data/notify.js +8 -1
  16. package/static/build/loaders/app.js +6 -6
  17. package/static/build/loaders/db.js +2 -2
  18. package/static/build/loaders/express.js +1 -6
  19. package/static/build/loaders/initFile.js +1 -1
  20. package/static/build/loaders/server.js +1 -1
  21. package/static/build/protos/api.js +23 -1
  22. package/static/build/schedule/addCron.js +67 -10
  23. package/static/build/schedule/delCron.js +11 -1
  24. package/static/build/services/cron.js +88 -24
  25. package/static/build/services/grpc.js +1 -1
  26. package/static/build/services/http.js +1 -1
  27. package/static/build/services/notify.js +42 -0
  28. package/static/build/services/user.js +3 -0
  29. package/static/build/shared/auth.js +7 -1
  30. package/static/build/shared/i18n.js +7 -0
  31. package/static/build/validation/schedule.js +9 -0
  32. package/static/dist/1147.62793604.async.js +1 -0
  33. package/static/dist/2208.85285476.async.js +1 -0
  34. package/static/dist/2286.f371c743.async.js +1 -0
  35. package/static/dist/3191.545ab873.async.js +1 -0
  36. package/static/dist/8826.db34ad8d.async.js +1 -0
  37. package/static/dist/index.html +2 -2
  38. package/static/dist/{preload_helper.e0bef63b.js → preload_helper.3048fe5b.js} +1 -1
  39. package/static/dist/{src__pages__crontab__index.6b63ca05.async.js → src__pages__crontab__index.68e78a33.async.js} +1 -1
  40. package/static/dist/src__pages__dependence__modal.f7bb47ba.async.js +1 -0
  41. package/static/dist/src__pages__env__editNameModal.48715576.async.js +1 -0
  42. package/static/dist/src__pages__env__index.452cdc47.async.js +1 -0
  43. package/static/dist/src__pages__env__modal.e1234834.async.js +1 -0
  44. package/static/dist/src__pages__error__index.dedb2b90.async.js +1 -0
  45. package/static/dist/src__pages__initialization__index.22620a77.async.js +1 -0
  46. package/static/dist/src__pages__script__editNameModal.532a3044.async.js +1 -0
  47. package/static/dist/src__pages__script__index.1fc20f22.async.js +1 -0
  48. package/static/dist/src__pages__script__renameModal.5cdde919.async.js +1 -0
  49. package/static/dist/src__pages__script__saveModal.5ffed35f.async.js +1 -0
  50. package/static/dist/src__pages__script__setting.1a82c6d7.async.js +1 -0
  51. package/static/dist/src__pages__setting__appModal.43b7758a.async.js +1 -0
  52. package/static/dist/src__pages__setting__dependence.9c04c79a.async.js +1 -0
  53. package/static/dist/src__pages__setting__index.7e070838.async.js +1 -0
  54. package/static/dist/src__pages__setting__notification.bac481c2.async.js +1 -0
  55. package/static/dist/src__pages__setting__security.b7235753.async.js +1 -0
  56. package/static/dist/src__pages__setting__systemLog.b81a8c1d.async.js +1 -0
  57. package/static/dist/src__pages__subscription__modal.562448e2.async.js +1 -0
  58. package/static/dist/{umi.025a31b3.js → umi.a54a59f6.js} +1 -1
  59. package/static/dist/1147.c420132e.async.js +0 -1
  60. package/static/dist/2208.f7ba3dfa.async.js +0 -1
  61. package/static/dist/2286.164bb089.async.js +0 -1
  62. package/static/dist/3191.37d489cf.async.js +0 -1
  63. package/static/dist/8826.6879b271.async.js +0 -1
  64. package/static/dist/src__pages__dependence__modal.11124896.async.js +0 -1
  65. package/static/dist/src__pages__env__editNameModal.5d264b25.async.js +0 -1
  66. package/static/dist/src__pages__env__index.9a565d2b.async.js +0 -1
  67. package/static/dist/src__pages__env__modal.7f2ef1bc.async.js +0 -1
  68. package/static/dist/src__pages__error__index.f156b45e.async.js +0 -1
  69. package/static/dist/src__pages__initialization__index.e96d4ba8.async.js +0 -1
  70. package/static/dist/src__pages__script__editNameModal.3e7e100d.async.js +0 -1
  71. package/static/dist/src__pages__script__index.e869812f.async.js +0 -1
  72. package/static/dist/src__pages__script__renameModal.5e987ef5.async.js +0 -1
  73. package/static/dist/src__pages__script__saveModal.3f9d23d6.async.js +0 -1
  74. package/static/dist/src__pages__script__setting.a535793a.async.js +0 -1
  75. package/static/dist/src__pages__setting__appModal.251cd14f.async.js +0 -1
  76. package/static/dist/src__pages__setting__dependence.29f13e8c.async.js +0 -1
  77. package/static/dist/src__pages__setting__index.7a39d415.async.js +0 -1
  78. package/static/dist/src__pages__setting__notification.390fc905.async.js +0 -1
  79. package/static/dist/src__pages__setting__security.598720a8.async.js +0 -1
  80. package/static/dist/src__pages__setting__systemLog.1a5c855f.async.js +0 -1
  81. package/static/dist/src__pages__subscription__modal.3562c670.async.js +0 -1
@@ -151,6 +151,7 @@ enum NotificationMode {
151
151
  chronocat = 18;
152
152
  ntfy = 19;
153
153
  wxPusherBot = 20;
154
+ wxPusherSpt = 21;
154
155
  }
155
156
 
156
157
  message NotificationInfo {
@@ -244,6 +245,8 @@ message NotificationInfo {
244
245
  optional string wxPusherBotAppToken = 66;
245
246
  optional string wxPusherBotTopicIds = 67;
246
247
  optional string wxPusherBotUids = 68;
248
+
249
+ optional string wxPusherSptList = 70;
247
250
  }
248
251
 
249
252
  message SystemNotifyRequest {
@@ -0,0 +1,81 @@
1
+ # Kubernetes deployment
2
+
3
+ This deploys Qinglong as a single-replica `StatefulSet` with persistent data at `/ql/data`.
4
+
5
+ ```bash
6
+ kubectl apply -k deploy/kubernetes/overlays/local
7
+ kubectl -n qinglong rollout status statefulset/qinglong
8
+ ```
9
+
10
+ Open the panel locally:
11
+
12
+ ```bash
13
+ kubectl -n qinglong port-forward svc/qinglong 5700:5700
14
+ ```
15
+
16
+ Then visit <http://127.0.0.1:5700>.
17
+
18
+ ## Image registry overlays
19
+
20
+ Use `overlays/example` as the committed template for registry customization:
21
+
22
+ ```yaml
23
+ whyour/qinglong:debian -> registry.example.com/whyour/qinglong:debian
24
+ ```
25
+
26
+ Create `overlays/local/kustomization.yaml` for the actual cluster image. The `local` overlay is ignored by git so private registry names, digests, and credentials-related references stay local.
27
+
28
+ ## Storage
29
+
30
+ The manifest creates a 5 GiB `ReadWriteOnce` PVC from the cluster's default `StorageClass`.
31
+ If your cluster has no default storage class, add `storageClassName` under:
32
+
33
+ ```yaml
34
+ volumeClaimTemplates:
35
+ - metadata:
36
+ name: data
37
+ spec:
38
+ storageClassName: your-storage-class
39
+ ```
40
+
41
+ Keep `replicas: 1`. Qinglong stores state in the persistent data directory, including SQLite files, so multiple replicas should not share the same data volume.
42
+
43
+ ## Ingress example
44
+
45
+ If you expose Qinglong through an Ingress path other than `/`, set `QlBaseUrl` to the same path with leading and trailing slashes.
46
+
47
+ ```yaml
48
+ env:
49
+ - name: QlBaseUrl
50
+ value: "/qinglong/"
51
+ ```
52
+
53
+ Example Ingress:
54
+
55
+ ```yaml
56
+ apiVersion: networking.k8s.io/v1
57
+ kind: Ingress
58
+ metadata:
59
+ name: qinglong
60
+ namespace: qinglong
61
+ spec:
62
+ rules:
63
+ - host: qinglong.example.com
64
+ http:
65
+ paths:
66
+ - path: /
67
+ pathType: Prefix
68
+ backend:
69
+ service:
70
+ name: qinglong
71
+ port:
72
+ number: 5700
73
+ ```
74
+
75
+ ## Maintenance commands
76
+
77
+ ```bash
78
+ kubectl -n qinglong logs -f statefulset/qinglong
79
+ kubectl -n qinglong exec -it statefulset/qinglong -- ql check
80
+ kubectl -n qinglong exec -it statefulset/qinglong -- ql update
81
+ ```
@@ -0,0 +1,5 @@
1
+ apiVersion: kustomize.config.k8s.io/v1beta1
2
+ kind: Kustomization
3
+
4
+ resources:
5
+ - qinglong.yaml
@@ -0,0 +1,95 @@
1
+ apiVersion: v1
2
+ kind: Namespace
3
+ metadata:
4
+ name: qinglong
5
+ ---
6
+ apiVersion: v1
7
+ kind: Service
8
+ metadata:
9
+ name: qinglong
10
+ namespace: qinglong
11
+ labels:
12
+ app.kubernetes.io/name: qinglong
13
+ spec:
14
+ type: ClusterIP
15
+ selector:
16
+ app.kubernetes.io/name: qinglong
17
+ ports:
18
+ - name: http
19
+ port: 5700
20
+ targetPort: http
21
+ ---
22
+ apiVersion: apps/v1
23
+ kind: StatefulSet
24
+ metadata:
25
+ name: qinglong
26
+ namespace: qinglong
27
+ labels:
28
+ app.kubernetes.io/name: qinglong
29
+ spec:
30
+ serviceName: qinglong
31
+ replicas: 1
32
+ selector:
33
+ matchLabels:
34
+ app.kubernetes.io/name: qinglong
35
+ template:
36
+ metadata:
37
+ labels:
38
+ app.kubernetes.io/name: qinglong
39
+ spec:
40
+ securityContext:
41
+ fsGroup: 5432
42
+ fsGroupChangePolicy: OnRootMismatch
43
+ containers:
44
+ - name: qinglong
45
+ image: whyour/qinglong:debian
46
+ imagePullPolicy: IfNotPresent
47
+ env:
48
+ - name: QlBaseUrl
49
+ value: "/"
50
+ - name: TZ
51
+ value: Asia/Shanghai
52
+ ports:
53
+ - name: http
54
+ containerPort: 5700
55
+ readinessProbe:
56
+ httpGet:
57
+ path: /api/health
58
+ port: http
59
+ initialDelaySeconds: 20
60
+ periodSeconds: 10
61
+ timeoutSeconds: 3
62
+ failureThreshold: 6
63
+ livenessProbe:
64
+ httpGet:
65
+ path: /api/health
66
+ port: http
67
+ initialDelaySeconds: 60
68
+ periodSeconds: 20
69
+ timeoutSeconds: 3
70
+ failureThreshold: 6
71
+ startupProbe:
72
+ httpGet:
73
+ path: /api/health
74
+ port: http
75
+ periodSeconds: 5
76
+ timeoutSeconds: 3
77
+ failureThreshold: 60
78
+ resources:
79
+ requests:
80
+ cpu: 100m
81
+ memory: 256Mi
82
+ limits:
83
+ memory: 1Gi
84
+ volumeMounts:
85
+ - name: data
86
+ mountPath: /ql/data
87
+ volumeClaimTemplates:
88
+ - metadata:
89
+ name: data
90
+ spec:
91
+ accessModes:
92
+ - ReadWriteOnce
93
+ resources:
94
+ requests:
95
+ storage: 5Gi
@@ -0,0 +1,10 @@
1
+ apiVersion: kustomize.config.k8s.io/v1beta1
2
+ kind: Kustomization
3
+
4
+ resources:
5
+ - ../../base
6
+
7
+ images:
8
+ - name: whyour/qinglong
9
+ newName: registry.example.com/whyour/qinglong
10
+ newTag: debian
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@whyour/qinglong",
3
3
  "packageManager": "pnpm@8.3.1",
4
- "version": "2.21.0-6",
4
+ "version": "2.21.0-8",
5
5
  "description": "Timed task management platform supporting Python3, JavaScript, Shell, Typescript",
6
6
  "repository": {
7
7
  "type": "git",
@@ -241,14 +241,19 @@ export NTFY_PASSWORD=""
241
241
  export NTFY_ACTIONS=""
242
242
 
243
243
  ## 21. wxPusher
244
- ## 官方文档: https://wxpusher.zjiecode.com/docs/
245
- ## 管理后台: https://wxpusher.zjiecode.com/admin/
244
+ ### 方式一:标准发送(更强大)
245
+ #### 官方文档: https://wxpusher.zjiecode.com/docs/
246
+ #### 管理后台: https://wxpusher.zjiecode.com/admin/
246
247
  ## wxPusher 的 appToken
247
248
  export WXPUSHER_APP_TOKEN=""
248
249
  ## wxPusher 的 topicIds,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
249
250
  export WXPUSHER_TOPIC_IDS=""
250
251
  ## wxPusher 的 用户ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
251
252
  export WXPUSHER_UIDS=""
253
+ ### 方式二:极简发送(最简单,简单好用,一键配置,更推荐)
254
+ #### wxPusher 的 SPT(极简推送),扫码获取 https://wxpusher.zjiecode.com/docs/#/?id=spt
255
+ #### 多个用英文逗号,分隔,最多10个;与上面的 appToken 方式二选一即可
256
+ export WXPUSHER_SPT_LIST=""
252
257
 
253
258
  ## 22. 自定义通知
254
259
  ## 自定义通知 接收回调的URL
package/sample/notify.js CHANGED
@@ -147,11 +147,16 @@ const push_config = {
147
147
  NTFY_PASSWORD: '', // 推送用户密码,可选
148
148
  NTFY_ACTIONS: '', // 推送用户动作,可选
149
149
 
150
+ // 方式一:标准发送(更强大)
150
151
  // 官方文档: https://wxpusher.zjiecode.com/docs/
151
152
  // 管理后台: https://wxpusher.zjiecode.com/admin/
152
153
  WXPUSHER_APP_TOKEN: '', // wxpusher 的 appToken
153
154
  WXPUSHER_TOPIC_IDS: '', // wxpusher 的 主题ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
154
155
  WXPUSHER_UIDS: '', // wxpusher 的 用户ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
156
+ // 方式二:极简发送(最简单,简单好用,一键配置,更推荐)
157
+ // wxPusher 的 SPT(极简推送),扫码获取 https://wxpusher.zjiecode.com/docs/#/?id=spt
158
+ // 多个用英文逗号,分隔,最多10个;与上面的 appToken 方式二选一即可
159
+ WXPUSHER_SPT_LIST: '', // wxpusher 的 SPT(极简推送),多个用英文逗号,分隔,最多10个
155
160
 
156
161
  // 官方文档: https://openilink.com/docs/hub/apps
157
162
  OPENILINK_APP_TOKEN: '', // OpeniLink 的 app_token,在 OpeniLink Hub 后台安装 App 后获取
@@ -1431,6 +1436,64 @@ function wxPusherNotify(text, desp) {
1431
1436
  });
1432
1437
  }
1433
1438
 
1439
+ function wxPusherSptNotify(text, desp) {
1440
+ return new Promise((resolve) => {
1441
+ const { WXPUSHER_SPT_LIST } = push_config;
1442
+ if (WXPUSHER_SPT_LIST) {
1443
+ // 处理 SPT,将逗号分隔的字符串转为数组
1444
+ const spts = WXPUSHER_SPT_LIST.split(',')
1445
+ .map((spt) => spt.trim())
1446
+ .filter((spt) => spt);
1447
+
1448
+ if (!spts.length) {
1449
+ console.log('wxpusher SPT 不能为空!!');
1450
+ return resolve();
1451
+ }
1452
+ if (spts.length > 10) {
1453
+ console.log('wxpusher SPT 最多支持 10 个!!');
1454
+ return resolve();
1455
+ }
1456
+
1457
+ const body = {
1458
+ content: `<h1>${text}</h1><br/><div style='white-space: pre-wrap;'>${desp}</div>`,
1459
+ summary: text,
1460
+ contentType: 2,
1461
+ // 单个 SPT 用 spt,多个用 sptList
1462
+ ...(spts.length === 1 ? { spt: spts[0] } : { sptList: spts }),
1463
+ };
1464
+
1465
+ const options = {
1466
+ url: 'https://wxpusher.zjiecode.com/api/send/message/simple-push',
1467
+ body: JSON.stringify(body),
1468
+ headers: {
1469
+ 'Content-Type': 'application/json',
1470
+ },
1471
+ timeout,
1472
+ };
1473
+
1474
+ $.post(options, (err, resp, data) => {
1475
+ try {
1476
+ if (err) {
1477
+ console.log('wxpusher SPT 发送通知消息失败!\n', err);
1478
+ } else {
1479
+ if (data.code === 1000) {
1480
+ console.log('wxpusher SPT 发送通知消息完成!');
1481
+ } else {
1482
+ console.log(`wxpusher SPT 发送通知消息异常:${data.msg}`);
1483
+ }
1484
+ }
1485
+ } catch (e) {
1486
+ $.logErr(e, resp);
1487
+ } finally {
1488
+ resolve(data);
1489
+ }
1490
+ });
1491
+ } else {
1492
+ resolve();
1493
+ }
1494
+ });
1495
+ }
1496
+
1434
1497
  function openiLinkNotify(text, desp) {
1435
1498
  return new Promise((resolve) => {
1436
1499
  const { OPENILINK_APP_TOKEN, OPENILINK_HUB_URL, OPENILINK_CONTEXT_TOKEN } =
@@ -1609,6 +1672,7 @@ async function sendNotify(text, desp, params = {}) {
1609
1672
  qmsgNotify(text, desp), // 自定义通知
1610
1673
  ntfyNotify(text, desp), // Ntfy
1611
1674
  wxPusherNotify(text, desp), // wxpusher
1675
+ wxPusherSptNotify(text, desp), // wxpusher SPT
1612
1676
  openiLinkNotify(text, desp), // OpeniLink
1613
1677
  ]);
1614
1678
  }
package/sample/notify.py CHANGED
@@ -133,9 +133,16 @@ push_config = {
133
133
  'NTFY_PASSWORD': '', # 推送用户密码,可选
134
134
  'NTFY_ACTIONS': '', # 推送用户动作,可选
135
135
 
136
+ ### 方式一:标准发送(更强大)
137
+ #### 官方文档: https://wxpusher.zjiecode.com/docs/
138
+ #### 管理后台: https://wxpusher.zjiecode.com/admin/
136
139
  'WXPUSHER_APP_TOKEN': '', # wxpusher 的 appToken 官方文档: https://wxpusher.zjiecode.com/docs/ 管理后台: https://wxpusher.zjiecode.com/admin/
137
140
  'WXPUSHER_TOPIC_IDS': '', # wxpusher 的 主题ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
138
141
  'WXPUSHER_UIDS': '', # wxpusher 的 用户ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
142
+ ### 方式二:极简发送(最简单,简单好用,一键配置,更推荐)
143
+ #### wxPusher 的 SPT(极简推送),扫码获取 https://wxpusher.zjiecode.com/docs/#/?id=spt
144
+ #### 多个用英文逗号,分隔,最多10个;与上面的 appToken 方式二选一即可
145
+ 'WXPUSHER_SPT_LIST': '', # wxpusher 的 SPT(极简推送),多个用英文逗号,分隔,最多10个 官方文档: https://wxpusher.zjiecode.com/docs/#/?id=spt
139
146
 
140
147
  'OPENILINK_APP_TOKEN': '', # OpeniLink 的 app_token,在 OpeniLink Hub 后台安装 App 后获取 官方文档: https://openilink.com/docs/hub/apps
141
148
  'OPENILINK_HUB_URL': '', # OpeniLink Hub 地址,默认为 https://hub.openilink.com,自建 Hub 时填写自己的地址
@@ -902,6 +909,53 @@ def wxpusher_bot(title: str, content: str) -> None:
902
909
  print(f"wxpusher 推送失败!错误信息:{response.get('msg')}")
903
910
 
904
911
 
912
+ def wxpusher_spt(title: str, content: str) -> None:
913
+ """
914
+ 通过 wxpusher 极简推送(SPT)推送消息。
915
+ 支持的环境变量:
916
+ - WXPUSHER_SPT_LIST: SPT, 多个用英文逗号,分隔, 最多10个
917
+ """
918
+ if not push_config.get("WXPUSHER_SPT_LIST"):
919
+ return
920
+
921
+ # 处理 SPT,将逗号分隔的字符串转为数组
922
+ spts = [
923
+ spt.strip()
924
+ for spt in push_config.get("WXPUSHER_SPT_LIST").split(",")
925
+ if spt.strip()
926
+ ]
927
+
928
+ if not spts:
929
+ print("wxpusher 服务的 WXPUSHER_SPT_LIST 不能为空!!")
930
+ return
931
+ if len(spts) > 10:
932
+ print("wxpusher 服务的 WXPUSHER_SPT_LIST 最多支持 10 个!!")
933
+ return
934
+
935
+ print("wxpusher SPT 服务启动")
936
+
937
+ url = "https://wxpusher.zjiecode.com/api/send/message/simple-push"
938
+
939
+ data = {
940
+ "content": f"<h1>{title}</h1><br/><div style='white-space: pre-wrap;'>{content}</div>",
941
+ "summary": title,
942
+ "contentType": 2,
943
+ }
944
+ # 单个 SPT 用 spt,多个用 sptList
945
+ if len(spts) == 1:
946
+ data["spt"] = spts[0]
947
+ else:
948
+ data["sptList"] = spts
949
+
950
+ headers = {"Content-Type": "application/json"}
951
+ response = requests.post(url=url, json=data, headers=headers).json()
952
+
953
+ if response.get("code") == 1000:
954
+ print("wxpusher SPT 推送成功!")
955
+ else:
956
+ print(f"wxpusher SPT 推送失败!错误信息:{response.get('msg')}")
957
+
958
+
905
959
  def openilink(title: str, content: str) -> None:
906
960
  """
907
961
  通过 OpeniLink 推送消息。
@@ -1104,6 +1158,8 @@ def add_notify_function():
1104
1158
  push_config.get("WXPUSHER_TOPIC_IDS") or push_config.get("WXPUSHER_UIDS")
1105
1159
  ):
1106
1160
  notify_function.append(wxpusher_bot)
1161
+ if push_config.get("WXPUSHER_SPT_LIST"):
1162
+ notify_function.append(wxpusher_spt)
1107
1163
  if push_config.get("OPENILINK_APP_TOKEN"):
1108
1164
  notify_function.append(openilink)
1109
1165
  if not notify_function:
@@ -47,7 +47,7 @@ exports.default = (app) => {
47
47
  .required()
48
48
  .pattern(/^[a-zA-Z_][0-9a-zA-Z_]*$/),
49
49
  remarks: celebrate_1.Joi.string().optional().allow(''),
50
- labels: celebrate_1.Joi.array().items(celebrate_1.Joi.string().trim()).optional(),
50
+ labels: celebrate_1.Joi.array().items(celebrate_1.Joi.string().trim()).optional().allow(null),
51
51
  })),
52
52
  }), async (req, res, next) => {
53
53
  var _a;
@@ -70,7 +70,7 @@ exports.default = (app) => {
70
70
  name: celebrate_1.Joi.string().required(),
71
71
  remarks: celebrate_1.Joi.string().optional().allow('').allow(null),
72
72
  id: celebrate_1.Joi.number().required(),
73
- labels: celebrate_1.Joi.array().items(celebrate_1.Joi.string().trim()).optional(),
73
+ labels: celebrate_1.Joi.array().items(celebrate_1.Joi.string().trim()).optional().allow(null),
74
74
  }),
75
75
  }), async (req, res, next) => {
76
76
  const logger = typedi_1.Container.get('logger');
@@ -10,6 +10,7 @@ const system_1 = __importDefault(require("../services/system"));
10
10
  const celebrate_1 = require("celebrate");
11
11
  const user_1 = __importDefault(require("../services/user"));
12
12
  const i18n_1 = require("../shared/i18n");
13
+ const auth_1 = require("../shared/auth");
13
14
  const util_1 = require("../config/util");
14
15
  const dayjs_1 = __importDefault(require("dayjs"));
15
16
  const multer_1 = __importDefault(require("multer"));
@@ -32,12 +33,7 @@ exports.default = (app) => {
32
33
  const userService = typedi_1.Container.get(user_1.default);
33
34
  const authInfo = await userService.getAuthInfo();
34
35
  const { version, changeLog, changeLogLink, publishTime } = await (0, util_1.parseVersion)(config_1.default.versionFile);
35
- let isInitialized = true;
36
- if (Object.keys(authInfo).length === 2 &&
37
- authInfo.username === 'admin' &&
38
- authInfo.password === 'admin') {
39
- isInitialized = false;
40
- }
36
+ const isInitialized = !(0, auth_1.isDefaultAuthInfo)(authInfo);
41
37
  res.send({
42
38
  code: 200,
43
39
  data: {
@@ -316,7 +312,10 @@ exports.default = (app) => {
316
312
  }), async (req, res, next) => {
317
313
  try {
318
314
  const userService = typedi_1.Container.get(user_1.default);
319
- await userService.resetAuthInfo(req.body);
315
+ const result = await userService.resetAuthInfo(req.body);
316
+ if (result) {
317
+ return res.send(result);
318
+ }
320
319
  res.send({ code: 200, message: (0, i18n_1.t)('更新成功') });
321
320
  }
322
321
  catch (e) {
@@ -72,37 +72,37 @@ class Application {
72
72
  // Wait for gRPC worker to signal it's ready before starting HTTP worker
73
73
  this.waitForWorkerReady(grpcWorker, 30000)
74
74
  .then(() => {
75
- logger_1.default.info('✌️ gRPC worker is ready, starting HTTP worker');
75
+ logger_1.default.info('[boot] gRPC worker is ready, starting HTTP worker');
76
76
  this.httpWorker = this.forkWorker('http');
77
77
  })
78
78
  .catch((error) => {
79
- logger_1.default.error('✌️ Failed to wait for gRPC worker:', error);
79
+ logger_1.default.error('[boot] Failed to wait for gRPC worker:', error);
80
80
  process.exit(1);
81
81
  });
82
82
  cluster_1.default.on('exit', (worker, code, signal) => {
83
83
  const metadata = this.workerMetadataMap.get(worker.id);
84
84
  if (metadata) {
85
85
  if (!this.isShuttingDown) {
86
- logger_1.default.error(`✌️ ${metadata.serviceType} worker ${worker.process.pid} died (${signal || code}). Restarting...`);
86
+ logger_1.default.error(`${metadata.serviceType} worker ${worker.process.pid} died (${signal || code}). Restarting...`);
87
87
  // If gRPC worker died, restart it and wait for it to be ready
88
88
  if (metadata.serviceType === 'grpc') {
89
89
  const newGrpcWorker = this.forkWorker('grpc');
90
90
  this.waitForWorkerReady(newGrpcWorker, 30000)
91
91
  .then(() => {
92
- logger_1.default.info('✌️ gRPC worker restarted and ready');
92
+ logger_1.default.info('gRPC worker restarted and ready');
93
93
  // Re-register cron jobs by notifying the HTTP worker
94
94
  if (this.httpWorker) {
95
95
  try {
96
96
  this.httpWorker.send('reregister-crons');
97
- logger_1.default.info('✌️ Sent reregister-crons message to HTTP worker');
97
+ logger_1.default.info('Sent reregister-crons message to HTTP worker');
98
98
  }
99
99
  catch (error) {
100
- logger_1.default.error('✌️ Failed to send reregister-crons message:', error);
100
+ logger_1.default.error('Failed to send reregister-crons message:', error);
101
101
  }
102
102
  }
103
103
  })
104
104
  .catch((error) => {
105
- logger_1.default.error('✌️ Failed to restart gRPC worker:', error);
105
+ logger_1.default.error('Failed to restart gRPC worker:', error);
106
106
  process.exit(1);
107
107
  });
108
108
  }
@@ -110,7 +110,7 @@ class Application {
110
110
  // For HTTP worker, just restart it
111
111
  const newWorker = this.forkWorker(metadata.serviceType);
112
112
  this.httpWorker = newWorker;
113
- logger_1.default.info(`✌️ Restarted ${metadata.serviceType} worker (PID: ${newWorker.process.pid})`);
113
+ logger_1.default.info(`Restarted ${metadata.serviceType} worker (PID: ${newWorker.process.pid})`);
114
114
  }
115
115
  }
116
116
  this.workerMetadataMap.delete(worker.id);
@@ -168,14 +168,14 @@ class Application {
168
168
  if (worker) {
169
169
  const exitPromise = new Promise((resolve) => {
170
170
  worker.once('exit', () => {
171
- logger_1.default.info(`✌️ Worker ${worker.process.pid} exited`);
171
+ logger_1.default.info(`Worker ${worker.process.pid} exited`);
172
172
  resolve();
173
173
  });
174
174
  try {
175
175
  worker.send('shutdown');
176
176
  }
177
177
  catch (error) {
178
- logger_1.default.warn(`✌️ Failed to send shutdown to worker ${worker.process.pid}:`, error);
178
+ logger_1.default.warn(`Failed to send shutdown to worker ${worker.process.pid}:`, error);
179
179
  }
180
180
  });
181
181
  workerPromises.push(exitPromise);
@@ -186,7 +186,7 @@ class Application {
186
186
  Promise.all(workerPromises),
187
187
  new Promise((resolve) => {
188
188
  setTimeout(() => {
189
- logger_1.default.warn('✌️ Worker shutdown timeout reached');
189
+ logger_1.default.warn('Worker shutdown timeout reached');
190
190
  resolve();
191
191
  }, 10000);
192
192
  }),
@@ -194,7 +194,7 @@ class Application {
194
194
  process.exit(0);
195
195
  }
196
196
  catch (error) {
197
- logger_1.default.error('✌️ Error during worker shutdown:', error);
197
+ logger_1.default.error('Error during worker shutdown:', error);
198
198
  process.exit(1);
199
199
  }
200
200
  };
@@ -205,10 +205,10 @@ class Application {
205
205
  var _a;
206
206
  const serviceType = process.env.SERVICE_TYPE;
207
207
  if (!serviceType || !['http', 'grpc'].includes(serviceType)) {
208
- logger_1.default.error('✌️ Invalid SERVICE_TYPE:', serviceType);
208
+ logger_1.default.error('[boot] Invalid SERVICE_TYPE:', serviceType);
209
209
  process.exit(1);
210
210
  }
211
- logger_1.default.info(`✌️ ${serviceType} worker started (PID: ${process.pid})`);
211
+ logger_1.default.info(`[boot] ${serviceType} worker started (PID: ${process.pid})`);
212
212
  try {
213
213
  if (serviceType === 'http') {
214
214
  await this.startHttpService();
@@ -219,7 +219,7 @@ class Application {
219
219
  (_a = process.send) === null || _a === void 0 ? void 0 : _a.call(process, 'ready');
220
220
  }
221
221
  catch (error) {
222
- logger_1.default.error(`✌️ ${serviceType} worker failed:`, error);
222
+ logger_1.default.error(`[boot] ${serviceType} worker failed:`, error);
223
223
  process.exit(1);
224
224
  }
225
225
  }
@@ -251,14 +251,14 @@ class Application {
251
251
  else if (msg === 'reregister-crons' && serviceType === 'http') {
252
252
  // Re-register cron jobs when gRPC worker restarts
253
253
  try {
254
- logger_1.default.info('✌️ Received reregister-crons message, re-registering cron jobs...');
254
+ logger_1.default.info('[boot] Received reregister-crons message, re-registering cron jobs...');
255
255
  const CronService = (await Promise.resolve().then(() => __importStar(require('./services/cron')))).default;
256
256
  const cronService = typedi_1.Container.get(CronService);
257
257
  await cronService.autosave_crontab();
258
- logger_1.default.info('✌️ Cron jobs re-registered successfully');
258
+ logger_1.default.info('[boot] Cron jobs re-registered successfully');
259
259
  }
260
260
  catch (error) {
261
- logger_1.default.error('✌️ Failed to re-register cron jobs:', error);
261
+ logger_1.default.error('[boot] Failed to re-register cron jobs:', error);
262
262
  }
263
263
  }
264
264
  });
@@ -281,7 +281,7 @@ class Application {
281
281
  process.exit(0);
282
282
  }
283
283
  catch (error) {
284
- logger_1.default.error(`✌️ [${serviceType}] Error during shutdown:`, error);
284
+ logger_1.default.error(`[${serviceType}] Error during shutdown:`, error);
285
285
  process.exit(1);
286
286
  }
287
287
  }
@@ -47,6 +47,7 @@ exports.NotificationModeStringMap = {
47
47
  18: 'chronocat',
48
48
  19: 'ntfy',
49
49
  20: 'wxPusherBot',
50
+ 21: 'wxPusherSpt',
50
51
  };
51
52
  exports.LINUX_DEPENDENCE_COMMAND = {
52
53
  Debian: {
@@ -52,7 +52,7 @@ async function tmpFile(prefix) {
52
52
  return path_1.default.join(dir, `.${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}.pem`);
53
53
  }
54
54
  async function generateAllCerts() {
55
- logger_1.default.info('Generating gRPC mTLS certificates...');
55
+ logger_1.default.info('[boot] Generating gRPC mTLS certificates...');
56
56
  const caKeyTmp = await tmpFile('ca_key');
57
57
  const caCertTmp = await tmpFile('ca_cert');
58
58
  const serverKeyTmp = await tmpFile('server_key');
@@ -92,7 +92,7 @@ async function generateAllCerts() {
92
92
  const clientCert = run(`openssl x509 -req -days 3650 -in '${clientCsrTmp}' -CA '${caCertTmp}' -CAkey '${caKeyTmp}' -CAcreateserial -extfile '${clientExtTmp}' 2>/dev/null`);
93
93
  const clientKey = await fs.readFile(clientKeyTmp, 'utf-8');
94
94
  await cleanup();
95
- logger_1.default.info('gRPC mTLS certificates generated successfully');
95
+ logger_1.default.info('[boot] gRPC mTLS certificates generated successfully');
96
96
  return { caCert, serverCert, serverKey, clientCert, clientKey };
97
97
  }
98
98
  catch (e) {
@@ -107,7 +107,7 @@ async function saveCerts(tlsConfig) {
107
107
  await fs.writeFile(serverKeyPath, tlsConfig.serverKey, { mode: 0o600 });
108
108
  await fs.writeFile(clientCertPath, tlsConfig.clientCert, { mode: 0o644 });
109
109
  await fs.writeFile(clientKeyPath, tlsConfig.clientKey, { mode: 0o600 });
110
- logger_1.default.info(`gRPC mTLS certificates saved to ${certDir}`);
110
+ logger_1.default.info(`[boot] gRPC mTLS certificates saved to ${certDir}`);
111
111
  }
112
112
  async function loadExistingCerts() {
113
113
  const exists = await Promise.all([
@@ -127,7 +127,7 @@ async function loadExistingCerts() {
127
127
  fs.readFile(clientCertPath, 'utf-8'),
128
128
  fs.readFile(clientKeyPath, 'utf-8'),
129
129
  ]);
130
- logger_1.default.info('Loaded existing gRPC mTLS certificates from disk');
130
+ logger_1.default.info('[boot] Loaded existing gRPC mTLS certificates from disk');
131
131
  return { caCert, serverCert, serverKey, clientCert, clientKey };
132
132
  }
133
133
  async function initGrpcCerts() {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpeniLinkNotification = exports.WxPusherBotNotification = exports.NtfyNotification = exports.LarkNotification = exports.WebhookNotification = exports.ChronocatNotification = exports.PushMeNotification = exports.EmailNotification = exports.WePlusBotNotification = exports.PushPlusNotification = exports.IGotNotification = exports.AibotkNotification = exports.WeWorkAppNotification = exports.WeWorkBotNotification = exports.DingtalkBotNotification = exports.TelegramBotNotification = exports.BarkNotification = exports.synologyChatNotification = exports.PushDeerNotification = exports.ServerChanNotification = exports.GoCqHttpBotNotification = exports.GotifyNotification = exports.NotificationMode = void 0;
3
+ exports.OpeniLinkNotification = exports.WxPusherSptNotification = exports.WxPusherBotNotification = exports.NtfyNotification = exports.LarkNotification = exports.WebhookNotification = exports.ChronocatNotification = exports.PushMeNotification = exports.EmailNotification = exports.WePlusBotNotification = exports.PushPlusNotification = exports.IGotNotification = exports.AibotkNotification = exports.WeWorkAppNotification = exports.WeWorkBotNotification = exports.DingtalkBotNotification = exports.TelegramBotNotification = exports.BarkNotification = exports.synologyChatNotification = exports.PushDeerNotification = exports.ServerChanNotification = exports.GoCqHttpBotNotification = exports.GotifyNotification = exports.NotificationMode = void 0;
4
4
  var NotificationMode;
5
5
  (function (NotificationMode) {
6
6
  NotificationMode["gotify"] = "gotify";
@@ -223,6 +223,13 @@ class WxPusherBotNotification extends NotificationBaseInfo {
223
223
  }
224
224
  }
225
225
  exports.WxPusherBotNotification = WxPusherBotNotification;
226
+ class WxPusherSptNotification extends NotificationBaseInfo {
227
+ constructor() {
228
+ super(...arguments);
229
+ this.wxPusherSptList = '';
230
+ }
231
+ }
232
+ exports.WxPusherSptNotification = WxPusherSptNotification;
226
233
  class OpeniLinkNotification extends NotificationBaseInfo {
227
234
  constructor() {
228
235
  super(...arguments);