Compare commits

...

40 Commits

Author SHA1 Message Date
xiaojunnuo fa14f62198 v1.37.16 2025-12-16 01:46:13 +08:00
xiaojunnuo 5526665494 build: prepare to build 2025-12-16 01:44:10 +08:00
xiaojunnuo 6249af996a build: prepare to build 2025-12-16 01:41:26 +08:00
xiaojunnuo e51a1b365e build: prepare to build 2025-12-16 01:30:58 +08:00
xiaojunnuo f53f00d126 chore: 1 2025-12-16 00:39:21 +08:00
xiaojunnuo ab8fbaf21d chore: 修复清除定时任务未生效的bug 2025-12-16 00:34:32 +08:00
xiaojunnuo 63d8bcf882 perf: 批量设置定时,支持清除定时 2025-12-16 00:21:31 +08:00
xiaojunnuo e4e16bc6a6 fix: 修复ipv6作为证书域名申请证书校验失败的bug 2025-12-15 23:34:47 +08:00
xiaojunnuo e4c21c4d5c chore: 模版发邮件 plus 2025-12-15 22:32:25 +08:00
xiaojunnuo d9e6dbf889 chore: 优化telegram更多保留字符 2025-12-15 22:21:43 +08:00
xiaojunnuo 16f6365b18 chore: oidc自动注册时增加邮箱 2025-12-15 00:23:35 +08:00
xiaojunnuo cdab54bf51 chore: 1 2025-12-15 00:21:42 +08:00
xiaojunnuo b6fea0c856 perf: oidc支持使用第三方昵称或账号作为certd用户的用户名 2025-12-15 00:19:55 +08:00
xiaojunnuo 6f186932cc perf: 支持彩虹聚合登录 2025-12-15 00:12:27 +08:00
xiaojunnuo de544ec725 chore: email template优化 2025-12-14 23:19:32 +08:00
xiaojunnuo a6c0d2c6f1 perf: 支持邮件模版设置 2025-12-14 01:36:20 +08:00
xiaojunnuo 437d956cad chore: email template 2025-12-12 23:39:09 +08:00
xiaojunnuo 43ba0b9da6 docs: 1panel增加应用商店部署方式 2025-12-11 18:14:15 +08:00
xiaojunnuo fe1e2c3b62 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2025-12-10 14:17:20 +08:00
xiaojunnuo bbe7e5f96d chore: 1 2025-12-10 14:15:39 +08:00
xiaojunnuo 2bfad9fc65 fix: 优化西部数据 500 already exists 的问题 2025-12-09 23:33:11 +08:00
xiaojunnuo 9f24c18f7f chore: 优化数据库脚本 2025-12-09 23:28:29 +08:00
xiaojunnuo a2d1e5ea03 chore: 修复sqlite语句双引号改单引号 2025-12-09 23:11:19 +08:00
xiaojunnuo b082e4e988 chore: 1 2025-12-09 00:47:08 +08:00
xiaojunnuo 45fbce0c2a perf: 新增数据库迁移doc说明文档,优化datetime字段平滑迁移 2025-12-09 00:45:10 +08:00
xiaojunnuo ff7006e232 build: release 2025-12-07 01:17:22 +08:00
xiaojunnuo c68fdef0e4 build: publish 2025-12-07 00:59:50 +08:00
xiaojunnuo 4c60e4edc1 build: trigger build image 2025-12-07 00:59:39 +08:00
xiaojunnuo f2e4e59f8d v1.37.15 2025-12-07 00:58:12 +08:00
xiaojunnuo 898205b5b1 build: prepare to build 2025-12-07 00:56:13 +08:00
xiaojunnuo 8ec6862861 chore: 升级fs 2025-12-07 00:56:06 +08:00
xiaojunnuo c3ba6322d8 build: prepare to build 2025-12-07 00:55:38 +08:00
xiaojunnuo e589828425 build: prepare to build 2025-12-07 00:47:24 +08:00
xiaojunnuo c909aa161b chore: webhook修改为隐藏变量,避免别人fork后触发我的流水线 2025-12-07 00:18:05 +08:00
xiaojunnuo 5cee7d44f1 perf: 第三方登录支持gitee 2025-12-06 17:25:02 +08:00
xiaojunnuo 973b323a99 docs: 优化教程 2025-12-06 16:24:19 +08:00
xiaojunnuo d55954a363 perf: 支持k8s apply 2025-12-05 02:05:27 +08:00
xiaojunnuo adca151e4f perf: 邮件模版安全优化 2025-12-05 00:45:56 +08:00
xiaojunnuo 43513049be perf: 支持部署到中国移动CDN 2025-12-04 00:46:25 +08:00
xiaojunnuo a5ca41131b fix: oidc 支持nonce 2025-12-03 22:00:35 +08:00
127 changed files with 2330 additions and 389 deletions
+2 -1
View File
@@ -44,7 +44,8 @@ jobs:
- name: deploy-certd-demo
uses: tyrrrz/action-http-request@master
with:
url: http://flow-openapi.aliyun.com/pipeline/webhook/lzCzlGrLCOHQaTMMt0mG
# 通过webhook 触发 certd-demo来部署
url: ${{ secrets.WEBHOOK_CERTD_DEMO }}
method: POST
headers: |
Content-Type: application/json
+4 -2
View File
@@ -121,10 +121,12 @@ jobs:
- name: deploy-certd-doc
uses: tyrrrz/action-http-request@master
with:
url: http://flow-openapi.aliyun.com/pipeline/webhook/IiSxLDp9aOhgDUxJPytv
url: ${{ secrets.WEBHOOK_CERTD_DOC }}
method: POST
body: |
{}
{
"CERTD_VERSION": "1.0.0"
}
headers: |
Content-Type: application/json
retry-count: 3
+2 -1
View File
@@ -9,5 +9,6 @@
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"editor.tabSize": 2
"editor.tabSize": 2,
"explorer.autoReveal": false
}
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
### Bug Fixes
* 修复ipv6作为证书域名申请证书校验失败的bug ([e4e16bc](https://github.com/certd/certd/commit/e4e16bc6a65bb082c18ca0590226f0987a47d477))
* 优化西部数据 500 already exists 的问题 ([2bfad9f](https://github.com/certd/certd/commit/2bfad9fc651da208b610abd921fbfb2fbc04203f))
### Performance Improvements
* 批量设置定时,支持清除定时 ([63d8bcf](https://github.com/certd/certd/commit/63d8bcf8823f713365042d3c7aee3cf31d44b044))
* 新增数据库迁移doc说明文档,优化datetime字段平滑迁移 ([45fbce0](https://github.com/certd/certd/commit/45fbce0c2af5fb3ead6d3dd12a42f8cc1714262f))
* 支持彩虹聚合登录 ([6f18693](https://github.com/certd/certd/commit/6f186932ccad4becfdc0087c0539f7b2d0069844))
* 支持邮件模版设置 ([a6c0d2c](https://github.com/certd/certd/commit/a6c0d2c6f1fd6b60e6d7af290487c94564fd91ea))
* oidc支持使用第三方昵称或账号作为certd用户的用户名 ([b6fea0c](https://github.com/certd/certd/commit/b6fea0c8562abf912daa7d72958ceb2e93575d31))
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
### Bug Fixes
* oidc 支持nonce ([a5ca411](https://github.com/certd/certd/commit/a5ca41131b308b36b17ca359d9709ea8e9b7cee1))
### Performance Improvements
* 第三方登录支持gitee ([5cee7d4](https://github.com/certd/certd/commit/5cee7d44f17bd36972f477bc1f270999da558d05))
* 邮件模版安全优化 ([adca151](https://github.com/certd/certd/commit/adca151e4f07a4c6a2a753bfa48ee0d4d6469fd2))
* 支持部署到中国移动CDN ([4351304](https://github.com/certd/certd/commit/43513049beff407558d2a234415521464165cebc))
* 支持k8s apply ([d55954a](https://github.com/certd/certd/commit/d55954a36391ebe6a9397ff7dcfb710193ac5e34))
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
### Bug Fixes
+13
View File
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
### Bug Fixes
* oidc 支持nonce ([a5ca411](https://github.com/certd/certd/commit/a5ca41131b308b36b17ca359d9709ea8e9b7cee1))
### Performance Improvements
* 第三方登录支持gitee ([5cee7d4](https://github.com/certd/certd/commit/5cee7d44f17bd36972f477bc1f270999da558d05))
* 邮件模版安全优化 ([adca151](https://github.com/certd/certd/commit/adca151e4f07a4c6a2a753bfa48ee0d4d6469fd2))
* 支持部署到中国移动CDN ([4351304](https://github.com/certd/certd/commit/43513049beff407558d2a234415521464165cebc))
* 支持k8s apply ([d55954a](https://github.com/certd/certd/commit/d55954a36391ebe6a9397ff7dcfb710193ac5e34))
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
### Bug Fixes
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+33 -4
View File
@@ -7,7 +7,36 @@ https://1panel.cn/docs/installation/online_installation/
## 二、部署certd
有两种安装方式
### 1. 应用商店方式安装【推荐】
#### 1.1 安装
打开`1Panel->应用商店`,更新远程应用,搜索`certd`,点击安装
![](./images/store-1.png)
![](./images/store-2.png)
#### 1.2 访问测试:
http://ip:7001
https://ip:7002
默认账号密码
admin/123456
登录后请及时修改密码
#### 1.3 备份
![](./images/store-3.png)
#### 1.4 恢复
安装新Certd后,点击导入备份按钮,选择上面备份的文件即可
### 2. docker-compose方式安装
#### 2.1 安装
1. 打开`docker-compose.yaml`,整个内容复制下来
https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
@@ -22,7 +51,7 @@ https://1panel.cn/docs/installation/online_installation/
> 默认使用sqlite数据库,数据保存在`/data/certd`目录下,您可以手动备份该目录
> certd还支持`mysql`和`postgresql`数据库,[点我了解如何切换其他数据库](../database)
3. 访问测试
#### 2.2 访问测试
http://ip:7001
https://ip:7002
@@ -30,7 +59,7 @@ https://ip:7002
admin/123456
登录后请及时修改密码
## 三、升级
#### 2.3 升级
1. 找到容器,点击更多->升级
![](./images/upgrade-1.png)
@@ -39,11 +68,11 @@ admin/123456
![img.png](./images/upgrade-2.png)
## 四、数据备份
#### 2.4 备份
> 默认数据保存在`/data/certd`目录下,可以手动备份
> 建议配置一条 [数据库备份流水线](../../use/backup/),自动备份
## 五、备份恢复
#### 2.5 恢复
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
+50 -5
View File
@@ -65,9 +65,54 @@ docker-compose up -d
## 二、从旧版的sqlite切换数据库
1. 先将`旧certd`升级到最新版 `建议:备份sqlite数据库`
2. 按照上面全新安装方式部署一套`新的certd` `注意:新旧版本的certd要一致`
3. 使用数据库工具将数据从sqlite导入到mysql或postgresql `注意:flyway_history数据表不要导入`
4. 重启新certd
5. 确认没有问题之后,删除旧版certd
从旧版`sqlite`迁移到`mysql``postgresql`数据库
下面以 `SQLite``MySQL` 为例进行演示
![db-0.png](images/db-0.png)
#### 0.前提条件:
1. SQLite版Certd站点已经`升级到最新版` `建议:备份sqlite数据库`
2. `全新安装`MySQL版本Certd`确保是全新的,因为里面的数据会被清空覆盖`
3. 两套Certd站点版本一致
#### 1. 安装DBeaver工具
[https://dbeaver.io/download/](https://dbeaver.io/download/)
![db-1.png](images/db-1.png)
#### 2. 连接到sqlite数据库
![db-2.png](images/db-sqlite-1.png)
![db-3.png](images/db-sqlite-2.png)
#### 3. 连接到mysql或postgresql数据库
![db-4.png](images/db-mysql-1.png)
![db-5.png](images/db-mysql-2.png)
#### 4. 开始同步数据
选择mysql数据库,选择所有的表(`flyway_history除外`),右键导入数据
> 切记flyway_history数据表不要导入
![db-6.png](images/db-sync-1.jpg)
![db-7.png](images/db-sync-2.png)
![db-8.png](images/db-sync-3.png)
下一步、下一步,直到数据加载设置,勾选`在加载前截断目标表`(此选项很重要,并且会清空mysql certd数据库中的数据)
![db-7.png](images/db-sync-4.png)
#### 5. 导入完成
![db-9.png](images/db-success.png)
#### 6. 重启MySQL版本Certd
访问MySQL版本测试,数据已成功迁移
确认没有问题之后,删除旧版certd
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

+2 -1
View File
@@ -64,7 +64,8 @@
| 60.| **新网授权** | |
| 61.| **新网授权(代理方式)** | |
| 62.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 |
| 63.| **雨云授权** | https://app.rainyun.com/ |
| 63.| **中国移动CND授权** | |
| 64.| **雨云授权** | https://app.rainyun.com/ |
<style module>
table th:first-of-type {
+12 -10
View File
@@ -1,5 +1,5 @@
# 任务插件
`103` 款任务插件
`105` 款任务插件
## 1. 证书申请
| 序号 | 名称 | 说明 |
@@ -42,6 +42,7 @@
| 19.| **网宿-更新证书** | 网宿证书自动更新 |
| 20.| **金山云-更新CDN证书** | 金山云自动更新CDN证书 |
| 21.| **APISIX-更新证书** | 自动更新APISIX证书 |
| 22.| **中国移动-部署证书到CDN** | 中国移动自动部署证书到CDN |
## 4. 面板
| 序号 | 名称 | 说明 |
@@ -54,15 +55,16 @@
| 6.| **群晖-部署证书到群晖面板** | Synology,支持6.x以上版本 |
| 7.| **K8S-部署证书到Secret** | 部署证书到k8s的secret |
| 8.| **K8S-Ingress 证书部署** | 部署证书到k8s的Ingress |
| 9.| **1Panel-部署证书到1Panel** | 更新1Panel的证书 |
| 10.| **Plesk-部署Plesk网站证书** | |
| 11.| **雷池-更新证书** | 更新长亭雷池WAF的证书 |
| 12.| **lucky-更新Lucky证书** | |
| 13.| **uniCloud-部署到服务空间** | 部署到服务空间 |
| 14.| **威联通-部署证书到威联通** | 部署证书到qnap |
| 15.| **飞牛NAS-部署证书** | |
| 16.| **Proxmox-上传证书到Proxmox** | |
| 17.| **Dokploy-部署server证书** | 自动更新Dokploy server证书 |
| 9.| **K8S-Apply自定义yaml** | apply自定义yaml到k8s |
| 10.| **1Panel-部署证书到1Panel** | 更新1Panel的证书 |
| 11.| **Plesk-部署Plesk网站证书** | |
| 12.| **雷池-更新证书** | 更新长亭雷池WAF的证书 |
| 13.| **lucky-更新Lucky证书** | |
| 14.| **uniCloud-部署到服务空间** | 部署到服务空间 |
| 15.| **威联通-部署证书到威联通** | 部署证书到qnap |
| 16.| **飞牛NAS-部署证书** | |
| 17.| **Proxmox-上传证书到Proxmox** | |
| 18.| **Dokploy-部署server证书** | 自动更新Dokploy server证书 |
## 5. 阿里云
| 序号 | 名称 | 说明 |
+1 -1
View File
@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.37.14"
"version": "1.37.16"
}
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/publishlab/node-acme-client/compare/v1.37.15...v1.37.16) (2025-12-15)
### Bug Fixes
* 修复ipv6作为证书域名申请证书校验失败的bug ([e4e16bc](https://github.com/publishlab/node-acme-client/commit/e4e16bc6a65bb082c18ca0590226f0987a47d477))
## [1.37.15](https://github.com/publishlab/node-acme-client/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/acme-client
## [1.37.14](https://github.com/publishlab/node-acme-client/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/acme-client
+3 -3
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.37.14",
"version": "1.37.16",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.37.14",
"@certd/basic": "^1.37.16",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
@@ -70,5 +70,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+7 -3
View File
@@ -8,7 +8,7 @@ import {log as defaultLog} from './logger.js'
import axios from './axios.js'
import * as util from './util.js'
import {isAlpnCertificateAuthorizationValid} from './crypto/index.js'
import {utils} from '@certd/basic'
const dns = dnsSdk.promises
@@ -60,11 +60,15 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
}
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
const challengeUrl = `http://${authz.identifier.value}:${httpPort}${suffix}`;
let host = authz.identifier.value;
if(utils.domain.isIpv6(host)){
host = `[${host}]`;
}
const challengeUrl = `http://${host}:${httpPort}${suffix}`;
if (!await doQuery(challengeUrl)) {
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
const httpsChallengeUrl = `https://${authz.identifier.value}:${httpsPort}${suffix}`;
const httpsChallengeUrl = `https://${host}:${httpsPort}${suffix}`;
const res = await doQuery(httpsChallengeUrl)
if (!res) {
throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
+1
View File
@@ -26,3 +26,4 @@ dist-ssr
test/user.secret.*
test/**/*.js
src/**/*.spec.ts
test.mjs
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
### Bug Fixes
* 修复ipv6作为证书域名申请证书校验失败的bug ([e4e16bc](https://github.com/certd/certd/commit/e4e16bc6a65bb082c18ca0590226f0987a47d477))
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/basic
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/basic
+1 -1
View File
@@ -1 +1 @@
00:57
01:44
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.37.14",
"version": "1.37.16",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -47,5 +47,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+1 -1
View File
@@ -58,7 +58,7 @@ function isIpv6(d: string) {
if (!d) {
return false;
}
const isIPv6Regex = /^([\da-f]{1,4}:){2,7}[\da-f]{1,4}$/i;
const isIPv6Regex = /^([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{1,4}$|((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})$/gm;
return isIPv6Regex.test(d);
}
+1 -1
View File
@@ -1,4 +1,4 @@
export function isDev() {
const nodeEnv = process.env.NODE_ENV || '';
const nodeEnv = process.env.NODE_ENV || 'dev';
return nodeEnv === 'development' || nodeEnv.includes('local') || nodeEnv.startsWith('dev');
}
+16 -12
View File
@@ -1,14 +1,18 @@
import { random } from "lodash-es";
import { locker } from "./dist/utils/util.lock.js";
// import { random } from "lodash-es";
// import { locker } from "./dist/utils/util.lock.js";
async function testLocker() {
for (let i = 0; i < 10; i++) {
await locker.execute("test", async () => {
console.log("test", i);
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
throw new Error("test error");
});
}
}
// async function testLocker() {
// for (let i = 0; i < 10; i++) {
// await locker.execute("test", async () => {
// console.log("test", i);
// await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
// throw new Error("test error");
// });
// }
// }
await testLocker();
// await testLocker();
import { domainUtils } from "./dist/utils/util.domain.js";
console.log(domainUtils.isIpv6("::0:0:0:FFFF:129.144.52.38"));
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
### Performance Improvements
* 支持邮件模版设置 ([a6c0d2c](https://github.com/certd/certd/commit/a6c0d2c6f1fd6b60e6d7af290487c94564fd91ea))
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/pipeline
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/pipeline
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.37.14",
"version": "1.37.16",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -18,8 +18,8 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.37.14",
"@certd/plus-core": "^1.37.14",
"@certd/basic": "^1.37.16",
"@certd/plus-core": "^1.37.16",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+29 -12
View File
@@ -423,31 +423,46 @@ export class Executor {
let subject = "";
let content = "";
const errorMessage = error?.message;
const templateData: any = {
pipelineId: this.pipeline.id,
historyId: this.runtime.id,
pipelineTitle: this.pipeline.title,
};
let pipelineResult = "";
let errors = "";
if (when === "start") {
subject = `开始执行,${this.pipeline.title}${this.pipeline.id}`;
pipelineResult = "开始执行";
subject = `${pipelineResult}${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
} else if (when === "success") {
subject = `执行成功,${this.pipeline.title}${this.pipeline.id}`;
pipelineResult = "执行成功";
subject = `${pipelineResult}${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
} else if (when === "turnToSuccess") {
subject = `执行成功(失败转成功)${this.pipeline.title}${this.pipeline.id}`;
pipelineResult = "执行成功(失败转成功)";
subject = `${pipelineResult}${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
} else if (when === "error") {
subject = `执行失败,${this.pipeline.title}${this.pipeline.id}`;
pipelineResult = "执行失败";
subject = `${pipelineResult}${this.pipeline.title}${this.pipeline.id}`;
if (error instanceof RunnableError) {
const runnableError = error as RunnableError;
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n`;
for (const re of runnableError.errors) {
content += ` - ${re.runnable.title} 执行失败,错误详情:${re.e?.message || re.e?.error?.message}\n\n`;
errors += ` - ${re.runnable.title} 执行失败,错误详情:${re.e?.message || re.e?.error?.message}\n\n`;
}
content += errors;
} else {
errors = error.message;
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`;
}
} else {
return;
}
templateData.errors = errors;
templateData.pipelineResult = pipelineResult;
for (const notification of this.pipeline.notifications) {
if (!notification.when.includes(when)) {
continue;
@@ -455,10 +470,12 @@ export class Executor {
if (notification.type === "email" && notification.options?.receivers) {
try {
await this.options.emailService?.send({
subject,
content,
receivers: notification.options?.receivers,
await this.options.emailService?.sendByTemplate({
type: "pipelineResult",
data: templateData,
email: {
receivers: notification.options?.receivers,
},
});
} catch (e) {
logger.error("send email error", e);
@@ -472,15 +489,15 @@ export class Executor {
useEmail: false,
logger: this.logger,
body: {
notificationType: "pipelineResult",
title: subject,
content,
userId: this.pipeline.userId,
pipeline: this.pipeline,
result: this.lastRuntime?.pipeline?.status,
pipelineId: this.pipeline.id,
historyId: this.runtime.id,
errorMessage,
url,
...templateData,
},
});
} catch (e) {
@@ -15,6 +15,11 @@ export type NotificationBody = {
historyId?: number;
errorMessage?: string;
url?: string;
notificationType?: string;
attachments?: any[];
pipelineResult?: string;
pipelineTitle?: string;
errors?: string;
};
export type NotificationRequestHandleReqInput<T = any> = {
@@ -6,6 +6,13 @@ export type EmailSend = {
html?: string;
};
export type EmailSendByTemplateReq = {
type: string;
data: any;
email: { receivers: string[]; attachments?: any[] };
};
export interface IEmailService {
send(email: EmailSend): Promise<void>;
sendByTemplate(req: EmailSendByTemplateReq): Promise<void>;
}
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
**Note:** Version bump only for package @certd/lib-huawei
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/lib-huawei
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/lib-huawei
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.37.14",
"version": "1.37.16",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -24,5 +24,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
**Note:** Version bump only for package @certd/lib-iframe
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/lib-iframe
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/lib-iframe
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.37.14",
"version": "1.37.16",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
**Note:** Version bump only for package @certd/jdcloud
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/jdcloud
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/jdcloud
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.37.14",
"version": "1.37.16",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -56,5 +56,5 @@
"fetch"
]
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
**Note:** Version bump only for package @certd/lib-k8s
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
### Performance Improvements
* 邮件模版安全优化 ([adca151](https://github.com/certd/certd/commit/adca151e4f07a4c6a2a753bfa48ee0d4d6469fd2))
* 支持k8s apply ([d55954a](https://github.com/certd/certd/commit/d55954a36391ebe6a9397ff7dcfb710193ac5e34))
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/lib-k8s
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.37.14",
"version": "1.37.16",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,7 +17,7 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.37.14",
"@certd/basic": "^1.37.16",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -32,5 +32,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+34 -9
View File
@@ -1,4 +1,4 @@
import { CoreV1Api, KubeConfig, NetworkingV1Api, V1Ingress, V1Secret } from "@kubernetes/client-node";
import { CoreV1Api, KubeConfig, NetworkingV1Api, V1Ingress, V1Secret, KubernetesObjectApi, loadYaml, KubernetesObject } from "@kubernetes/client-node";
import dns from "dns";
import { ILogger } from "@certd/basic";
import { merge } from "lodash-es";
@@ -27,6 +27,11 @@ export class K8sClient {
}
init() {
const kubeconfig = this.getKubeConfig();
this.client = kubeconfig.makeApiClient(CoreV1Api);
}
getKubeConfig() {
const kubeconfig = new KubeConfig();
kubeconfig.loadFromString(this.kubeConfigStr);
this.kubeconfig = kubeconfig;
@@ -41,16 +46,35 @@ export class K8sClient {
} catch (e) {
this.logger.warn("skipTLSVerify error", e);
}
return kubeconfig;
}
this.client = kubeconfig.makeApiClient(CoreV1Api);
getKubeClient() {
const kc = this.getKubeConfig();
const client = KubernetesObjectApi.makeApiClient(kc);
return client;
}
// const reqOpts = { kubeconfig, request: {} } as any;
// if (this.lookup) {
// reqOpts.request.lookup = this.lookup;
// }
//
// const backend = new Request(reqOpts);
// this.client = new Client({ backend, version: '1.13' });
async apply(manifest: string) {
const yml = loadYaml<KubernetesObject>(manifest);
const client = this.getKubeClient();
try {
await client.create(yml);
} catch (e) {
this.logger.error("apply error", e.response?.body);
if (e.response?.body?.reason === "AlreadyExists") {
//patch
this.logger.info("patch existing resource: ", yml.metadata?.name);
const existing = await client.read(yml as any);
if (!yml.metadata) {
yml.metadata = {};
}
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
await client.patch(yml);
return;
}
throw e;
}
}
/**
@@ -168,6 +192,7 @@ export class K8sClient {
const oldIngress = await client.readNamespacedIngress(ingressName, namespace);
const newIngress = merge(oldIngress.body, opts.body);
const res = await client.replaceNamespacedIngress(ingressName, namespace, newIngress);
this.logger.info("ingress patched", opts.body);
return res;
}
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
### Bug Fixes
* 修复ipv6作为证书域名申请证书校验失败的bug ([e4e16bc](https://github.com/certd/certd/commit/e4e16bc6a65bb082c18ca0590226f0987a47d477))
### Performance Improvements
* 支持邮件模版设置 ([a6c0d2c](https://github.com/certd/certd/commit/a6c0d2c6f1fd6b60e6d7af290487c94564fd91ea))
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/lib-server
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/lib-server
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.37.14",
"version": "1.37.16",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -28,11 +28,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.37.14",
"@certd/basic": "^1.37.14",
"@certd/pipeline": "^1.37.14",
"@certd/plugin-lib": "^1.37.14",
"@certd/plus-core": "^1.37.14",
"@certd/acme-client": "^1.37.16",
"@certd/basic": "^1.37.16",
"@certd/pipeline": "^1.37.16",
"@certd/plugin-lib": "^1.37.16",
"@certd/plus-core": "^1.37.16",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",
@@ -64,5 +64,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
@@ -108,6 +108,11 @@ export class SysLicenseInfo extends BaseSettings {
license?: string;
}
export type EmailTemplate = {
addonId?: number;
}
export class SysEmailConf extends BaseSettings {
static __title__ = '邮箱配置';
static __key__ = 'sys.email';
@@ -126,6 +131,13 @@ export class SysEmailConf extends BaseSettings {
};
sender: string;
usePlus?: boolean;
templates:{
registerCode?: EmailTemplate,
forgotPassword?: EmailTemplate,
pipelineResult?: EmailTemplate,
common?: EmailTemplate,
}
}
export class SysSiteInfo extends BaseSettings {
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/midway-flyway-js
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.37.14",
"version": "1.37.16",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
### Bug Fixes
* 修复ipv6作为证书域名申请证书校验失败的bug ([e4e16bc](https://github.com/certd/certd/commit/e4e16bc6a65bb082c18ca0590226f0987a47d477))
### Performance Improvements
* 支持邮件模版设置 ([a6c0d2c](https://github.com/certd/certd/commit/a6c0d2c6f1fd6b60e6d7af290487c94564fd91ea))
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/plugin-cert
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/plugin-cert
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.37.14",
"version": "1.37.16",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,10 +17,10 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/acme-client": "^1.37.14",
"@certd/basic": "^1.37.14",
"@certd/pipeline": "^1.37.14",
"@certd/plugin-lib": "^1.37.14",
"@certd/acme-client": "^1.37.16",
"@certd/basic": "^1.37.16",
"@certd/pipeline": "^1.37.16",
"@certd/plugin-lib": "^1.37.16",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -43,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
@@ -21,6 +21,11 @@ export class DomainParser implements IDomainParser {
}
async parse(fullDomain: string) {
//如果是ip
if (utils.domain.isIp(fullDomain)) {
return fullDomain;
}
this.logger.info(`查找主域名:${fullDomain}`);
const cacheKey = `domain_parse:${fullDomain}`;
const value = utils.cache.get(cacheKey);
@@ -154,6 +154,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
title: `证书申请成功【${this.pipeline.title}`,
content: `域名:${this.domains.join(",")}`,
url: url,
notificationType: "certApplySuccess",
};
try {
await this.ctx.notificationService.send({
@@ -220,7 +220,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
if(form.challengeType === 'cname' ){
return '请按照上面的提示,给要申请证书的域名添加CNAME记录,添加后,点击验证,验证成功后不要删除记录,申请和续期证书会一直用它'
}else if (form.challengeType === 'http'){
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录下'
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录的.well-known/acme-challenge/目录下'
}else if (form.challengeType === 'http'){
return '给每个域名单独配置dns提供商'
}
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
**Note:** Version bump only for package @certd/plugin-lib
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
**Note:** Version bump only for package @certd/plugin-lib
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/plugin-lib
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.37.14",
"version": "1.37.16",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -22,8 +22,8 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-s3": "^3.787.0",
"@certd/basic": "^1.37.14",
"@certd/pipeline": "^1.37.14",
"@certd/basic": "^1.37.16",
"@certd/pipeline": "^1.37.16",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -53,5 +53,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ddb18e6c219d0f7a7acb4a3355be5db3fd9e096e"
"gitHead": "f2e4e59f8d1b54b835d5da43aef8b527d6a4ba0b"
}
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
### Bug Fixes
* 修复ipv6作为证书域名申请证书校验失败的bug ([e4e16bc](https://github.com/certd/certd/commit/e4e16bc6a65bb082c18ca0590226f0987a47d477))
### Performance Improvements
* 批量设置定时,支持清除定时 ([63d8bcf](https://github.com/certd/certd/commit/63d8bcf8823f713365042d3c7aee3cf31d44b044))
* 支持彩虹聚合登录 ([6f18693](https://github.com/certd/certd/commit/6f186932ccad4becfdc0087c0539f7b2d0069844))
* 支持邮件模版设置 ([a6c0d2c](https://github.com/certd/certd/commit/a6c0d2c6f1fd6b60e6d7af290487c94564fd91ea))
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
### Performance Improvements
* 第三方登录支持gitee ([5cee7d4](https://github.com/certd/certd/commit/5cee7d44f17bd36972f477bc1f270999da558d05))
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
### Bug Fixes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.37.14",
"version": "1.37.16",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -33,11 +33,11 @@
"@aws-sdk/s3-request-presigner": "^3.535.0",
"@certd/vue-js-cron-light": "^4.0.14",
"@ctrl/tinycolor": "^4.1.0",
"@fast-crud/editor-code": "^1.27.6",
"@fast-crud/fast-crud": "^1.27.6",
"@fast-crud/fast-extends": "^1.27.6",
"@fast-crud/ui-antdv4": "^1.27.6",
"@fast-crud/ui-interface": "^1.27.6",
"@fast-crud/editor-code": "^1.27.7",
"@fast-crud/fast-crud": "^1.27.7",
"@fast-crud/fast-extends": "^1.27.7",
"@fast-crud/ui-antdv4": "^1.27.7",
"@fast-crud/ui-interface": "^1.27.7",
"@iconify/tailwind": "^1.2.0",
"@iconify/vue": "^4.1.1",
"@manypkg/get-packages": "^2.2.2",
@@ -106,8 +106,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.37.14",
"@certd/pipeline": "^1.37.14",
"@certd/lib-iframe": "^1.37.16",
"@certd/pipeline": "^1.37.16",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 36 KiB

@@ -0,0 +1,33 @@
<template>
<div class="params-show">
<a-tag type="primary" color="green" v-for="item of params" :key="item.value" class="item">
<span class="label">{{ item.label }}=</span>
<fs-copyable :modelValue="`\$\{${item.value}\}`" :button="{show:false}" :inline="true"></fs-copyable>
</a-tag>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "ParamsShow",
});
const props = defineProps<{
params: { value: string; label: string }[];
}>();
</script>
<style lang="less">
.params-show {
display: flex;
flex-wrap: wrap;
align-items: center;
min-height: 32px;
.item {
display: flex;
align-items: center;
margin-bottom: 2px;
margin-top: 2px;
}
}
</style>
@@ -11,6 +11,7 @@ import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
import InputPassword from "./common/input-password.vue";
import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
import ApiTest from "./common/api-test.vue";
import ParamsShow from "./common/params-show.vue";
export * from "./cert/index.js";
export default {
install(app: any) {
@@ -29,5 +30,6 @@ export default {
app.component("RemoteInput", RemoteInput);
app.component("CertDomainsGetter", CertDomainsGetter);
app.component("InputPassword", InputPassword);
app.component("ParamsShow", ParamsShow);
},
};
@@ -164,7 +164,7 @@ const steps = ref<Step[]>([
{
image: "/static/doc/images/15-1-email.png",
title: t("guide.scheduleAndEmailTask.setEmailNotification"),
descriptions: [t("guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails"), t("guide.scheduleAndEmailTask.basicVersionNeedsMailServer")],
descriptions: [t("guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails")],
},
{
title: t("guide.scheduleAndEmailTask.tutorialEndTitle"),
@@ -82,7 +82,7 @@ provide("fn:ai.open", openChat);
<LockScreen :avatar @to-login="handleLogout" />
</template>
<template #header-right-0>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<tutorial-button class="flex-center header-btn" />
</div>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
@@ -75,7 +75,7 @@
<div>
<span v-if="!settingStore.isComm">
<span>Powered by</span>
<a> handsfree.work </a>
<a> handfree.work </a>
</span>
<template v-if="siteInfo.licenseTo">
@@ -74,6 +74,7 @@ const sysPublic: Ref<SysPublicSetting> = computed(() => {
.login-container {
width: 100%;
height: 100%;
overflow: auto;
background: #f0f2f5 url(/static/background.svg) no-repeat 50%;
background-size: 100%;
//padding: 50px 0 84px;
@@ -631,7 +631,7 @@ export default {
autoHideTime: "Auto Hide Time",
autoHideTimeHelper: "Minutes without requests before auto hiding",
hideOpenApi: "Hide Open API",
hideOpenApiHelper: "Whether to hide open APIs; whether to expose /api/v1 prefixed endpoints",
hideOpenApiHelper: "Whether to hide open APIs; whether to hide /api/v1 prefixed endpoints",
hideSiteImmediately: "Hide Site Immediately",
hideImmediately: "Hide Immediately",
confirmHideSiteTitle: "Are you sure to hide the site immediately?",
@@ -780,6 +780,18 @@ export default {
oauthAutoRedirectHelper: "Whether to auto redirect to OAuth2 login when login (using the first enabled OAuth2 login type)",
oauthOnly: "OAuth2 Login Only",
oauthOnlyHelper: "Whether to only allow OAuth2 login, disable password login",
email: {
templates: "Email Templates",
templateType: "Template Type",
templateProvider: "Template Config",
templateSetting: "Email Template Setting",
serverSetting: "Email Server Setting",
sendTest: "Send Test",
templateProviderSelectorPlaceholder: "Not Configured",
},
},
},
modal: {
@@ -64,7 +64,6 @@ export default {
recommendDailyRun: "Recommend configuring to run once daily; new certs requested 35 days before expiry and auto-skipped otherwise",
setEmailNotification: "Set Email Notifications",
suggestErrorAndRecoveryEmails: "Suggest listening for 'On Error' and 'Error to Success' to quickly troubleshoot failures (basic version requires mail server setup)",
basicVersionNeedsMailServer: "(basic version requires configuring mail server)",
tutorialEndTitle: "Tutorial End",
thanksForWatching: "Thank you for watching, hope it helps you",
},
@@ -635,7 +635,7 @@ export default {
autoHideTime: "自动隐藏时间",
autoHideTimeHelper: "多少分钟内无请求自动隐藏",
hideOpenApi: "隐藏开放接口",
hideOpenApiHelper: "是否隐藏开放接口,是否放开/api/v1开头的接口",
hideOpenApiHelper: "是否隐藏开放接口,是否同时隐藏/api/v1开头的接口",
hideSiteImmediately: "立即隐藏站点",
hideImmediately: "立即隐藏",
confirmHideSiteTitle: "确定要立即隐藏站点吗?",
@@ -780,6 +780,18 @@ export default {
oauthAutoRedirectHelper: "是否自动跳转第三方登录(使用第一个已启用的第三方登录类型)",
oauthOnly: "仅使用第三方登录",
oauthOnlyHelper: "是否仅使用第三方登录,关闭密码登录(注意:请务必在测试第三方登录功能正常后再开启)",
email: {
templates: "邮件模板",
templateType: "模板类型",
templateProvider: "模板配置",
templateSetting: "邮件模板设置",
serverSetting: "邮件服务器设置",
sendTest: "发送测试",
templateProviderSelectorPlaceholder: "未配置",
},
},
},
modal: {
@@ -61,10 +61,9 @@ export default {
description: "自动运行",
setSchedule: "设置定时执行",
pipelineSuccessThenSchedule: "流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了",
recommendDailyRun: "推荐配置每天运行一次,到期前35天会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。",
recommendDailyRun: "推荐配置每天运行一次,默认到期前35天会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。",
setEmailNotification: "设置邮件通知",
suggestErrorAndRecoveryEmails: "建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)",
basicVersionNeedsMailServer: "(基础版需要配置邮件服务器)",
suggestErrorAndRecoveryEmails: "建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题",
tutorialEndTitle: "教程结束",
thanksForWatching: "感谢观看,希望对你有所帮助",
},
@@ -1,4 +1,26 @@
import Validator from "async-validator";
function isIpv6(d: string) {
if (!d) {
return false;
}
const isIPv6Regex = /^([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{1,4}$|((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})$/gm;
return isIPv6Regex.test(d);
}
function isIpv4(d: string) {
if (!d) {
return false;
}
const isIPv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
return isIPv4Regex.test(d);
}
function isIp(d: string) {
if (!d) {
return false;
}
return isIpv4(d) || isIpv6(d);
}
// 自定义验证器函数
export function isDomain(rule: any, value: any) {
if (value == null || value == "") {
@@ -14,7 +36,9 @@ export function isDomain(rule: any, value: any) {
const compiled = new RegExp(exp);
for (const domain of domains) {
//域名可以是泛域名,中文域名,数字域名,英文域名,域名中可以包含-和. ,可以_开头
if (isIp(domain)) {
continue;
}
if (!compiled.test(domain)) {
throw new Error(`域名有误:${domain},请输入正确的域名`);
}
@@ -21,6 +21,7 @@
},
},
}"
:disabled="disabled"
:show-current="false"
:show-select="false"
:dialog="{ width: 960 }"
@@ -147,6 +148,7 @@ async function refreshTarget(value: any) {
type: "",
};
}
debugger
}
watch(
@@ -16,12 +16,14 @@
<a-descriptions-item :label="t('authentication.email')">{{ userInfo.email }}</a-descriptions-item>
<a-descriptions-item :label="t('authentication.phoneNumber')">{{ userInfo.phoneCode }}{{ userInfo.mobile }}</a-descriptions-item>
<a-descriptions-item v-if="settingStore.sysPublic.oauthEnabled && settingStore.isPlus" label="第三方账号绑定">
<div v-for="item in computedOauthBounds" :key="item.name" class="flex items-center gap-2 mb-2">
<fs-icon :icon="item.icon" class="mr-2 text-blue-500" />
<span class="mr-2 w-36">{{ item.title }}</span>
<a-button v-if="item.bound" type="primary" danger @click="unbind(item.name)">解绑</a-button>
<a-button v-else type="primary" @click="bind(item.name)"></a-button>
</div>
<template v-for="item in computedOauthBounds" :key="item.name">
<div v-if="item.addonId" class="flex items-center gap-2 mb-2">
<fs-icon :icon="item.icon" class="mr-2 text-blue-500 w-5 flex justify-center items-center" />
<span class="mr-2 w-36">{{ item.title }}</span>
<a-button v-if="item.bound" type="primary" danger @click="unbind(item.name)"></a-button>
<a-button v-else type="primary" @click="bind(item.name)">绑定</a-button>
</div>
</template>
</a-descriptions-item>
<a-descriptions-item :label="t('common.handle')">
<a-button type="primary" @click="doUpdate">{{ t("authentication.updateProfile") }}</a-button>
@@ -40,6 +42,7 @@ import { useI18n } from "/src/locales";
import { useUserProfile } from "./use";
import { Modal } from "ant-design-vue";
import { useSettingStore } from "/@/store/settings";
import { isEmpty } from "lodash-es";
const { t } = useI18n();
@@ -3,10 +3,11 @@
</template>
<script setup lang="ts">
import { useFormWrapper } from "@fast-crud/fast-crud";
import { compute, dict, useFormWrapper } from "@fast-crud/fast-crud";
import * as api from "../api";
import { useSettingStore } from "/@/store/settings";
import { useI18n } from "/src/locales";
import { computed } from "vue";
const { t } = useI18n();
const props = defineProps<{
@@ -20,7 +21,7 @@ async function batchUpdateRequest(form: any) {
await api.BatchUpdateTrigger(props.selectedRowKeys, {
title: "定时触发",
type: "timer",
props: form.props,
props: form.clear ? false : form.props,
});
emit("change");
}
@@ -33,6 +34,28 @@ async function openFormDialog() {
settingStore.checkPlus();
const crudOptions: any = {
columns: {
clear: {
title: "设置/清空",
form: {
value: false,
component: {
name: "fs-dict-switch",
vModel: "checked",
dict: dict({
data: [
{
label: "设置定时",
value: false,
},
{
label: "清空定时",
value: true,
},
],
}),
},
},
},
"props.cron": {
title: t("certd.schedule"),
form: {
@@ -40,12 +63,18 @@ async function openFormDialog() {
name: "cron-editor",
vModel: "modelValue",
},
show: compute(({ form }) => {
return form.clear !== true;
}),
rules: [{ required: true, message: t("certd.selectCron") }],
},
},
},
form: {
mode: "edit",
initialForm: {
clear: false,
},
//@ts-ignore
async doSubmit({ form }) {
await batchUpdateRequest(form);
@@ -7,7 +7,7 @@
<template v-for="item in oauthProviderList" :key="item.type">
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item.name)">
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
<div>{{ item.addonTitle || item.title }}</div>
<div class="ellipsis title" :title="item.addonTitle || item.title">{{ item.addonTitle || item.title }}</div>
</div>
</template>
</div>
@@ -101,6 +101,12 @@ async function goOauthLogin(type: string) {
gap: 8px;
padding: 8px 8px;
border-radius: 100px;
width: 100px;
.title {
width: 100%;
text-align: center;
}
.fs-icon {
font-size: 36px;
color: #006be6;
@@ -1,5 +1,6 @@
import { request } from "/src/api/service";
const apiPrefix = "/mine/email";
const apiSettingPrefix = "/sys/settings";
export async function TestSend(receiver: string) {
await request({
@@ -10,3 +11,10 @@ export async function TestSend(receiver: string) {
},
});
}
export async function GetEmailTemplates() {
return await request({
url: apiSettingPrefix + "/getEmailTemplates",
method: "post",
});
}
@@ -7,53 +7,104 @@
</div>
</template>
<div class="flex-o">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" class="email-form-box" @finish="onFinish" @finish-failed="onFinishFailed">
<div v-if="!formState.usePlus" class="email-form">
<a-form-item :label="t('certd.useCustomEmailServer')"> </a-form-item>
<a-form-item :label="t('certd.smtpDomain')" name="host" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpDomain') }]">
<a-input v-model:value="formState.host" />
</a-form-item>
<div class="email-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" class="" @finish="onFinish">
<h2>{{ t("certd.sys.setting.email.serverSetting") }}</h2>
<a-tabs v-model:active-key="activeKey" @change="onChangeActiveKey">
<a-tab-pane key="custom" :tab="t('certd.useCustomEmailServer')">
<div>
<a-form-item :label="t('certd.smtpDomain')" name="host" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpDomain') }]">
<a-input v-model:value="formState.host" />
</a-form-item>
<a-form-item :label="t('certd.smtpPort')" name="port" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpPort') }]">
<a-input v-model:value="formState.port" />
</a-form-item>
<a-form-item :label="t('certd.smtpPort')" name="port" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpPort') }]">
<a-input v-model:value="formState.port" />
</a-form-item>
<a-form-item :label="t('certd.username')" :name="['auth', 'user']" :rules="[{ required: true, message: t('certd.pleaseEnterUsername') }]">
<a-input v-model:value="formState.auth.user" />
</a-form-item>
<a-form-item :label="t('certd.password')" :name="['auth', 'pass']" :rules="[{ required: true, message: t('certd.pleaseEnterPassword') }]">
<a-input-password v-model:value="formState.auth.pass" />
<div class="helper">{{ t("certd.qqEmailAuthCodeHelper") }}</div>
</a-form-item>
<a-form-item :label="t('certd.senderEmail')" name="sender" :rules="[{ required: true, message: t('certd.pleaseEnterSenderEmail') }]">
<a-input v-model:value="formState.sender" />
</a-form-item>
<a-form-item :label="t('certd.useSsl')" name="secure">
<a-switch v-model:checked="formState.secure" />
<div class="helper">{{ t("certd.sslPortNote") }}</div>
</a-form-item>
<a-form-item :label="t('certd.ignoreCertValidation')" :name="['tls', 'rejectUnauthorized']">
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">{{ t("certd.save") }}</a-button>
</a-form-item>
</div>
<div class="email-form">
<a-form-item :label="t('certd.useOfficialEmailServer')" name="usePlus">
<div class="flex-o">
<a-switch v-model:checked="formState.usePlus" :disabled="!settingStore.isPlus" @change="onUsePlusChanged" />
<vip-button class="ml-5" mode="button"></vip-button>
<a-form-item :label="t('certd.username')" :name="['auth', 'user']" :rules="[{ required: true, message: t('certd.pleaseEnterUsername') }]">
<a-input v-model:value="formState.auth.user" />
</a-form-item>
<a-form-item :label="t('certd.password')" :name="['auth', 'pass']" :rules="[{ required: true, message: t('certd.pleaseEnterPassword') }]">
<a-input-password v-model:value="formState.auth.pass" />
<div class="helper">{{ t("certd.qqEmailAuthCodeHelper") }}</div>
</a-form-item>
<a-form-item :label="t('certd.senderEmail')" name="sender" :rules="[{ required: true, message: t('certd.pleaseEnterSenderEmail') }]">
<a-input v-model:value="formState.sender" />
</a-form-item>
<a-form-item :label="t('certd.useSsl')" name="secure">
<a-switch v-model:checked="formState.secure" />
<div class="helper">{{ t("certd.sslPortNote") }}</div>
</a-form-item>
<a-form-item :label="t('certd.ignoreCertValidation')" :name="['tls', 'rejectUnauthorized']">
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
</a-form-item>
</div>
<div class="helper">{{ t("certd.useOfficialEmailServerHelper") }}</div>
</a-form-item>
</a-tab-pane>
<a-tab-pane key="plus" class="plus" :disabled="!settingStore.isPlus">
<template #tab>
<span class="flex items-center">
{{ t("certd.useOfficialEmailServer") }}
<vip-button class="ml-5" mode="button"></vip-button>
</span>
</template>
<a-form-item :label="t('certd.useOfficialEmailServer')" name="usePlus">
<div class="flex-o">
<a-switch v-model:checked="formState.usePlus" :disabled="!settingStore.isPlus" @change="onUsePlusChanged" />
</div>
<div class="helper">{{ t("certd.useOfficialEmailServerHelper") }}</div>
</a-form-item>
</a-tab-pane>
</a-tabs>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">{{ t("certd.save") }}</a-button>
</a-form-item>
<div class="flex items-center">
<h2>{{ t("certd.sys.setting.email.templateSetting") }}</h2>
<vip-button class="ml-10" mode="button"></vip-button>
</div>
<a-form-item :label="t('certd.sys.setting.email.templates')" :name="['templates']">
<div class="flex flex-wrap">
<table class="w-full table-auto border-collapse border border-gray-400">
<thead>
<tr>
<th class="border border-gray-300 px-4 py-2 w-1/3">{{ t("certd.sys.setting.email.templateType") }}</th>
<th class="border border-gray-300 px-4 py-2 w-1/3">{{ t("certd.sys.setting.email.templateProvider") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, key) of emailTemplates" :key="key">
<td class="border border-gray-300 px-4 py-2">
<div class="flex items-center" :title="item.desc">
<fs-icon :icon="item.icon" class="mr-2 text-blue-600" />
{{ item.title }}
</div>
</td>
<td class="border border-gray-300 px-4 py-2">
<AddonSelector
v-model:model-value="item.addonId"
:disabled="!settingStore.isPlus"
addon-type="emailTemplate"
from="sys"
:type="item.name"
:placeholder="t('certd.sys.setting.email.templateProviderSelectorPlaceholder')"
/>
</td>
</tr>
</tbody>
</table>
</div>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">{{ t("certd.save") }}</a-button>
</a-form-item>
</a-form>
</div>
<div class="email-form">
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onTestSend">
<h2>{{ t("certd.sys.setting.email.sendTest") }}</h2>
<a-form-item :label="t('certd.testReceiverEmail')" name="receiver" :rules="[{ required: true, message: t('certd.pleaseEnterTestReceiverEmail') }]">
<a-input v-model:value="testFormState.receiver" />
<div class="helper">{{ t("certd.saveBeforeTest") }}</div>
@@ -71,14 +122,14 @@
</template>
<script setup lang="ts">
import { reactive } from "vue";
import { onMounted, reactive, ref } from "vue";
import * as api from "../api";
import * as emailApi from "./api.email";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/src/store/settings";
import * as _ from "lodash-es";
import { useI18n } from "/src/locales";
import AddonSelector from "../../../certd/addon/addon-selector/index.vue";
const { t } = useI18n();
defineOptions({
name: "EmailSetting",
@@ -98,8 +149,14 @@ interface FormState {
};
sender: string;
usePlus: boolean;
templates: {
pipelineResult: any;
registerCode: any;
forgotPassword: any;
};
}
const activeKey = ref("custom");
const formState = reactive<Partial<FormState>>({
auth: {
user: "",
@@ -109,27 +166,65 @@ const formState = reactive<Partial<FormState>>({
usePlus: false,
});
const emailTemplates = ref([]);
async function loadEmailTemplates() {
emailTemplates.value = await emailApi.GetEmailTemplates();
}
function fillEmailTemplates(form: any) {
const providers: any = {};
for (const item of emailTemplates.value) {
const type = item.name;
providers[type] = {
type: type,
title: item.title,
icon: item.icon,
addonId: item.addonId || null,
};
}
form.templates = providers;
return providers;
}
async function load() {
const data: any = await api.EmailSettingsGet();
_.merge(formState, data);
}
load();
onMounted(async () => {
await load();
refreshActiveKeyByUsePlus();
await loadEmailTemplates();
});
const onFinish = async (form: any) => {
console.log("Success:", form);
fillEmailTemplates(form);
await api.EmailSettingsSave(form);
notification.success({
message: t("certd.saveSuccess"),
});
};
const onFinishFailed = (errorInfo: any) => {
// console.log("Failed:", errorInfo);
};
async function onUsePlusChanged() {
await api.EmailSettingsSave(formState);
refreshActiveKeyByUsePlus();
await onFinish(formState);
}
async function refreshActiveKeyByUsePlus() {
if (formState.usePlus) {
activeKey.value = "plus";
} else {
activeKey.value = "custom";
}
}
async function onChangeActiveKey(key: string) {
activeKey.value = key;
if (key === "plus") {
formState.usePlus = true;
} else {
formState.usePlus = false;
}
await onFinish(formState);
}
interface TestFormState {
@@ -157,12 +252,21 @@ const settingStore = useSettingStore();
<style lang="less">
.page-setting-email {
.ant-tabs-nav {
margin-left: 80px;
}
.email-form-box {
display: flex;
}
h2 {
margin: 20px 0px;
font-weight: 600;
}
.email-form {
width: 500px;
width: 700px;
max-width: 100%;
margin: 20px;
}
@@ -172,5 +276,10 @@ const settingStore = useSettingStore();
color: #999;
font-size: 10px;
}
.addon-selector {
.inner {
justify-content: space-between;
}
}
}
</style>
@@ -7,7 +7,7 @@
<vip-button class="ml-5" mode="button"></vip-button>
</div>
</a-form-item>
<a-form-item v-if="formState.public.oauthEnabled" :label="t('certd.sys.setting.oauthProviders')" :name="['public', 'oauthProviders']">
<a-form-item :label="t('certd.sys.setting.oauthProviders')" :name="['public', 'oauthProviders']">
<div class="flex flex-wrap">
<table class="w-full table-auto border-collapse border border-gray-400">
<thead>
@@ -31,7 +31,14 @@
</fs-copyable>
</td>
<td class="border border-gray-300 px-4 py-2">
<AddonSelector v-model:model-value="item.addonId" addon-type="oauth" from="sys" :type="item.name" :placeholder="t('certd.sys.setting.oauthProviderSelectorPlaceholder')" />
<AddonSelector
v-model:model-value="item.addonId"
:disabled="!formState.public.oauthEnabled"
addon-type="oauth"
from="sys"
:type="item.name"
:placeholder="t('certd.sys.setting.oauthProviderSelectorPlaceholder')"
/>
</td>
</tr>
</tbody>
+1
View File
@@ -22,3 +22,4 @@ run/
tools/lego/*
!tools/lego/readme.md
test.mjs
isolate-*.log
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
### Bug Fixes
* 优化西部数据 500 already exists 的问题 ([2bfad9f](https://github.com/certd/certd/commit/2bfad9fc651da208b610abd921fbfb2fbc04203f))
### Performance Improvements
* 批量设置定时,支持清除定时 ([63d8bcf](https://github.com/certd/certd/commit/63d8bcf8823f713365042d3c7aee3cf31d44b044))
* 新增数据库迁移doc说明文档,优化datetime字段平滑迁移 ([45fbce0](https://github.com/certd/certd/commit/45fbce0c2af5fb3ead6d3dd12a42f8cc1714262f))
* 支持彩虹聚合登录 ([6f18693](https://github.com/certd/certd/commit/6f186932ccad4becfdc0087c0539f7b2d0069844))
* 支持邮件模版设置 ([a6c0d2c](https://github.com/certd/certd/commit/a6c0d2c6f1fd6b60e6d7af290487c94564fd91ea))
* oidc支持使用第三方昵称或账号作为certd用户的用户名 ([b6fea0c](https://github.com/certd/certd/commit/b6fea0c8562abf912daa7d72958ceb2e93575d31))
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
### Bug Fixes
* oidc 支持nonce ([a5ca411](https://github.com/certd/certd/commit/a5ca41131b308b36b17ca359d9709ea8e9b7cee1))
### Performance Improvements
* 第三方登录支持gitee ([5cee7d4](https://github.com/certd/certd/commit/5cee7d44f17bd36972f477bc1f270999da558d05))
* 邮件模版安全优化 ([adca151](https://github.com/certd/certd/commit/adca151e4f07a4c6a2a753bfa48ee0d4d6469fd2))
* 支持部署到中国移动CDN ([4351304](https://github.com/certd/certd/commit/43513049beff407558d2a234415521464165cebc))
* 支持k8s apply ([d55954a](https://github.com/certd/certd/commit/d55954a36391ebe6a9397ff7dcfb710193ac5e34))
## [1.37.14](https://github.com/certd/certd/compare/v1.37.13...v1.37.14) (2025-12-02)
**Note:** Version bump only for package @certd/ui-server
@@ -0,0 +1,2 @@
ALTER TABLE `sys_settings` MODIFY COLUMN `setting` longtext NULL;
ALTER TABLE `user_settings` MODIFY COLUMN `setting` longtext NULL;
@@ -0,0 +1,20 @@
update cd_access set create_time = '2024-08-08 00:00:00.000' where create_time < '999999999999999999999';
update cd_access set update_time = '2024-08-08 00:00:00.000' where update_time < '999999999999999999999';
update pi_history set create_time = '2024-08-08 00:00:00.000' where create_time < '999999999999999999999';
update pi_history set update_time = '2024-08-08 00:00:00.000' where update_time < '999999999999999999999';
update pi_history_log set create_time = '2024-08-08 00:00:00.000' where create_time < '999999999999999999999';
update pi_history_log set update_time = '2024-08-08 00:00:00.000' where update_time < '999999999999999999999';
update pi_pipeline set create_time = '2024-08-08 00:00:00.000' where create_time < '999999999999999999999';
update pi_pipeline set update_time = '2024-08-08 00:00:00.000' where update_time < '999999999999999999999';
update sys_permission set create_time = '2024-08-08 00:00:00.000' where create_time < '999999999999999999999';
update sys_permission set update_time = '2024-08-08 00:00:00.000' where update_time < '999999999999999999999';
update sys_role set create_time = '2024-08-08 00:00:00.000' where create_time < '999999999999999999999';
update sys_role set update_time = '2024-08-08 00:00:00.000' where update_time < '999999999999999999999';
update sys_user set create_time = '2024-08-08 00:00:00.000' where create_time < '999999999999999999999';
update sys_user set update_time = '2024-08-08 00:00:00.000' where update_time < '999999999999999999999';
+18 -18
View File
@@ -1,12 +1,12 @@
{
"name": "@certd/ui-server",
"version": "1.37.14",
"version": "1.37.16",
"description": "fast-server base midway",
"private": true,
"type": "module",
"scripts": {
"start": "cross-env NODE_ENV=production node ./bootstrap.js",
"dev-start": "mwtsc --watch --run @midwayjs/mock/app",
"start": "cross-env NODE_ENV=production node --optimize-for-size ./bootstrap.js",
"dev-start": "cross-env NODE_ENV=dev & mwtsc --watch --run @midwayjs/mock/app",
"dc": "cd ../../../ && pnpm run dev",
"dev": "cross-env NODE_ENV=local & pnpm run dev-start",
"dev-commlocal": "cross-env NODE_ENV=dev-commlocal mwtsc --watch --run @midwayjs/mock/app",
@@ -23,13 +23,13 @@
"lint": "mwts check",
"lint:fix": "mwts fix",
"ci": "npm run cov",
"build": "mwtsc --cleanOutDir --skipLibCheck",
"build": "cross-env NODE_ENV=production mwtsc --cleanOutDir --skipLibCheck",
"export-metadata": "node export-plugin-yaml.js",
"export-md": "node export-plugin-md.js",
"dev-build": "echo 1",
"build-on-docker": "node ./before-build.js && npm run build",
"up-mw-deps": "npx midway-version -u -w",
"heap": "cross-env NODE_ENV=local clinic heapprofiler -- node ./bootstrap.js",
"heap": "cross-env NODE_ENV=production clinic heapprofiler -- node --optimize-for-size --inspect ./bootstrap.js",
"flame": "clinic flame -- node ./bootstrap.js",
"tsc": "tsc --skipLibCheck",
"slimming": "node ./slimming.js",
@@ -45,20 +45,20 @@
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.37.14",
"@certd/basic": "^1.37.14",
"@certd/commercial-core": "^1.37.14",
"@certd/acme-client": "^1.37.16",
"@certd/basic": "^1.37.16",
"@certd/commercial-core": "^1.37.16",
"@certd/cv4pve-api-javascript": "^8.4.2",
"@certd/jdcloud": "^1.37.14",
"@certd/lib-huawei": "^1.37.14",
"@certd/lib-k8s": "^1.37.14",
"@certd/lib-server": "^1.37.14",
"@certd/midway-flyway-js": "^1.37.14",
"@certd/pipeline": "^1.37.14",
"@certd/plugin-cert": "^1.37.14",
"@certd/plugin-lib": "^1.37.14",
"@certd/plugin-plus": "^1.37.14",
"@certd/plus-core": "^1.37.14",
"@certd/jdcloud": "^1.37.16",
"@certd/lib-huawei": "^1.37.16",
"@certd/lib-k8s": "^1.37.16",
"@certd/lib-server": "^1.37.16",
"@certd/midway-flyway-js": "^1.37.16",
"@certd/pipeline": "^1.37.16",
"@certd/plugin-cert": "^1.37.16",
"@certd/plugin-lib": "^1.37.16",
"@certd/plugin-plus": "^1.37.16",
"@certd/plus-core": "^1.37.16",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",
@@ -21,6 +21,8 @@ import * as upload from '@midwayjs/upload';
import { setLogger } from '@certd/acme-client';
import {HiddenMiddleware} from "./middleware/hidden.js";
//@ts-ignore
// process.env.UV_THREADPOOL_SIZE = 2
process.on('uncaughtException', error => {
console.error('未捕获的异常:', error);
// 在这里可以添加日志记录、发送错误通知等操作
@@ -1,9 +1,10 @@
import { Rule, RuleType } from "@midwayjs/validate";
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server";
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
import { Rule, RuleType } from "@midwayjs/validate";
import { CaptchaService } from "../../modules/basic/service/captcha-service.js";
import { CodeService } from "../../modules/basic/service/code-service.js";
import { EmailService } from "../../modules/basic/service/email-service.js";
import { CaptchaService } from "../../modules/basic/service/captcha-service.js";
import { AddonGetterService } from "../../modules/pipeline/service/addon-getter-service.js";
export class SmsCodeReq {
@Rule(RuleType.string().required())
@@ -49,6 +50,9 @@ export class BasicController extends BaseController {
@Inject()
captchaService: CaptchaService;
@Inject()
addonGetterService: AddonGetterService;
@Post('/captcha/get', { summary: Constants.per.guest })
async getCaptcha(@Query("captchaAddonId") captchaAddonId:number) {
const form = await this.captchaService.getCaptcha(captchaAddonId)
@@ -83,17 +87,18 @@ export class BasicController extends BaseController {
const opts = {
verificationType: body.verificationType,
verificationCodeLength: undefined,
title: undefined,
content: undefined,
duration: undefined,
};
if(body?.verificationType === 'forgotPassword') {
opts.title = '找回密码';
opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略';
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
opts.verificationCodeLength = 6;
}else{
opts.duration = 10;
opts.verificationCodeLength = 6;
}
await this.codeService.checkCaptcha(body.captcha);
await this.codeService.sendEmailCode(body.email, opts);
// 设置缓存内容
@@ -169,9 +169,10 @@ export class ConnectController extends BaseController {
const userInfo = validationValue.userInfo;
const oauthType = validationValue.type;
let newUser = new UserEntity()
newUser.username = `${oauthType}_${userInfo.nickName}_${simpleNanoId(6)}`;
newUser.username = `${userInfo.nickName}_${simpleNanoId(6)}_${oauthType}`;
newUser.avatar = userInfo.avatar;
newUser.nickName = userInfo.nickName || simpleNanoId(6);
newUser.email = userInfo.email || "";
newUser = await this.userService.register("username", newUser, async (txManager) => {
const oauthBound: OauthBoundEntity = new OauthBoundEntity()
@@ -1,6 +1,7 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
import {
addonRegistry,
AddonService,
CrudController,
SysPrivateSettings,
SysPublicSettings,
@@ -30,6 +31,8 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
pipelineService: PipelineService;
@Inject()
codeService: CodeService;
@Inject()
addonService: AddonService;
getService() {
return this.service;
@@ -86,6 +89,25 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
return this.ok(conf);
}
@Post('/getEmailTemplates', { summary: 'sys:settings:view' })
async getEmailTemplates(@Body(ALL) body) {
const conf = await getEmailSettings(this.service, this.userSettingsService);
const templates = conf.templates || {}
const emailTemplateProviders = await this.addonService.getDefineList("emailTemplate")
const proviers = []
for (const item of emailTemplateProviders) {
const templateConf = templates[item.name] || {}
proviers.push({
name: item.name,
title: item.title,
addonId : templateConf.addonId,
})
}
return this.ok(proviers);
}
@Post('/saveEmailSettings', { summary: 'sys:settings:edit' })
async saveEmailSettings(@Body(ALL) body) {
const conf = await getEmailSettings(this.service, this.userSettingsService);
@@ -130,7 +130,8 @@ export class AutoCRegisterCron {
title,
content,
errorMessage:title,
url
url,
notificationType: "vipExpireRemind",
}
},adminUser.id)
}
@@ -182,7 +183,8 @@ export class AutoCRegisterCron {
title,
content,
errorMessage: title,
url
url,
notificationType: "userExpireRemind",
}
}, user.id)
}
@@ -26,6 +26,7 @@ export class AutoZPrint {
async init() {
//监听https
this.startHttpsServer();
logger.info("ENV:", process.env.NODE_ENV);
if (isDev()) {
this.startHeapLog();
}
@@ -49,7 +50,7 @@ export class AutoZPrint {
setInterval(() => {
const mu = process.memoryUsage();
logger.info(`rss:${format(mu.rss)},heapUsed: ${format(mu.heapUsed)},heapTotal: ${format(mu.heapTotal)},external: ${format(mu.external)}`);
}, 60000);
}, 20000);
}
startHttpsServer() {
@@ -1,14 +1,10 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { cache, isDev, randomNumber, simpleNanoId } from '@certd/basic';
import { SysSettingsService, SysSiteInfo } from '@certd/lib-server';
import { SmsServiceFactory } from '../sms/factory.js';
import { AccessService, AccessSysGetter, CodeErrorException, SysSettingsService } from '@certd/lib-server';
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { ISmsService } from '../sms/api.js';
import { CodeErrorException } from '@certd/lib-server';
import { EmailService } from './email-service.js';
import { AccessService } from '@certd/lib-server';
import { AccessSysGetter } from '@certd/lib-server';
import { isComm } from '@certd/plus-core';
import { SmsServiceFactory } from '../sms/factory.js';
import { CaptchaService } from "./captcha-service.js";
import { EmailService } from './email-service.js';
// {data: '<svg.../svg>', text: 'abcd'}
/**
@@ -84,8 +80,6 @@ export class CodeService {
async sendEmailCode(
email: string,
opts?: {
title?: string,
content?: string,
duration?: number,
verificationType?: string,
verificationCodeLength?: number,
@@ -96,26 +90,27 @@ export class CodeService {
}
let siteTitle = 'Certd';
if (isComm()) {
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
if (siteInfo) {
siteTitle = siteInfo.title || siteTitle;
}
}
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1));
const code = randomNumber(verificationCodeLength);
const title = `${siteTitle}${!!opts?.title ? opts.title : '验证码'}`;
const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`;
await this.emailService.send({
subject: title,
content: content,
receivers: [email],
const templateData = {
code, duration,
title: "验证码",
content:`您的验证码是${code},请勿泄露`,
notificationType: "registerCode"
}
if (opts?.verificationType === 'forgotPassword') {
templateData.title = '找回密码';
templateData.notificationType = "forgotPassword"
}
await this.emailService.sendByTemplate({
type: templateData.notificationType,
data: templateData,
email:{
receivers: [email],
},
});
const key = this.buildEmailCodeKey(email,opts?.verificationType);
@@ -1,5 +1,5 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import type { EmailSend } from '@certd/pipeline';
import type { EmailSend, EmailSendByTemplateReq } from '@certd/pipeline';
import { IEmailService } from '@certd/pipeline';
import { logger } from '@certd/basic';
@@ -8,9 +8,11 @@ import { isComm, isPlus } from '@certd/plus-core';
import nodemailer from 'nodemailer';
import { SendMailOptions } from 'nodemailer';
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
import { PlusService, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
import { AddonService, PlusService, SysEmailConf, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
import { getEmailSettings } from '../../sys/settings/fix.js';
import { UserEmailSetting } from "../../mine/service/models.js";
import { AddonGetterService } from '../../pipeline/service/addon-getter-service.js';
import { EmailContent, ITemplateProvider } from '../../../plugins/plugin-template/api.js';
export type EmailConfig = {
host: string;
@@ -38,6 +40,12 @@ export class EmailService implements IEmailService {
@Inject()
plusService: PlusService;
@Inject()
addonGetterService: AddonGetterService;
@Inject()
addonService: AddonService
async sendByPlus(email: EmailSend) {
if (!isPlus()) {
throw new Error('plus not enabled');
@@ -49,7 +57,6 @@ export class EmailService implements IEmailService {
* content: string;
* receivers: string[];
*/
await this.plusService.sendEmail(email);
}
@@ -62,26 +69,6 @@ export class EmailService implements IEmailService {
throw new Error('收件人不能为空');
}
const emailConf = await getEmailSettings(this.sysSettingsService, this.settingsService);
if (!emailConf.host && emailConf.usePlus == null) {
if (isPlus()) {
//自动使用plus发邮件
return await this.sendByPlus(email);
}
throw new Error('邮件服务器还未设置');
}
if (emailConf.usePlus && isPlus()) {
return await this.sendByPlus(email);
}
await this.sendByCustom(emailConf, email);
logger.info('sendEmail complete: ', email);
}
private async sendByCustom(emailConfig: EmailConfig, email: EmailSend) {
const transporter = nodemailer.createTransport(emailConfig);
let sysTitle = 'Certd';
if (isComm()) {
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
@@ -93,10 +80,34 @@ export class EmailService implements IEmailService {
if (!subject.includes(`${sysTitle}`)) {
subject = `${sysTitle}${subject}`;
}
email.subject = subject;
const emailConf = await getEmailSettings(this.sysSettingsService, this.settingsService);
if (!emailConf.host && emailConf.usePlus == null) {
if (isPlus()) {
//自动使用plus发邮件
return await this.sendByPlus(email);
}
throw new Error('邮件服务器还未设置');
}
if (emailConf.usePlus && isPlus()) {
return await this.sendByPlus(email);
}
await this.sendByCustom(emailConf, email, sysTitle);
logger.info('sendEmail complete: ', email);
}
private async sendByCustom(emailConfig: EmailConfig, email: EmailSend, sysTitle: string) {
const transporter = nodemailer.createTransport(emailConfig);
const mailOptions = {
from: `${sysTitle} <${emailConfig.sender}>`,
to: email.receivers.join(', '), // list of receivers
subject: subject,
subject: email.subject,
text: email.content,
html: email.html,
attachments: email.attachments,
@@ -105,30 +116,78 @@ export class EmailService implements IEmailService {
}
async test(userId: number, receiver: string) {
await this.send({
receivers: [receiver],
subject: '测试邮件,from certd',
content: '测试邮件,from certd',
await this.sendByTemplate({
type: "common",
data: {
title: '测试邮件,from certd',
content: '测试邮件,from certd',
url:"https://certd.handfree.work",
},
email: {
receivers: [receiver],
},
});
}
async list(userId: any) {
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
return userEmailSetting.list;
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
return userEmailSetting.list;
}
async delete(userId: any, email: string) {
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
userEmailSetting.list = userEmailSetting.list.filter(item=>item !== email);
await this.settingsService.saveSetting(userId,userEmailSetting)
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
userEmailSetting.list = userEmailSetting.list.filter(item => item !== email);
await this.settingsService.saveSetting(userId, userEmailSetting)
}
async add(userId: any, email: string) {
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
//如果已存在
if(userEmailSetting.list.includes(email)){
if (userEmailSetting.list.includes(email)) {
return
}
userEmailSetting.list.unshift(email)
await this.settingsService.saveSetting(userId,userEmailSetting)
await this.settingsService.saveSetting(userId, userEmailSetting)
}
async sendByTemplate(req: EmailSendByTemplateReq) {
let content = null
if (isPlus()) {
const emailConf = await this.sysSettingsService.getSetting<SysEmailConf>(SysEmailConf);
const template = emailConf?.templates?.[req.type]
if (template && template.addonId) {
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(template.addonId, true, 0)
if (addon) {
content = await addon.buildContent({ data: req.data })
}
}
if (!content) {
//看看有没有通用模版
if (emailConf?.templates?.common && emailConf?.templates?.common.addonId) {
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(emailConf.templates.common.addonId, true, 0)
if (addon) {
content = await addon.buildContent({ data: req.data })
}
}
}
// 没有找到模版,使用默认模版
if (!content) {
try {
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank("emailTemplate", req.type)
content = await addon.buildDefaultContent({ data: req.data })
} catch (e) {
// 对应的通知类型模版可能没有注册或者开发
}
}
}
if (!content) {
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank("emailTemplate", "common")
content = await addon.buildDefaultContent({ data: req.data })
}
return await this.send({
...req.email,
...content
})
}
}
@@ -1,5 +1,5 @@
import parser from 'cron-parser';
import { ILogger } from '@certd/basic';
import { ILogger, logger } from '@certd/basic';
export type CronTaskReq = {
/**
@@ -52,7 +52,7 @@ export class Cron {
queue: CronTask[] = [];
constructor(opts: any) {
this.logger = opts.logger;
this.logger = opts.logger || logger;
this.immediateTriggerOnce = opts.immediateTriggerOnce;
}
@@ -268,7 +268,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
url,
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
errorMessage: site.error
errorMessage: site.error,
notificationType: "siteCheckError",
}
},
site.userId
@@ -295,7 +296,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
content,
url,
errorMessage: "站点证书即将过期"
errorMessage: "站点证书即将过期",
notificationType: "siteCertExpireRemind",
}
},
site.userId
@@ -311,7 +313,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
title: `站点证书已过期${-validDays}天<${site.name}>`,
content,
url,
errorMessage: "站点证书已过期"
errorMessage: "站点证书已过期",
notificationType: "siteCertExpireRemind",
}
},
site.userId
@@ -62,4 +62,11 @@ export class AddonGetterService {
return await this.getAddonById(id, true, userId);
}
async getBlank(addonType:string,subType:string){
return await this.getAddonById(null,false,0,{
type: addonType, name:subType
})
}
}
@@ -128,7 +128,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
const pipeline = JSON.parse(item.content);
let stepCount = 0;
if(pipeline.stages){
if (pipeline.stages) {
RunnableCollection.each(pipeline.stages, (runnable: any) => {
stepCount++;
});
@@ -237,7 +237,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
let fromType = "pipeline";
if (bean.type === "cert_upload") {
fromType = "upload";
}else if (bean.type === "cert_auto") {
} else if (bean.type === "cert_auto") {
fromType = "auto";
}
await this.certInfoService.updateDomains(pipeline.id, pipeline.userId || bean.userId, domains, fromType);
@@ -376,17 +376,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
if (immediateTriggerOnce) {
try{
try {
await this.trigger(pipeline.id);
await sleep(200);
}catch(e){
} catch (e) {
logger.error(e);
}
}
}
async trigger(id: any, stepId?: string , doCheck = false) {
async trigger(id: any, stepId?: string, doCheck = false) {
const entity: PipelineEntity = await this.info(id);
if (doCheck) {
await this.beforeCheck(entity);
@@ -441,6 +441,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
pipeline = await this.info(id);
} else {
pipeline = id;
id = pipeline.id;
}
if (!pipeline) {
return;
@@ -456,6 +457,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
removeCron(pipelineId, trigger) {
const name = this.buildCronKey(pipelineId, trigger.id);
this.cron.remove(name);
logger.info("当前定时器数量:", this.cron.getTaskSize());
}
registerCron(pipelineId, trigger) {
@@ -500,8 +502,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
async isPipelineValidTimeEnabled(entity: PipelineEntity) {
const settings = await this.sysSettingsService.getPublicSettings();
if (isPlus() && settings.pipelineValidTimeEnabled){
if (entity.validTime > 0 && entity.validTime < Date.now()){
if (isPlus() && settings.pipelineValidTimeEnabled) {
if (entity.validTime > 0 && entity.validTime < Date.now()) {
return false
}
}
@@ -524,7 +526,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (!validTimeEnabled) {
throw new Error(`流水线${entity.id}已过期,不予执行`);
}
let suite: UserSuiteEntity = null;
if (isComm()) {
suite = await this.checkHasDeployCount(entity.id, entity.userId);
@@ -538,8 +540,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
async doRun(entity: PipelineEntity, triggerId: string, stepId?: string) {
let suite:any = null
try{
let suite: any = null
try {
const res = await this.beforeCheck(entity);
suite = res.suite
} catch (e) {
@@ -803,11 +805,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
for (const item of list) {
const pipeline = JSON.parse(item.content);
pipeline.triggers = [{
id: nanoid(),
title: "定时触发",
...trigger
}];
if (trigger.props === false) {
//清除trigger
pipeline.triggers = []
} else {
pipeline.triggers = [{
id: nanoid(),
title: "定时触发",
...trigger
}];
}
await this.doUpdatePipelineJson(item, pipeline);
}
@@ -919,20 +927,20 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
}
async createAutoPipeline(req: { domains: string[]; email: string; userId: number ,from:string}) {
async createAutoPipeline(req: { domains: string[]; email: string; userId: number, from: string }) {
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
let pipeline: any = {
title: req.domains[0] + `证书自动申请【${req.from??"OpenAPI"}`,
title: req.domains[0] + `证书自动申请【${req.from ?? "OpenAPI"}`,
runnableType: "pipeline",
triggers: [
{
id: nanoid(),
title: "定时触发",
props:{
props: {
cron: randomCron,
},
type: "timer"

Some files were not shown because too many files have changed in this diff Show More