@whyour/qinglong 2.21.0-6 → 2.21.0-7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/back/protos/api.proto +3 -0
- package/deploy/kubernetes/README.md +81 -0
- package/deploy/kubernetes/base/kustomization.yaml +5 -0
- package/deploy/kubernetes/base/qinglong.yaml +95 -0
- package/deploy/kubernetes/overlays/example/kustomization.yaml +10 -0
- package/package.json +1 -1
- package/sample/config.sample.sh +7 -2
- package/sample/notify.js +64 -0
- package/sample/notify.py +56 -0
- package/static/build/api/env.js +2 -2
- package/static/build/app.js +19 -19
- package/static/build/config/const.js +1 -0
- package/static/build/config/grpcCerts.js +4 -4
- package/static/build/data/notify.js +8 -1
- package/static/build/loaders/app.js +6 -6
- package/static/build/loaders/db.js +2 -2
- package/static/build/loaders/initFile.js +1 -1
- package/static/build/loaders/server.js +1 -1
- package/static/build/protos/api.js +23 -1
- package/static/build/schedule/addCron.js +67 -10
- package/static/build/schedule/delCron.js +11 -1
- package/static/build/services/cron.js +88 -24
- package/static/build/services/grpc.js +1 -1
- package/static/build/services/http.js +1 -1
- package/static/build/services/notify.js +42 -0
- package/static/build/shared/i18n.js +7 -0
- package/static/build/validation/schedule.js +9 -0
- package/static/dist/1147.62793604.async.js +1 -0
- package/static/dist/2208.85285476.async.js +1 -0
- package/static/dist/2286.f371c743.async.js +1 -0
- package/static/dist/8826.db34ad8d.async.js +1 -0
- package/static/dist/index.html +2 -2
- package/static/dist/{preload_helper.e0bef63b.js → preload_helper.0148f907.js} +1 -1
- package/static/dist/{src__pages__crontab__index.6b63ca05.async.js → src__pages__crontab__index.68e78a33.async.js} +1 -1
- package/static/dist/src__pages__dependence__modal.f7bb47ba.async.js +1 -0
- package/static/dist/src__pages__env__editNameModal.48715576.async.js +1 -0
- package/static/dist/src__pages__env__index.452cdc47.async.js +1 -0
- package/static/dist/src__pages__env__modal.e1234834.async.js +1 -0
- package/static/dist/src__pages__error__index.dedb2b90.async.js +1 -0
- package/static/dist/src__pages__initialization__index.22620a77.async.js +1 -0
- package/static/dist/src__pages__script__editNameModal.532a3044.async.js +1 -0
- package/static/dist/src__pages__script__index.1fc20f22.async.js +1 -0
- package/static/dist/src__pages__script__renameModal.5cdde919.async.js +1 -0
- package/static/dist/src__pages__script__saveModal.5ffed35f.async.js +1 -0
- package/static/dist/src__pages__script__setting.1a82c6d7.async.js +1 -0
- package/static/dist/src__pages__setting__appModal.43b7758a.async.js +1 -0
- package/static/dist/src__pages__setting__dependence.9c04c79a.async.js +1 -0
- package/static/dist/src__pages__setting__index.7e070838.async.js +1 -0
- package/static/dist/src__pages__setting__notification.bac481c2.async.js +1 -0
- package/static/dist/src__pages__setting__security.b7235753.async.js +1 -0
- package/static/dist/src__pages__setting__systemLog.b81a8c1d.async.js +1 -0
- package/static/dist/src__pages__subscription__modal.562448e2.async.js +1 -0
- package/static/dist/{umi.025a31b3.js → umi.19f46d39.js} +1 -1
- package/static/dist/1147.c420132e.async.js +0 -1
- package/static/dist/2208.f7ba3dfa.async.js +0 -1
- package/static/dist/2286.164bb089.async.js +0 -1
- package/static/dist/8826.6879b271.async.js +0 -1
- package/static/dist/src__pages__dependence__modal.11124896.async.js +0 -1
- package/static/dist/src__pages__env__editNameModal.5d264b25.async.js +0 -1
- package/static/dist/src__pages__env__index.9a565d2b.async.js +0 -1
- package/static/dist/src__pages__env__modal.7f2ef1bc.async.js +0 -1
- package/static/dist/src__pages__error__index.f156b45e.async.js +0 -1
- package/static/dist/src__pages__initialization__index.e96d4ba8.async.js +0 -1
- package/static/dist/src__pages__script__editNameModal.3e7e100d.async.js +0 -1
- package/static/dist/src__pages__script__index.e869812f.async.js +0 -1
- package/static/dist/src__pages__script__renameModal.5e987ef5.async.js +0 -1
- package/static/dist/src__pages__script__saveModal.3f9d23d6.async.js +0 -1
- package/static/dist/src__pages__script__setting.a535793a.async.js +0 -1
- package/static/dist/src__pages__setting__appModal.251cd14f.async.js +0 -1
- package/static/dist/src__pages__setting__dependence.29f13e8c.async.js +0 -1
- package/static/dist/src__pages__setting__index.7a39d415.async.js +0 -1
- package/static/dist/src__pages__setting__notification.390fc905.async.js +0 -1
- package/static/dist/src__pages__setting__security.598720a8.async.js +0 -1
- package/static/dist/src__pages__setting__systemLog.1a5c855f.async.js +0 -1
- package/static/dist/src__pages__subscription__modal.3562c670.async.js +0 -1
package/back/protos/api.proto
CHANGED
|
@@ -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,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
|
package/package.json
CHANGED
package/sample/config.sample.sh
CHANGED
|
@@ -241,14 +241,19 @@ export NTFY_PASSWORD=""
|
|
|
241
241
|
export NTFY_ACTIONS=""
|
|
242
242
|
|
|
243
243
|
## 21. wxPusher
|
|
244
|
-
|
|
245
|
-
|
|
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:
|
package/static/build/api/env.js
CHANGED
|
@@ -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');
|
package/static/build/app.js
CHANGED
|
@@ -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('
|
|
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('
|
|
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(
|
|
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('
|
|
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('
|
|
97
|
+
logger_1.default.info('Sent reregister-crons message to HTTP worker');
|
|
98
98
|
}
|
|
99
99
|
catch (error) {
|
|
100
|
-
logger_1.default.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('
|
|
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(
|
|
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(
|
|
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(
|
|
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('
|
|
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('
|
|
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('
|
|
208
|
+
logger_1.default.error('[boot] Invalid SERVICE_TYPE:', serviceType);
|
|
209
209
|
process.exit(1);
|
|
210
210
|
}
|
|
211
|
-
logger_1.default.info(
|
|
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(
|
|
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('
|
|
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('
|
|
258
|
+
logger_1.default.info('[boot] Cron jobs re-registered successfully');
|
|
259
259
|
}
|
|
260
260
|
catch (error) {
|
|
261
|
-
logger_1.default.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(
|
|
284
|
+
logger_1.default.error(`[${serviceType}] Error during shutdown:`, error);
|
|
285
285
|
process.exit(1);
|
|
286
286
|
}
|
|
287
287
|
}
|
|
@@ -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);
|
|
@@ -12,16 +12,16 @@ const initTask_1 = __importDefault(require("./initTask"));
|
|
|
12
12
|
const initFile_1 = __importDefault(require("./initFile"));
|
|
13
13
|
exports.default = async ({ app }) => {
|
|
14
14
|
(0, depInjector_1.default)();
|
|
15
|
-
logger_1.default.info('
|
|
15
|
+
logger_1.default.info('[boot] Dependency loaded');
|
|
16
16
|
await (0, deps_1.default)();
|
|
17
|
-
logger_1.default.info('
|
|
17
|
+
logger_1.default.info('[boot] Link deps loaded');
|
|
18
18
|
await (0, initFile_1.default)();
|
|
19
|
-
logger_1.default.info('
|
|
19
|
+
logger_1.default.info('[boot] Init file loaded');
|
|
20
20
|
await (0, initData_1.default)();
|
|
21
|
-
logger_1.default.info('
|
|
21
|
+
logger_1.default.info('[boot] Init data loaded');
|
|
22
22
|
(0, initTask_1.default)();
|
|
23
|
-
logger_1.default.info('
|
|
23
|
+
logger_1.default.info('[boot] Init task loaded');
|
|
24
24
|
(0, express_1.default)({ app });
|
|
25
|
-
logger_1.default.info('
|
|
25
|
+
logger_1.default.info('[boot] Express loaded');
|
|
26
26
|
};
|
|
27
27
|
//# sourceMappingURL=app.js.map
|