Compare commits
25 Commits
ff7006e232
...
v1.37.16
| Author | SHA1 | Date | |
|---|---|---|---|
| fa14f62198 | |||
| 5526665494 | |||
| 6249af996a | |||
| e51a1b365e | |||
| f53f00d126 | |||
| ab8fbaf21d | |||
| 63d8bcf882 | |||
| e4e16bc6a6 | |||
| e4c21c4d5c | |||
| d9e6dbf889 | |||
| 16f6365b18 | |||
| cdab54bf51 | |||
| b6fea0c856 | |||
| 6f186932cc | |||
| de544ec725 | |||
| a6c0d2c6f1 | |||
| 437d956cad | |||
| 43ba0b9da6 | |||
| fe1e2c3b62 | |||
| bbe7e5f96d | |||
| 2bfad9fc65 | |||
| 9f24c18f7f | |||
| a2d1e5ea03 | |||
| b082e4e988 | |||
| 45fbce0c2a |
@@ -9,5 +9,6 @@
|
|||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
},
|
},
|
||||||
"editor.tabSize": 2
|
"editor.tabSize": 2,
|
||||||
|
"explorer.autoReveal": false
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,21 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -7,7 +7,36 @@ https://1panel.cn/docs/installation/online_installation/
|
|||||||
|
|
||||||
## 二、部署certd
|
## 二、部署certd
|
||||||
|
|
||||||
|
有两种安装方式
|
||||||
|
|
||||||
|
### 1. 应用商店方式安装【推荐】
|
||||||
|
|
||||||
|
#### 1.1 安装
|
||||||
|
打开`1Panel->应用商店`,更新远程应用,搜索`certd`,点击安装
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 1.2 访问测试:
|
||||||
|
http://ip:7001
|
||||||
|
https://ip:7002
|
||||||
|
默认账号密码
|
||||||
|
admin/123456
|
||||||
|
登录后请及时修改密码
|
||||||
|
|
||||||
|
#### 1.3 备份
|
||||||
|

|
||||||
|
|
||||||
|
#### 1.4 恢复
|
||||||
|
安装新Certd后,点击导入备份按钮,选择上面备份的文件即可
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. docker-compose方式安装
|
||||||
|
|
||||||
|
#### 2.1 安装
|
||||||
1. 打开`docker-compose.yaml`,整个内容复制下来
|
1. 打开`docker-compose.yaml`,整个内容复制下来
|
||||||
https://gitee.com/certd/certd/raw/v2/docker/run/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`目录下,您可以手动备份该目录
|
> 默认使用sqlite数据库,数据保存在`/data/certd`目录下,您可以手动备份该目录
|
||||||
> certd还支持`mysql`和`postgresql`数据库,[点我了解如何切换其他数据库](../database)
|
> certd还支持`mysql`和`postgresql`数据库,[点我了解如何切换其他数据库](../database)
|
||||||
|
|
||||||
3. 访问测试
|
#### 2.2 访问测试
|
||||||
|
|
||||||
http://ip:7001
|
http://ip:7001
|
||||||
https://ip:7002
|
https://ip:7002
|
||||||
@@ -30,7 +59,7 @@ https://ip:7002
|
|||||||
admin/123456
|
admin/123456
|
||||||
登录后请及时修改密码
|
登录后请及时修改密码
|
||||||
|
|
||||||
## 三、升级
|
#### 2.3 升级
|
||||||
|
|
||||||
1. 找到容器,点击更多->升级
|
1. 找到容器,点击更多->升级
|
||||||

|

|
||||||
@@ -39,11 +68,11 @@ admin/123456
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 四、数据备份
|
#### 2.4 备份
|
||||||
|
|
||||||
> 默认数据保存在`/data/certd`目录下,可以手动备份
|
> 默认数据保存在`/data/certd`目录下,可以手动备份
|
||||||
> 建议配置一条 [数据库备份流水线](../../use/backup/),自动备份
|
> 建议配置一条 [数据库备份流水线](../../use/backup/),自动备份
|
||||||
|
|
||||||
## 五、备份恢复
|
#### 2.5 恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
||||||
|
|||||||
@@ -65,9 +65,54 @@ docker-compose up -d
|
|||||||
|
|
||||||
## 二、从旧版的sqlite切换数据库
|
## 二、从旧版的sqlite切换数据库
|
||||||
|
|
||||||
1. 先将`旧certd`升级到最新版 (`建议:备份sqlite数据库` )
|
从旧版`sqlite`迁移到`mysql`或`postgresql`数据库
|
||||||
2. 按照上面全新安装方式部署一套`新的certd` (`注意:新旧版本的certd要一致`)
|
|
||||||
3. 使用数据库工具将数据从sqlite导入到mysql或postgresql (`注意:flyway_history数据表不要导入`)
|
|
||||||
4. 重启新certd
|
|
||||||
5. 确认没有问题之后,删除旧版certd
|
|
||||||
|
|
||||||
|
下面以 `SQLite` 转 `MySQL` 为例进行演示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 0.前提条件:
|
||||||
|
1. SQLite版Certd站点已经`升级到最新版` (`建议:备份sqlite数据库` )
|
||||||
|
2. `全新安装`MySQL版本Certd(`确保是全新的,因为里面的数据会被清空覆盖`)
|
||||||
|
3. 两套Certd站点版本一致
|
||||||
|
|
||||||
|
#### 1. 安装DBeaver工具
|
||||||
|
|
||||||
|
[https://dbeaver.io/download/](https://dbeaver.io/download/)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 2. 连接到sqlite数据库
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 3. 连接到mysql或postgresql数据库
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
#### 4. 开始同步数据
|
||||||
|
|
||||||
|
选择mysql数据库,选择所有的表(`flyway_history除外`),右键导入数据
|
||||||
|
|
||||||
|
> 切记flyway_history数据表不要导入
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
下一步、下一步,直到数据加载设置,勾选`在加载前截断目标表`(此选项很重要,并且会清空mysql certd数据库中的数据)
|
||||||
|

|
||||||
|
|
||||||
|
#### 5. 导入完成
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 6. 重启MySQL版本Certd
|
||||||
|
|
||||||
|
访问MySQL版本测试,数据已成功迁移
|
||||||
|
|
||||||
|
确认没有问题之后,删除旧版certd
|
||||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 145 KiB |
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.37.15"
|
"version": "1.37.16"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"private": false,
|
"private": false,
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "scr/index.js",
|
"module": "scr/index.js",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.37.15",
|
"@certd/basic": "^1.37.16",
|
||||||
"@peculiar/x509": "^1.11.0",
|
"@peculiar/x509": "^1.11.0",
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {log as defaultLog} from './logger.js'
|
|||||||
import axios from './axios.js'
|
import axios from './axios.js'
|
||||||
import * as util from './util.js'
|
import * as util from './util.js'
|
||||||
import {isAlpnCertificateAuthorizationValid} from './crypto/index.js'
|
import {isAlpnCertificateAuthorizationValid} from './crypto/index.js'
|
||||||
|
import {utils} from '@certd/basic'
|
||||||
|
|
||||||
const dns = dnsSdk.promises
|
const dns = dnsSdk.promises
|
||||||
|
|
||||||
@@ -60,11 +60,15 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
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)) {
|
if (!await doQuery(challengeUrl)) {
|
||||||
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
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)
|
const res = await doQuery(httpsChallengeUrl)
|
||||||
if (!res) {
|
if (!res) {
|
||||||
throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
|
throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
|
||||||
|
|||||||
@@ -26,3 +26,4 @@ dist-ssr
|
|||||||
test/user.secret.*
|
test/user.secret.*
|
||||||
test/**/*.js
|
test/**/*.js
|
||||||
src/**/*.spec.ts
|
src/**/*.spec.ts
|
||||||
|
test.mjs
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
00:56
|
01:44
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ function isIpv6(d: string) {
|
|||||||
if (!d) {
|
if (!d) {
|
||||||
return false;
|
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);
|
return isIPv6Regex.test(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function isDev() {
|
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');
|
return nodeEnv === 'development' || nodeEnv.includes('local') || nodeEnv.startsWith('dev');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { random } from "lodash-es";
|
// import { random } from "lodash-es";
|
||||||
import { locker } from "./dist/utils/util.lock.js";
|
// import { locker } from "./dist/utils/util.lock.js";
|
||||||
|
|
||||||
async function testLocker() {
|
// async function testLocker() {
|
||||||
for (let i = 0; i < 10; i++) {
|
// for (let i = 0; i < 10; i++) {
|
||||||
await locker.execute("test", async () => {
|
// await locker.execute("test", async () => {
|
||||||
console.log("test", i);
|
// console.log("test", i);
|
||||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
|
// await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
|
||||||
throw new Error("test error");
|
// 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"));
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.37.15",
|
"@certd/basic": "^1.37.16",
|
||||||
"@certd/plus-core": "^1.37.15",
|
"@certd/plus-core": "^1.37.16",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"reflect-metadata": "^0.1.13"
|
"reflect-metadata": "^0.1.13"
|
||||||
|
|||||||
@@ -423,31 +423,46 @@ export class Executor {
|
|||||||
let subject = "";
|
let subject = "";
|
||||||
let content = "";
|
let content = "";
|
||||||
const errorMessage = error?.message;
|
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") {
|
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}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
||||||
} else if (when === "success") {
|
} 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}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
||||||
} else if (when === "turnToSuccess") {
|
} 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}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
||||||
} else if (when === "error") {
|
} else if (when === "error") {
|
||||||
subject = `执行失败,${this.pipeline.title}【${this.pipeline.id}】`;
|
pipelineResult = "执行失败";
|
||||||
|
subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`;
|
||||||
if (error instanceof RunnableError) {
|
if (error instanceof RunnableError) {
|
||||||
const runnableError = error as RunnableError;
|
const runnableError = error as RunnableError;
|
||||||
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n`;
|
||||||
for (const re of runnableError.errors) {
|
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 {
|
} else {
|
||||||
|
errors = error.message;
|
||||||
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templateData.errors = errors;
|
||||||
|
templateData.pipelineResult = pipelineResult;
|
||||||
|
|
||||||
for (const notification of this.pipeline.notifications) {
|
for (const notification of this.pipeline.notifications) {
|
||||||
if (!notification.when.includes(when)) {
|
if (!notification.when.includes(when)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -455,10 +470,12 @@ export class Executor {
|
|||||||
|
|
||||||
if (notification.type === "email" && notification.options?.receivers) {
|
if (notification.type === "email" && notification.options?.receivers) {
|
||||||
try {
|
try {
|
||||||
await this.options.emailService?.send({
|
await this.options.emailService?.sendByTemplate({
|
||||||
subject,
|
type: "pipelineResult",
|
||||||
content,
|
data: templateData,
|
||||||
receivers: notification.options?.receivers,
|
email: {
|
||||||
|
receivers: notification.options?.receivers,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("send email error", e);
|
logger.error("send email error", e);
|
||||||
@@ -472,15 +489,15 @@ export class Executor {
|
|||||||
useEmail: false,
|
useEmail: false,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
body: {
|
body: {
|
||||||
|
notificationType: "pipelineResult",
|
||||||
title: subject,
|
title: subject,
|
||||||
content,
|
content,
|
||||||
userId: this.pipeline.userId,
|
userId: this.pipeline.userId,
|
||||||
pipeline: this.pipeline,
|
pipeline: this.pipeline,
|
||||||
result: this.lastRuntime?.pipeline?.status,
|
result: this.lastRuntime?.pipeline?.status,
|
||||||
pipelineId: this.pipeline.id,
|
|
||||||
historyId: this.runtime.id,
|
|
||||||
errorMessage,
|
errorMessage,
|
||||||
url,
|
url,
|
||||||
|
...templateData,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ export type NotificationBody = {
|
|||||||
historyId?: number;
|
historyId?: number;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
notificationType?: string;
|
||||||
|
attachments?: any[];
|
||||||
|
pipelineResult?: string;
|
||||||
|
pipelineTitle?: string;
|
||||||
|
errors?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationRequestHandleReqInput<T = any> = {
|
export type NotificationRequestHandleReqInput<T = any> = {
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ export type EmailSend = {
|
|||||||
html?: string;
|
html?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EmailSendByTemplateReq = {
|
||||||
|
type: string;
|
||||||
|
data: any;
|
||||||
|
email: { receivers: string[]; attachments?: any[] };
|
||||||
|
};
|
||||||
|
|
||||||
export interface IEmailService {
|
export interface IEmailService {
|
||||||
send(email: EmailSend): Promise<void>;
|
send(email: EmailSend): Promise<void>;
|
||||||
|
sendByTemplate(req: EmailSendByTemplateReq): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-huawei",
|
"name": "@certd/lib-huawei",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
"types": "./dist/d/index.d.ts",
|
"types": "./dist/d/index.d.ts",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-iframe",
|
"name": "@certd/lib-iframe",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/jdcloud",
|
"name": "@certd/jdcloud",
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"description": "jdcloud openApi sdk",
|
"description": "jdcloud openApi sdk",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-k8s",
|
"name": "@certd/lib-k8s",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.37.15",
|
"@certd/basic": "^1.37.16",
|
||||||
"@kubernetes/client-node": "0.21.0"
|
"@kubernetes/client-node": "0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.37.15",
|
"@certd/acme-client": "^1.37.16",
|
||||||
"@certd/basic": "^1.37.15",
|
"@certd/basic": "^1.37.16",
|
||||||
"@certd/pipeline": "^1.37.15",
|
"@certd/pipeline": "^1.37.16",
|
||||||
"@certd/plugin-lib": "^1.37.15",
|
"@certd/plugin-lib": "^1.37.16",
|
||||||
"@certd/plus-core": "^1.37.15",
|
"@certd/plus-core": "^1.37.16",
|
||||||
"@midwayjs/cache": "3.14.0",
|
"@midwayjs/cache": "3.14.0",
|
||||||
"@midwayjs/core": "3.20.11",
|
"@midwayjs/core": "3.20.11",
|
||||||
"@midwayjs/i18n": "3.20.13",
|
"@midwayjs/i18n": "3.20.13",
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ export class SysLicenseInfo extends BaseSettings {
|
|||||||
license?: string;
|
license?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type EmailTemplate = {
|
||||||
|
addonId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class SysEmailConf extends BaseSettings {
|
export class SysEmailConf extends BaseSettings {
|
||||||
static __title__ = '邮箱配置';
|
static __title__ = '邮箱配置';
|
||||||
static __key__ = 'sys.email';
|
static __key__ = 'sys.email';
|
||||||
@@ -126,6 +131,13 @@ export class SysEmailConf extends BaseSettings {
|
|||||||
};
|
};
|
||||||
sender: string;
|
sender: string;
|
||||||
usePlus?: boolean;
|
usePlus?: boolean;
|
||||||
|
|
||||||
|
templates:{
|
||||||
|
registerCode?: EmailTemplate,
|
||||||
|
forgotPassword?: EmailTemplate,
|
||||||
|
pipelineResult?: EmailTemplate,
|
||||||
|
common?: EmailTemplate,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SysSiteInfo extends BaseSettings {
|
export class SysSiteInfo extends BaseSettings {
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/midway-flyway-js",
|
"name": "@certd/midway-flyway-js",
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-cert",
|
"name": "@certd/plugin-cert",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.37.15",
|
"@certd/acme-client": "^1.37.16",
|
||||||
"@certd/basic": "^1.37.15",
|
"@certd/basic": "^1.37.16",
|
||||||
"@certd/pipeline": "^1.37.15",
|
"@certd/pipeline": "^1.37.16",
|
||||||
"@certd/plugin-lib": "^1.37.15",
|
"@certd/plugin-lib": "^1.37.16",
|
||||||
"@google-cloud/publicca": "^1.3.0",
|
"@google-cloud/publicca": "^1.3.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ export class DomainParser implements IDomainParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parse(fullDomain: string) {
|
async parse(fullDomain: string) {
|
||||||
|
//如果是ip
|
||||||
|
if (utils.domain.isIp(fullDomain)) {
|
||||||
|
return fullDomain;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.info(`查找主域名:${fullDomain}`);
|
this.logger.info(`查找主域名:${fullDomain}`);
|
||||||
const cacheKey = `domain_parse:${fullDomain}`;
|
const cacheKey = `domain_parse:${fullDomain}`;
|
||||||
const value = utils.cache.get(cacheKey);
|
const value = utils.cache.get(cacheKey);
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
|||||||
title: `证书申请成功【${this.pipeline.title}】`,
|
title: `证书申请成功【${this.pipeline.title}】`,
|
||||||
content: `域名:${this.domains.join(",")}`,
|
content: `域名:${this.domains.join(",")}`,
|
||||||
url: url,
|
url: url,
|
||||||
|
notificationType: "certApplySuccess",
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await this.ctx.notificationService.send({
|
await this.ctx.notificationService.send({
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
if(form.challengeType === 'cname' ){
|
if(form.challengeType === 'cname' ){
|
||||||
return '请按照上面的提示,给要申请证书的域名添加CNAME记录,添加后,点击验证,验证成功后不要删除记录,申请和续期证书会一直用它'
|
return '请按照上面的提示,给要申请证书的域名添加CNAME记录,添加后,点击验证,验证成功后不要删除记录,申请和续期证书会一直用它'
|
||||||
}else if (form.challengeType === 'http'){
|
}else if (form.challengeType === 'http'){
|
||||||
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录下'
|
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录的.well-known/acme-challenge/目录下'
|
||||||
}else if (form.challengeType === 'http'){
|
}else if (form.challengeType === 'http'){
|
||||||
return '给每个域名单独配置dns提供商'
|
return '给每个域名单独配置dns提供商'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
"@alicloud/tea-util": "^1.4.10",
|
"@alicloud/tea-util": "^1.4.10",
|
||||||
"@aws-sdk/client-s3": "^3.787.0",
|
"@aws-sdk/client-s3": "^3.787.0",
|
||||||
"@certd/basic": "^1.37.15",
|
"@certd/basic": "^1.37.16",
|
||||||
"@certd/pipeline": "^1.37.15",
|
"@certd/pipeline": "^1.37.16",
|
||||||
"@kubernetes/client-node": "0.21.0",
|
"@kubernetes/client-node": "0.21.0",
|
||||||
"ali-oss": "^6.22.0",
|
"ali-oss": "^6.22.0",
|
||||||
"basic-ftp": "^5.0.5",
|
"basic-ftp": "^5.0.5",
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
"zod-defaults": "^0.1.3"
|
"zod-defaults": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.37.15",
|
"@certd/lib-iframe": "^1.37.16",
|
||||||
"@certd/pipeline": "^1.37.15",
|
"@certd/pipeline": "^1.37.16",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -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 InputPassword from "./common/input-password.vue";
|
||||||
import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
|
import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
|
||||||
import ApiTest from "./common/api-test.vue";
|
import ApiTest from "./common/api-test.vue";
|
||||||
|
import ParamsShow from "./common/params-show.vue";
|
||||||
export * from "./cert/index.js";
|
export * from "./cert/index.js";
|
||||||
export default {
|
export default {
|
||||||
install(app: any) {
|
install(app: any) {
|
||||||
@@ -29,5 +30,6 @@ export default {
|
|||||||
app.component("RemoteInput", RemoteInput);
|
app.component("RemoteInput", RemoteInput);
|
||||||
app.component("CertDomainsGetter", CertDomainsGetter);
|
app.component("CertDomainsGetter", CertDomainsGetter);
|
||||||
app.component("InputPassword", InputPassword);
|
app.component("InputPassword", InputPassword);
|
||||||
|
app.component("ParamsShow", ParamsShow);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -631,7 +631,7 @@ export default {
|
|||||||
autoHideTime: "Auto Hide Time",
|
autoHideTime: "Auto Hide Time",
|
||||||
autoHideTimeHelper: "Minutes without requests before auto hiding",
|
autoHideTimeHelper: "Minutes without requests before auto hiding",
|
||||||
hideOpenApi: "Hide Open API",
|
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",
|
hideSiteImmediately: "Hide Site Immediately",
|
||||||
hideImmediately: "Hide Immediately",
|
hideImmediately: "Hide Immediately",
|
||||||
confirmHideSiteTitle: "Are you sure to hide the site 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)",
|
oauthAutoRedirectHelper: "Whether to auto redirect to OAuth2 login when login (using the first enabled OAuth2 login type)",
|
||||||
oauthOnly: "OAuth2 Login Only",
|
oauthOnly: "OAuth2 Login Only",
|
||||||
oauthOnlyHelper: "Whether to only allow OAuth2 login, disable password login",
|
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: {
|
modal: {
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ export default {
|
|||||||
autoHideTime: "自动隐藏时间",
|
autoHideTime: "自动隐藏时间",
|
||||||
autoHideTimeHelper: "多少分钟内无请求自动隐藏",
|
autoHideTimeHelper: "多少分钟内无请求自动隐藏",
|
||||||
hideOpenApi: "隐藏开放接口",
|
hideOpenApi: "隐藏开放接口",
|
||||||
hideOpenApiHelper: "是否隐藏开放接口,是否放开/api/v1开头的接口",
|
hideOpenApiHelper: "是否隐藏开放接口,是否同时隐藏/api/v1开头的接口",
|
||||||
hideSiteImmediately: "立即隐藏站点",
|
hideSiteImmediately: "立即隐藏站点",
|
||||||
hideImmediately: "立即隐藏",
|
hideImmediately: "立即隐藏",
|
||||||
confirmHideSiteTitle: "确定要立即隐藏站点吗?",
|
confirmHideSiteTitle: "确定要立即隐藏站点吗?",
|
||||||
@@ -780,6 +780,18 @@ export default {
|
|||||||
oauthAutoRedirectHelper: "是否自动跳转第三方登录(使用第一个已启用的第三方登录类型)",
|
oauthAutoRedirectHelper: "是否自动跳转第三方登录(使用第一个已启用的第三方登录类型)",
|
||||||
oauthOnly: "仅使用第三方登录",
|
oauthOnly: "仅使用第三方登录",
|
||||||
oauthOnlyHelper: "是否仅使用第三方登录,关闭密码登录(注意:请务必在测试第三方登录功能正常后再开启)",
|
oauthOnlyHelper: "是否仅使用第三方登录,关闭密码登录(注意:请务必在测试第三方登录功能正常后再开启)",
|
||||||
|
|
||||||
|
email: {
|
||||||
|
templates: "邮件模板",
|
||||||
|
templateType: "模板类型",
|
||||||
|
templateProvider: "模板配置",
|
||||||
|
|
||||||
|
templateSetting: "邮件模板设置",
|
||||||
|
serverSetting: "邮件服务器设置",
|
||||||
|
sendTest: "发送测试",
|
||||||
|
|
||||||
|
templateProviderSelectorPlaceholder: "未配置",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
|
|||||||
@@ -1,4 +1,26 @@
|
|||||||
import Validator from "async-validator";
|
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) {
|
export function isDomain(rule: any, value: any) {
|
||||||
if (value == null || value == "") {
|
if (value == null || value == "") {
|
||||||
@@ -14,7 +36,9 @@ export function isDomain(rule: any, value: any) {
|
|||||||
const compiled = new RegExp(exp);
|
const compiled = new RegExp(exp);
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
//域名可以是泛域名,中文域名,数字域名,英文域名,域名中可以包含-和. ,可以_开头
|
//域名可以是泛域名,中文域名,数字域名,英文域名,域名中可以包含-和. ,可以_开头
|
||||||
|
if (isIp(domain)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!compiled.test(domain)) {
|
if (!compiled.test(domain)) {
|
||||||
throw new Error(`域名有误:${domain},请输入正确的域名`);
|
throw new Error(`域名有误:${domain},请输入正确的域名`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
|
:disabled="disabled"
|
||||||
:show-current="false"
|
:show-current="false"
|
||||||
:show-select="false"
|
:show-select="false"
|
||||||
:dialog="{ width: 960 }"
|
:dialog="{ width: 960 }"
|
||||||
@@ -147,6 +148,7 @@ async function refreshTarget(value: any) {
|
|||||||
type: "",
|
type: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 * as api from "../api";
|
||||||
import { useSettingStore } from "/@/store/settings";
|
import { useSettingStore } from "/@/store/settings";
|
||||||
import { useI18n } from "/src/locales";
|
import { useI18n } from "/src/locales";
|
||||||
|
import { computed } from "vue";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -20,7 +21,7 @@ async function batchUpdateRequest(form: any) {
|
|||||||
await api.BatchUpdateTrigger(props.selectedRowKeys, {
|
await api.BatchUpdateTrigger(props.selectedRowKeys, {
|
||||||
title: "定时触发",
|
title: "定时触发",
|
||||||
type: "timer",
|
type: "timer",
|
||||||
props: form.props,
|
props: form.clear ? false : form.props,
|
||||||
});
|
});
|
||||||
emit("change");
|
emit("change");
|
||||||
}
|
}
|
||||||
@@ -33,6 +34,28 @@ async function openFormDialog() {
|
|||||||
settingStore.checkPlus();
|
settingStore.checkPlus();
|
||||||
const crudOptions: any = {
|
const crudOptions: any = {
|
||||||
columns: {
|
columns: {
|
||||||
|
clear: {
|
||||||
|
title: "设置/清空",
|
||||||
|
form: {
|
||||||
|
value: false,
|
||||||
|
component: {
|
||||||
|
name: "fs-dict-switch",
|
||||||
|
vModel: "checked",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
label: "设置定时",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "清空定时",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"props.cron": {
|
"props.cron": {
|
||||||
title: t("certd.schedule"),
|
title: t("certd.schedule"),
|
||||||
form: {
|
form: {
|
||||||
@@ -40,12 +63,18 @@ async function openFormDialog() {
|
|||||||
name: "cron-editor",
|
name: "cron-editor",
|
||||||
vModel: "modelValue",
|
vModel: "modelValue",
|
||||||
},
|
},
|
||||||
|
show: compute(({ form }) => {
|
||||||
|
return form.clear !== true;
|
||||||
|
}),
|
||||||
rules: [{ required: true, message: t("certd.selectCron") }],
|
rules: [{ required: true, message: t("certd.selectCron") }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
mode: "edit",
|
mode: "edit",
|
||||||
|
initialForm: {
|
||||||
|
clear: false,
|
||||||
|
},
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
async doSubmit({ form }) {
|
async doSubmit({ form }) {
|
||||||
await batchUpdateRequest(form);
|
await batchUpdateRequest(form);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
const apiPrefix = "/mine/email";
|
const apiPrefix = "/mine/email";
|
||||||
|
const apiSettingPrefix = "/sys/settings";
|
||||||
|
|
||||||
export async function TestSend(receiver: string) {
|
export async function TestSend(receiver: string) {
|
||||||
await request({
|
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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="flex-o">
|
<div class="email-form">
|
||||||
<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">
|
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" class="" @finish="onFinish">
|
||||||
<div v-if="!formState.usePlus" class="email-form">
|
<h2>{{ t("certd.sys.setting.email.serverSetting") }}</h2>
|
||||||
<a-form-item :label="t('certd.useCustomEmailServer')"> </a-form-item>
|
<a-tabs v-model:active-key="activeKey" @change="onChangeActiveKey">
|
||||||
<a-form-item :label="t('certd.smtpDomain')" name="host" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpDomain') }]">
|
<a-tab-pane key="custom" :tab="t('certd.useCustomEmailServer')">
|
||||||
<a-input v-model:value="formState.host" />
|
<div>
|
||||||
</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>
|
||||||
|
|
||||||
<a-form-item :label="t('certd.smtpPort')" name="port" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpPort') }]">
|
<a-form-item :label="t('certd.smtpPort')" name="port" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpPort') }]">
|
||||||
<a-input v-model:value="formState.port" />
|
<a-input v-model:value="formState.port" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="t('certd.username')" :name="['auth', 'user']" :rules="[{ required: true, message: t('certd.pleaseEnterUsername') }]">
|
<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-input v-model:value="formState.auth.user" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.password')" :name="['auth', 'pass']" :rules="[{ required: true, message: t('certd.pleaseEnterPassword') }]">
|
<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" />
|
<a-input-password v-model:value="formState.auth.pass" />
|
||||||
<div class="helper">{{ t("certd.qqEmailAuthCodeHelper") }}</div>
|
<div class="helper">{{ t("certd.qqEmailAuthCodeHelper") }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.senderEmail')" name="sender" :rules="[{ required: true, message: t('certd.pleaseEnterSenderEmail') }]">
|
<a-form-item :label="t('certd.senderEmail')" name="sender" :rules="[{ required: true, message: t('certd.pleaseEnterSenderEmail') }]">
|
||||||
<a-input v-model:value="formState.sender" />
|
<a-input v-model:value="formState.sender" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.useSsl')" name="secure">
|
<a-form-item :label="t('certd.useSsl')" name="secure">
|
||||||
<a-switch v-model:checked="formState.secure" />
|
<a-switch v-model:checked="formState.secure" />
|
||||||
<div class="helper">{{ t("certd.sslPortNote") }}</div>
|
<div class="helper">{{ t("certd.sslPortNote") }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.ignoreCertValidation')" :name="['tls', 'rejectUnauthorized']">
|
<a-form-item :label="t('certd.ignoreCertValidation')" :name="['tls', 'rejectUnauthorized']">
|
||||||
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
||||||
</a-form-item>
|
</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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="helper">{{ t("certd.useOfficialEmailServerHelper") }}</div>
|
</a-tab-pane>
|
||||||
</a-form-item>
|
<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>
|
</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>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="email-form">
|
<div class="email-form">
|
||||||
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onTestSend">
|
<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-form-item :label="t('certd.testReceiverEmail')" name="receiver" :rules="[{ required: true, message: t('certd.pleaseEnterTestReceiverEmail') }]">
|
||||||
<a-input v-model:value="testFormState.receiver" />
|
<a-input v-model:value="testFormState.receiver" />
|
||||||
<div class="helper">{{ t("certd.saveBeforeTest") }}</div>
|
<div class="helper">{{ t("certd.saveBeforeTest") }}</div>
|
||||||
@@ -71,14 +122,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive } from "vue";
|
import { onMounted, reactive, ref } from "vue";
|
||||||
import * as api from "../api";
|
import * as api from "../api";
|
||||||
import * as emailApi from "./api.email";
|
import * as emailApi from "./api.email";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import { useSettingStore } from "/src/store/settings";
|
import { useSettingStore } from "/src/store/settings";
|
||||||
import * as _ from "lodash-es";
|
import * as _ from "lodash-es";
|
||||||
import { useI18n } from "/src/locales";
|
import { useI18n } from "/src/locales";
|
||||||
|
import AddonSelector from "../../../certd/addon/addon-selector/index.vue";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "EmailSetting",
|
name: "EmailSetting",
|
||||||
@@ -98,8 +149,14 @@ interface FormState {
|
|||||||
};
|
};
|
||||||
sender: string;
|
sender: string;
|
||||||
usePlus: boolean;
|
usePlus: boolean;
|
||||||
|
templates: {
|
||||||
|
pipelineResult: any;
|
||||||
|
registerCode: any;
|
||||||
|
forgotPassword: any;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeKey = ref("custom");
|
||||||
const formState = reactive<Partial<FormState>>({
|
const formState = reactive<Partial<FormState>>({
|
||||||
auth: {
|
auth: {
|
||||||
user: "",
|
user: "",
|
||||||
@@ -109,27 +166,65 @@ const formState = reactive<Partial<FormState>>({
|
|||||||
usePlus: false,
|
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() {
|
async function load() {
|
||||||
const data: any = await api.EmailSettingsGet();
|
const data: any = await api.EmailSettingsGet();
|
||||||
_.merge(formState, data);
|
_.merge(formState, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
load();
|
onMounted(async () => {
|
||||||
|
await load();
|
||||||
|
refreshActiveKeyByUsePlus();
|
||||||
|
await loadEmailTemplates();
|
||||||
|
});
|
||||||
|
|
||||||
const onFinish = async (form: any) => {
|
const onFinish = async (form: any) => {
|
||||||
console.log("Success:", form);
|
fillEmailTemplates(form);
|
||||||
await api.EmailSettingsSave(form);
|
await api.EmailSettingsSave(form);
|
||||||
notification.success({
|
notification.success({
|
||||||
message: t("certd.saveSuccess"),
|
message: t("certd.saveSuccess"),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFinishFailed = (errorInfo: any) => {
|
|
||||||
// console.log("Failed:", errorInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function onUsePlusChanged() {
|
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 {
|
interface TestFormState {
|
||||||
@@ -157,12 +252,21 @@ const settingStore = useSettingStore();
|
|||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.page-setting-email {
|
.page-setting-email {
|
||||||
|
.ant-tabs-nav {
|
||||||
|
margin-left: 80px;
|
||||||
|
}
|
||||||
.email-form-box {
|
.email-form-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 20px 0px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.email-form {
|
.email-form {
|
||||||
width: 500px;
|
width: 700px;
|
||||||
|
max-width: 100%;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,5 +276,10 @@ const settingStore = useSettingStore();
|
|||||||
color: #999;
|
color: #999;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
.addon-selector {
|
||||||
|
.inner {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<vip-button class="ml-5" mode="button"></vip-button>
|
<vip-button class="ml-5" mode="button"></vip-button>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</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">
|
<div class="flex flex-wrap">
|
||||||
<table class="w-full table-auto border-collapse border border-gray-400">
|
<table class="w-full table-auto border-collapse border border-gray-400">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -31,7 +31,14 @@
|
|||||||
</fs-copyable>
|
</fs-copyable>
|
||||||
</td>
|
</td>
|
||||||
<td class="border border-gray-300 px-4 py-2">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -22,3 +22,4 @@ run/
|
|||||||
tools/lego/*
|
tools/lego/*
|
||||||
!tools/lego/readme.md
|
!tools/lego/readme.md
|
||||||
test.mjs
|
test.mjs
|
||||||
|
isolate-*.log
|
||||||
@@ -3,6 +3,20 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [1.37.15](https://github.com/certd/certd/compare/v1.37.14...v1.37.15) (2025-12-06)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-server",
|
"name": "@certd/ui-server",
|
||||||
"version": "1.37.15",
|
"version": "1.37.16",
|
||||||
"description": "fast-server base midway",
|
"description": "fast-server base midway",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env NODE_ENV=production node ./bootstrap.js",
|
"start": "cross-env NODE_ENV=production node --optimize-for-size ./bootstrap.js",
|
||||||
"dev-start": "mwtsc --watch --run @midwayjs/mock/app",
|
"dev-start": "cross-env NODE_ENV=dev & mwtsc --watch --run @midwayjs/mock/app",
|
||||||
"dc": "cd ../../../ && pnpm run dev",
|
"dc": "cd ../../../ && pnpm run dev",
|
||||||
"dev": "cross-env NODE_ENV=local & pnpm run dev-start",
|
"dev": "cross-env NODE_ENV=local & pnpm run dev-start",
|
||||||
"dev-commlocal": "cross-env NODE_ENV=dev-commlocal mwtsc --watch --run @midwayjs/mock/app",
|
"dev-commlocal": "cross-env NODE_ENV=dev-commlocal mwtsc --watch --run @midwayjs/mock/app",
|
||||||
@@ -23,13 +23,13 @@
|
|||||||
"lint": "mwts check",
|
"lint": "mwts check",
|
||||||
"lint:fix": "mwts fix",
|
"lint:fix": "mwts fix",
|
||||||
"ci": "npm run cov",
|
"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-metadata": "node export-plugin-yaml.js",
|
||||||
"export-md": "node export-plugin-md.js",
|
"export-md": "node export-plugin-md.js",
|
||||||
"dev-build": "echo 1",
|
"dev-build": "echo 1",
|
||||||
"build-on-docker": "node ./before-build.js && npm run build",
|
"build-on-docker": "node ./before-build.js && npm run build",
|
||||||
"up-mw-deps": "npx midway-version -u -w",
|
"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",
|
"flame": "clinic flame -- node ./bootstrap.js",
|
||||||
"tsc": "tsc --skipLibCheck",
|
"tsc": "tsc --skipLibCheck",
|
||||||
"slimming": "node ./slimming.js",
|
"slimming": "node ./slimming.js",
|
||||||
@@ -45,20 +45,20 @@
|
|||||||
"@aws-sdk/client-cloudfront": "^3.699.0",
|
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||||
"@aws-sdk/client-iam": "^3.699.0",
|
"@aws-sdk/client-iam": "^3.699.0",
|
||||||
"@aws-sdk/client-s3": "^3.705.0",
|
"@aws-sdk/client-s3": "^3.705.0",
|
||||||
"@certd/acme-client": "^1.37.15",
|
"@certd/acme-client": "^1.37.16",
|
||||||
"@certd/basic": "^1.37.15",
|
"@certd/basic": "^1.37.16",
|
||||||
"@certd/commercial-core": "^1.37.15",
|
"@certd/commercial-core": "^1.37.16",
|
||||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||||
"@certd/jdcloud": "^1.37.15",
|
"@certd/jdcloud": "^1.37.16",
|
||||||
"@certd/lib-huawei": "^1.37.15",
|
"@certd/lib-huawei": "^1.37.16",
|
||||||
"@certd/lib-k8s": "^1.37.15",
|
"@certd/lib-k8s": "^1.37.16",
|
||||||
"@certd/lib-server": "^1.37.15",
|
"@certd/lib-server": "^1.37.16",
|
||||||
"@certd/midway-flyway-js": "^1.37.15",
|
"@certd/midway-flyway-js": "^1.37.16",
|
||||||
"@certd/pipeline": "^1.37.15",
|
"@certd/pipeline": "^1.37.16",
|
||||||
"@certd/plugin-cert": "^1.37.15",
|
"@certd/plugin-cert": "^1.37.16",
|
||||||
"@certd/plugin-lib": "^1.37.15",
|
"@certd/plugin-lib": "^1.37.16",
|
||||||
"@certd/plugin-plus": "^1.37.15",
|
"@certd/plugin-plus": "^1.37.16",
|
||||||
"@certd/plus-core": "^1.37.15",
|
"@certd/plus-core": "^1.37.16",
|
||||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||||
"@koa/cors": "^5.0.0",
|
"@koa/cors": "^5.0.0",
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import * as upload from '@midwayjs/upload';
|
|||||||
import { setLogger } from '@certd/acme-client';
|
import { setLogger } from '@certd/acme-client';
|
||||||
import {HiddenMiddleware} from "./middleware/hidden.js";
|
import {HiddenMiddleware} from "./middleware/hidden.js";
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
// process.env.UV_THREADPOOL_SIZE = 2
|
||||||
process.on('uncaughtException', error => {
|
process.on('uncaughtException', error => {
|
||||||
console.error('未捕获的异常:', 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 { 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 { CodeService } from "../../modules/basic/service/code-service.js";
|
||||||
import { EmailService } from "../../modules/basic/service/email-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 {
|
export class SmsCodeReq {
|
||||||
@Rule(RuleType.string().required())
|
@Rule(RuleType.string().required())
|
||||||
@@ -49,6 +50,9 @@ export class BasicController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
captchaService: CaptchaService;
|
captchaService: CaptchaService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
addonGetterService: AddonGetterService;
|
||||||
|
|
||||||
@Post('/captcha/get', { summary: Constants.per.guest })
|
@Post('/captcha/get', { summary: Constants.per.guest })
|
||||||
async getCaptcha(@Query("captchaAddonId") captchaAddonId:number) {
|
async getCaptcha(@Query("captchaAddonId") captchaAddonId:number) {
|
||||||
const form = await this.captchaService.getCaptcha(captchaAddonId)
|
const form = await this.captchaService.getCaptcha(captchaAddonId)
|
||||||
@@ -83,17 +87,18 @@ export class BasicController extends BaseController {
|
|||||||
const opts = {
|
const opts = {
|
||||||
verificationType: body.verificationType,
|
verificationType: body.verificationType,
|
||||||
verificationCodeLength: undefined,
|
verificationCodeLength: undefined,
|
||||||
title: undefined,
|
|
||||||
content: undefined,
|
|
||||||
duration: undefined,
|
duration: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(body?.verificationType === 'forgotPassword') {
|
if(body?.verificationType === 'forgotPassword') {
|
||||||
opts.title = '找回密码';
|
|
||||||
opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略';
|
|
||||||
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
|
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
|
||||||
opts.verificationCodeLength = 6;
|
opts.verificationCodeLength = 6;
|
||||||
|
}else{
|
||||||
|
opts.duration = 10;
|
||||||
|
opts.verificationCodeLength = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await this.codeService.checkCaptcha(body.captcha);
|
await this.codeService.checkCaptcha(body.captcha);
|
||||||
await this.codeService.sendEmailCode(body.email, opts);
|
await this.codeService.sendEmailCode(body.email, opts);
|
||||||
// 设置缓存内容
|
// 设置缓存内容
|
||||||
|
|||||||
@@ -169,9 +169,10 @@ export class ConnectController extends BaseController {
|
|||||||
const userInfo = validationValue.userInfo;
|
const userInfo = validationValue.userInfo;
|
||||||
const oauthType = validationValue.type;
|
const oauthType = validationValue.type;
|
||||||
let newUser = new UserEntity()
|
let newUser = new UserEntity()
|
||||||
newUser.username = `${oauthType}_${userInfo.nickName}_${simpleNanoId(6)}`;
|
newUser.username = `${userInfo.nickName}_${simpleNanoId(6)}_${oauthType}`;
|
||||||
newUser.avatar = userInfo.avatar;
|
newUser.avatar = userInfo.avatar;
|
||||||
newUser.nickName = userInfo.nickName || simpleNanoId(6);
|
newUser.nickName = userInfo.nickName || simpleNanoId(6);
|
||||||
|
newUser.email = userInfo.email || "";
|
||||||
|
|
||||||
newUser = await this.userService.register("username", newUser, async (txManager) => {
|
newUser = await this.userService.register("username", newUser, async (txManager) => {
|
||||||
const oauthBound: OauthBoundEntity = new OauthBoundEntity()
|
const oauthBound: OauthBoundEntity = new OauthBoundEntity()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||||
import {
|
import {
|
||||||
addonRegistry,
|
addonRegistry,
|
||||||
|
AddonService,
|
||||||
CrudController,
|
CrudController,
|
||||||
SysPrivateSettings,
|
SysPrivateSettings,
|
||||||
SysPublicSettings,
|
SysPublicSettings,
|
||||||
@@ -30,6 +31,8 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
pipelineService: PipelineService;
|
pipelineService: PipelineService;
|
||||||
@Inject()
|
@Inject()
|
||||||
codeService: CodeService;
|
codeService: CodeService;
|
||||||
|
@Inject()
|
||||||
|
addonService: AddonService;
|
||||||
|
|
||||||
getService() {
|
getService() {
|
||||||
return this.service;
|
return this.service;
|
||||||
@@ -86,6 +89,25 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
return this.ok(conf);
|
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' })
|
@Post('/saveEmailSettings', { summary: 'sys:settings:edit' })
|
||||||
async saveEmailSettings(@Body(ALL) body) {
|
async saveEmailSettings(@Body(ALL) body) {
|
||||||
const conf = await getEmailSettings(this.service, this.userSettingsService);
|
const conf = await getEmailSettings(this.service, this.userSettingsService);
|
||||||
|
|||||||
@@ -130,7 +130,8 @@ export class AutoCRegisterCron {
|
|||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
errorMessage:title,
|
errorMessage:title,
|
||||||
url
|
url,
|
||||||
|
notificationType: "vipExpireRemind",
|
||||||
}
|
}
|
||||||
},adminUser.id)
|
},adminUser.id)
|
||||||
}
|
}
|
||||||
@@ -182,7 +183,8 @@ export class AutoCRegisterCron {
|
|||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
errorMessage: title,
|
errorMessage: title,
|
||||||
url
|
url,
|
||||||
|
notificationType: "userExpireRemind",
|
||||||
}
|
}
|
||||||
}, user.id)
|
}, user.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class AutoZPrint {
|
|||||||
async init() {
|
async init() {
|
||||||
//监听https
|
//监听https
|
||||||
this.startHttpsServer();
|
this.startHttpsServer();
|
||||||
|
logger.info("ENV:", process.env.NODE_ENV);
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
this.startHeapLog();
|
this.startHeapLog();
|
||||||
}
|
}
|
||||||
@@ -49,7 +50,7 @@ export class AutoZPrint {
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const mu = process.memoryUsage();
|
const mu = process.memoryUsage();
|
||||||
logger.info(`rss:${format(mu.rss)},heapUsed: ${format(mu.heapUsed)},heapTotal: ${format(mu.heapTotal)},external: ${format(mu.external)}`);
|
logger.info(`rss:${format(mu.rss)},heapUsed: ${format(mu.heapUsed)},heapTotal: ${format(mu.heapTotal)},external: ${format(mu.external)}`);
|
||||||
}, 60000);
|
}, 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
startHttpsServer() {
|
startHttpsServer() {
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
|
||||||
import { cache, isDev, randomNumber, simpleNanoId } from '@certd/basic';
|
import { cache, isDev, randomNumber, simpleNanoId } from '@certd/basic';
|
||||||
import { SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
import { AccessService, AccessSysGetter, CodeErrorException, SysSettingsService } from '@certd/lib-server';
|
||||||
import { SmsServiceFactory } from '../sms/factory.js';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { ISmsService } from '../sms/api.js';
|
import { ISmsService } from '../sms/api.js';
|
||||||
import { CodeErrorException } from '@certd/lib-server';
|
import { SmsServiceFactory } from '../sms/factory.js';
|
||||||
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 { CaptchaService } from "./captcha-service.js";
|
import { CaptchaService } from "./captcha-service.js";
|
||||||
|
import { EmailService } from './email-service.js';
|
||||||
|
|
||||||
// {data: '<svg.../svg>', text: 'abcd'}
|
// {data: '<svg.../svg>', text: 'abcd'}
|
||||||
/**
|
/**
|
||||||
@@ -84,8 +80,6 @@ export class CodeService {
|
|||||||
async sendEmailCode(
|
async sendEmailCode(
|
||||||
email: string,
|
email: string,
|
||||||
opts?: {
|
opts?: {
|
||||||
title?: string,
|
|
||||||
content?: string,
|
|
||||||
duration?: number,
|
duration?: number,
|
||||||
verificationType?: string,
|
verificationType?: string,
|
||||||
verificationCodeLength?: number,
|
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 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 duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1));
|
||||||
|
|
||||||
const code = randomNumber(verificationCodeLength);
|
const code = randomNumber(verificationCodeLength);
|
||||||
|
|
||||||
const title = `【${siteTitle}】${!!opts?.title ? opts.title : '验证码'}`;
|
const templateData = {
|
||||||
const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`;
|
code, duration,
|
||||||
|
title: "验证码",
|
||||||
await this.emailService.send({
|
content:`您的验证码是${code},请勿泄露`,
|
||||||
subject: title,
|
notificationType: "registerCode"
|
||||||
content: content,
|
}
|
||||||
receivers: [email],
|
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);
|
const key = this.buildEmailCodeKey(email,opts?.verificationType);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
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 { IEmailService } from '@certd/pipeline';
|
||||||
|
|
||||||
import { logger } from '@certd/basic';
|
import { logger } from '@certd/basic';
|
||||||
@@ -8,9 +8,11 @@ import { isComm, isPlus } from '@certd/plus-core';
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import { SendMailOptions } from 'nodemailer';
|
import { SendMailOptions } from 'nodemailer';
|
||||||
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
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 { getEmailSettings } from '../../sys/settings/fix.js';
|
||||||
import { UserEmailSetting } from "../../mine/service/models.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 = {
|
export type EmailConfig = {
|
||||||
host: string;
|
host: string;
|
||||||
@@ -38,6 +40,12 @@ export class EmailService implements IEmailService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
plusService: PlusService;
|
plusService: PlusService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
addonGetterService: AddonGetterService;
|
||||||
|
@Inject()
|
||||||
|
addonService: AddonService
|
||||||
|
|
||||||
|
|
||||||
async sendByPlus(email: EmailSend) {
|
async sendByPlus(email: EmailSend) {
|
||||||
if (!isPlus()) {
|
if (!isPlus()) {
|
||||||
throw new Error('plus not enabled');
|
throw new Error('plus not enabled');
|
||||||
@@ -49,7 +57,6 @@ export class EmailService implements IEmailService {
|
|||||||
* content: string;
|
* content: string;
|
||||||
* receivers: string[];
|
* receivers: string[];
|
||||||
*/
|
*/
|
||||||
|
|
||||||
await this.plusService.sendEmail(email);
|
await this.plusService.sendEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,26 +69,6 @@ export class EmailService implements IEmailService {
|
|||||||
throw new Error('收件人不能为空');
|
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';
|
let sysTitle = 'Certd';
|
||||||
if (isComm()) {
|
if (isComm()) {
|
||||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||||
@@ -93,10 +80,34 @@ export class EmailService implements IEmailService {
|
|||||||
if (!subject.includes(`【${sysTitle}】`)) {
|
if (!subject.includes(`【${sysTitle}】`)) {
|
||||||
subject = `【${sysTitle}】${subject}`;
|
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 = {
|
const mailOptions = {
|
||||||
from: `${sysTitle} <${emailConfig.sender}>`,
|
from: `${sysTitle} <${emailConfig.sender}>`,
|
||||||
to: email.receivers.join(', '), // list of receivers
|
to: email.receivers.join(', '), // list of receivers
|
||||||
subject: subject,
|
subject: email.subject,
|
||||||
text: email.content,
|
text: email.content,
|
||||||
html: email.html,
|
html: email.html,
|
||||||
attachments: email.attachments,
|
attachments: email.attachments,
|
||||||
@@ -105,30 +116,78 @@ export class EmailService implements IEmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async test(userId: number, receiver: string) {
|
async test(userId: number, receiver: string) {
|
||||||
await this.send({
|
await this.sendByTemplate({
|
||||||
receivers: [receiver],
|
type: "common",
|
||||||
subject: '测试邮件,from certd',
|
data: {
|
||||||
content: '测试邮件,from certd',
|
title: '测试邮件,from certd',
|
||||||
|
content: '测试邮件,from certd',
|
||||||
|
url:"https://certd.handfree.work",
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
receivers: [receiver],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(userId: any) {
|
async list(userId: any) {
|
||||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
|
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
|
||||||
return userEmailSetting.list;
|
return userEmailSetting.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(userId: any, email: string) {
|
async delete(userId: any, email: string) {
|
||||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
|
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
|
||||||
userEmailSetting.list = userEmailSetting.list.filter(item=>item !== email);
|
userEmailSetting.list = userEmailSetting.list.filter(item => item !== email);
|
||||||
await this.settingsService.saveSetting(userId,userEmailSetting)
|
await this.settingsService.saveSetting(userId, userEmailSetting)
|
||||||
}
|
}
|
||||||
async add(userId: any, email: string) {
|
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
|
return
|
||||||
}
|
}
|
||||||
userEmailSetting.list.unshift(email)
|
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 parser from 'cron-parser';
|
||||||
import { ILogger } from '@certd/basic';
|
import { ILogger, logger } from '@certd/basic';
|
||||||
|
|
||||||
export type CronTaskReq = {
|
export type CronTaskReq = {
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +52,7 @@ export class Cron {
|
|||||||
|
|
||||||
queue: CronTask[] = [];
|
queue: CronTask[] = [];
|
||||||
constructor(opts: any) {
|
constructor(opts: any) {
|
||||||
this.logger = opts.logger;
|
this.logger = opts.logger || logger;
|
||||||
this.immediateTriggerOnce = opts.immediateTriggerOnce;
|
this.immediateTriggerOnce = opts.immediateTriggerOnce;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -268,7 +268,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
|||||||
url,
|
url,
|
||||||
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
|
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
|
||||||
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
||||||
errorMessage: site.error
|
errorMessage: site.error,
|
||||||
|
notificationType: "siteCheckError",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
@@ -295,7 +296,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
|||||||
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
|
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
|
||||||
content,
|
content,
|
||||||
url,
|
url,
|
||||||
errorMessage: "站点证书即将过期"
|
errorMessage: "站点证书即将过期",
|
||||||
|
notificationType: "siteCertExpireRemind",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
@@ -311,7 +313,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
|||||||
title: `站点证书已过期${-validDays}天<${site.name}>`,
|
title: `站点证书已过期${-validDays}天<${site.name}>`,
|
||||||
content,
|
content,
|
||||||
url,
|
url,
|
||||||
errorMessage: "站点证书已过期"
|
errorMessage: "站点证书已过期",
|
||||||
|
notificationType: "siteCertExpireRemind",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
|
|||||||
@@ -62,4 +62,11 @@ export class AddonGetterService {
|
|||||||
return await this.getAddonById(id, true, userId);
|
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);
|
const pipeline = JSON.parse(item.content);
|
||||||
let stepCount = 0;
|
let stepCount = 0;
|
||||||
if(pipeline.stages){
|
if (pipeline.stages) {
|
||||||
RunnableCollection.each(pipeline.stages, (runnable: any) => {
|
RunnableCollection.each(pipeline.stages, (runnable: any) => {
|
||||||
stepCount++;
|
stepCount++;
|
||||||
});
|
});
|
||||||
@@ -237,7 +237,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
let fromType = "pipeline";
|
let fromType = "pipeline";
|
||||||
if (bean.type === "cert_upload") {
|
if (bean.type === "cert_upload") {
|
||||||
fromType = "upload";
|
fromType = "upload";
|
||||||
}else if (bean.type === "cert_auto") {
|
} else if (bean.type === "cert_auto") {
|
||||||
fromType = "auto";
|
fromType = "auto";
|
||||||
}
|
}
|
||||||
await this.certInfoService.updateDomains(pipeline.id, pipeline.userId || bean.userId, domains, fromType);
|
await this.certInfoService.updateDomains(pipeline.id, pipeline.userId || bean.userId, domains, fromType);
|
||||||
@@ -376,17 +376,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (immediateTriggerOnce) {
|
if (immediateTriggerOnce) {
|
||||||
try{
|
try {
|
||||||
await this.trigger(pipeline.id);
|
await this.trigger(pipeline.id);
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
}catch(e){
|
} catch (e) {
|
||||||
logger.error(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);
|
const entity: PipelineEntity = await this.info(id);
|
||||||
if (doCheck) {
|
if (doCheck) {
|
||||||
await this.beforeCheck(entity);
|
await this.beforeCheck(entity);
|
||||||
@@ -441,6 +441,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
pipeline = await this.info(id);
|
pipeline = await this.info(id);
|
||||||
} else {
|
} else {
|
||||||
pipeline = id;
|
pipeline = id;
|
||||||
|
id = pipeline.id;
|
||||||
}
|
}
|
||||||
if (!pipeline) {
|
if (!pipeline) {
|
||||||
return;
|
return;
|
||||||
@@ -456,6 +457,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
removeCron(pipelineId, trigger) {
|
removeCron(pipelineId, trigger) {
|
||||||
const name = this.buildCronKey(pipelineId, trigger.id);
|
const name = this.buildCronKey(pipelineId, trigger.id);
|
||||||
this.cron.remove(name);
|
this.cron.remove(name);
|
||||||
|
logger.info("当前定时器数量:", this.cron.getTaskSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCron(pipelineId, trigger) {
|
registerCron(pipelineId, trigger) {
|
||||||
@@ -500,8 +502,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
|
|
||||||
async isPipelineValidTimeEnabled(entity: PipelineEntity) {
|
async isPipelineValidTimeEnabled(entity: PipelineEntity) {
|
||||||
const settings = await this.sysSettingsService.getPublicSettings();
|
const settings = await this.sysSettingsService.getPublicSettings();
|
||||||
if (isPlus() && settings.pipelineValidTimeEnabled){
|
if (isPlus() && settings.pipelineValidTimeEnabled) {
|
||||||
if (entity.validTime > 0 && entity.validTime < Date.now()){
|
if (entity.validTime > 0 && entity.validTime < Date.now()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,7 +526,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
if (!validTimeEnabled) {
|
if (!validTimeEnabled) {
|
||||||
throw new Error(`流水线${entity.id}已过期,不予执行`);
|
throw new Error(`流水线${entity.id}已过期,不予执行`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let suite: UserSuiteEntity = null;
|
let suite: UserSuiteEntity = null;
|
||||||
if (isComm()) {
|
if (isComm()) {
|
||||||
suite = await this.checkHasDeployCount(entity.id, entity.userId);
|
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) {
|
async doRun(entity: PipelineEntity, triggerId: string, stepId?: string) {
|
||||||
|
|
||||||
let suite:any = null
|
let suite: any = null
|
||||||
try{
|
try {
|
||||||
const res = await this.beforeCheck(entity);
|
const res = await this.beforeCheck(entity);
|
||||||
suite = res.suite
|
suite = res.suite
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -803,11 +805,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
|
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
const pipeline = JSON.parse(item.content);
|
const pipeline = JSON.parse(item.content);
|
||||||
pipeline.triggers = [{
|
if (trigger.props === false) {
|
||||||
id: nanoid(),
|
//清除trigger
|
||||||
title: "定时触发",
|
pipeline.triggers = []
|
||||||
...trigger
|
} else {
|
||||||
}];
|
pipeline.triggers = [{
|
||||||
|
id: nanoid(),
|
||||||
|
title: "定时触发",
|
||||||
|
...trigger
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
await this.doUpdatePipelineJson(item, pipeline);
|
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 randomHour = Math.floor(Math.random() * 6);
|
||||||
const randomMin = Math.floor(Math.random() * 60);
|
const randomMin = Math.floor(Math.random() * 60);
|
||||||
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
||||||
|
|
||||||
let pipeline: any = {
|
let pipeline: any = {
|
||||||
title: req.domains[0] + `证书自动申请【${req.from??"OpenAPI"}】`,
|
title: req.domains[0] + `证书自动申请【${req.from ?? "OpenAPI"}】`,
|
||||||
runnableType: "pipeline",
|
runnableType: "pipeline",
|
||||||
triggers: [
|
triggers: [
|
||||||
{
|
{
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
title: "定时触发",
|
title: "定时触发",
|
||||||
props:{
|
props: {
|
||||||
cron: randomCron,
|
cron: randomCron,
|
||||||
},
|
},
|
||||||
type: "timer"
|
type: "timer"
|
||||||
|
|||||||
@@ -39,4 +39,5 @@ export * from './plugin-captcha/index.js'
|
|||||||
export * from './plugin-xinnet/index.js'
|
export * from './plugin-xinnet/index.js'
|
||||||
export * from './plugin-xinnetconnet/index.js'
|
export * from './plugin-xinnetconnet/index.js'
|
||||||
export * from './plugin-oauth/index.js'
|
export * from './plugin-oauth/index.js'
|
||||||
export * from './plugin-cmcc/index.js'
|
export * from './plugin-cmcc/index.js'
|
||||||
|
export * from './plugin-template/index.js'
|
||||||
@@ -132,6 +132,7 @@ nohup sh -c '$RESTART_CERT' >/dev/null 2>&1 & echo '10秒后重启' && exit
|
|||||||
title: `${this.repoName} 新版本 ${this.lastVersion} 发布`,
|
title: `${this.repoName} 新版本 ${this.lastVersion} 发布`,
|
||||||
content: `${body}\n\n > [Certd](https://certd.docmirror.cn),不止证书自动化,插件解锁无限可能!\n\n`,
|
content: `${body}\n\n > [Certd](https://certd.docmirror.cn),不止证书自动化,插件解锁无限可能!\n\n`,
|
||||||
url: `https://github.com/${this.repoName}/releases/tag/${this.lastVersion}`,
|
url: `https://github.com/${this.repoName}/releases/tag/${this.lastVersion}`,
|
||||||
|
notificationType: "githubReleaseCheck",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,26 @@ export class EmailNotification extends BaseNotification {
|
|||||||
receivers!: string[];
|
receivers!: string[];
|
||||||
|
|
||||||
async send(body: NotificationBody) {
|
async send(body: NotificationBody) {
|
||||||
await this.ctx.emailService.send({
|
|
||||||
subject: body.title,
|
const templateData = {
|
||||||
content: body.content + '\n\n[查看详情](' + body.url + ')',
|
...body,
|
||||||
|
}
|
||||||
|
const emailSend:any = {
|
||||||
receivers: this.receivers,
|
receivers: this.receivers,
|
||||||
});
|
}
|
||||||
|
if (body.attachments && body.attachments.length > 0) {
|
||||||
|
emailSend.attachments = body.attachments;
|
||||||
|
}
|
||||||
|
await this.ctx.emailService.sendByTemplate({
|
||||||
|
type: body.notificationType,
|
||||||
|
data: templateData,
|
||||||
|
email: emailSend
|
||||||
|
})
|
||||||
|
|
||||||
|
// await this.ctx.emailService.send({
|
||||||
|
// subject: body.title,
|
||||||
|
// content: body.content + '\n\n[查看详情](' + body.url + ')',
|
||||||
|
// receivers: this.receivers,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ export class TelegramNotification extends BaseNotification {
|
|||||||
|
|
||||||
replaceText(text: string) {
|
replaceText(text: string) {
|
||||||
// .*()<> 等都需要用\\进行替换
|
// .*()<> 等都需要用\\进行替换
|
||||||
return text.replace(/[\\.*()<>]/g, '\\$&');
|
return text.replace(/[_*[\]()~`>#\+\-=|{}.!]/g, '\\$&');
|
||||||
|
// .replace(/([\\_*`|!.[\](){}>+#=~-])/gm, '\\$1')
|
||||||
|
// return text.replace(/[\\.*()<>]/g, '\\$&');
|
||||||
}
|
}
|
||||||
async send(body: NotificationBody) {
|
async send(body: NotificationBody) {
|
||||||
if (!this.botToken || !this.chatId) {
|
if (!this.botToken || !this.chatId) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './api.js'
|
export * from './api.js'
|
||||||
export * from './oidc/plugin-oidc.js'
|
export * from './oidc/plugin-oidc.js'
|
||||||
export * from './wx/plugin-wx.js'
|
export * from './wx/plugin-wx.js'
|
||||||
export * from './oauth2/plugin-gitee.js'
|
export * from './oauth2/plugin-gitee.js'
|
||||||
|
export * from './oauth2/plugin-clogin.js'
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildLoginUrlReq, BuildLogoutUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "oauth",
|
||||||
|
name: 'clogin',
|
||||||
|
title: '彩虹聚合登录',
|
||||||
|
desc: '彩虹聚合登录',
|
||||||
|
icon: "emojione:rainbow",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class CloginOauthProvider extends BaseAddon implements IOauthProvider {
|
||||||
|
@AddonInput({
|
||||||
|
title: "系统地址",
|
||||||
|
helper: "http://clogin.xxxx.com/",
|
||||||
|
required: true,
|
||||||
|
col:{span:24},
|
||||||
|
})
|
||||||
|
endpoint = "";
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "登录类型",
|
||||||
|
component: {
|
||||||
|
name: "a-auto-complete",
|
||||||
|
options: [
|
||||||
|
{ label: "QQ", value: "qq" },
|
||||||
|
{ label: "微信", value: "wx" },
|
||||||
|
{ label: "支付宝", value: "alipay" },
|
||||||
|
{ label: "微博", value: "sina" },
|
||||||
|
{ label: "百度", value: "baidu" },
|
||||||
|
{ label: "华为", value: "huawei" },
|
||||||
|
{ label: "小米", value: "xiaomi" },
|
||||||
|
{ label: "谷歌", value: "google" },
|
||||||
|
{ label: "微软", value: "microsoft" },
|
||||||
|
{ label: "Facebook", value: "facebook" },
|
||||||
|
{ label: "Twitter", value: "twitter" },
|
||||||
|
{ label: "钉钉", value: "dingtalk" },
|
||||||
|
{ label: "Gitee", value: "gitee" },
|
||||||
|
{ label: "Github", value: "github" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
loginType = "";
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "自定义图标",
|
||||||
|
component: {
|
||||||
|
name:"fs-icon-selector",
|
||||||
|
vModel:"modelValue"
|
||||||
|
},
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
icon = "";
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "AppId",
|
||||||
|
helper: "彩虹聚合登录->应用列表->创建应用 获取",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
appId = "";
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "AppKey",
|
||||||
|
component: {
|
||||||
|
placeholder: "AppKey",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
appKey = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||||
|
|
||||||
|
let redirectUri = params.redirectUri || ""
|
||||||
|
if(redirectUri.indexOf("localhost:3008")>=0){
|
||||||
|
redirectUri = redirectUri.replace("localhost:3008", "certd.handfree.work")
|
||||||
|
}
|
||||||
|
const res = await this.ctx.http.request({
|
||||||
|
url: `${this.endpoint}/connect.php?act=login&appid=${this.appId}&appkey=${this.appKey}&type=${this.loginType}&redirect_uri=${redirectUri}`
|
||||||
|
})
|
||||||
|
|
||||||
|
this.checkRes(res)
|
||||||
|
|
||||||
|
return {
|
||||||
|
loginUrl: res.url,
|
||||||
|
ticketValue: {
|
||||||
|
state: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRes(res: any) {
|
||||||
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.msg || "请求接口失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onCallback(req: OnCallbackReq) {
|
||||||
|
|
||||||
|
//校验state
|
||||||
|
|
||||||
|
const code = req.code || ""
|
||||||
|
|
||||||
|
const tokenEndpoint = `http://clogin.yfy.docmirror.cn/connect.php?act=callback&appid=${this.appId}&appkey=${this.appKey}&type=${this.loginType}&code=${code}`
|
||||||
|
const res = await this.ctx.utils.http.request({
|
||||||
|
url: tokenEndpoint,
|
||||||
|
method: "post",
|
||||||
|
})
|
||||||
|
this.checkRes(res)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "access_token": "89DC9691E274D6B596FFCB8D43368234",
|
||||||
|
* "social_uid": "AD3F5033279C8187CBCBB29235D5F827",
|
||||||
|
"faceimg": "https://thirdqq.qlogo.cn/g?b=oidb&k=3WrWp3peBxlW4MFxDgDJEQ&s=100&t=1596856919",
|
||||||
|
"nickname": "大白",
|
||||||
|
"location": "XXXXX市",
|
||||||
|
"gender": "男",
|
||||||
|
"ip": "1.12.3.40"
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { access_token, faceimg, nickname, social_uid } = res
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: {
|
||||||
|
accessToken: access_token,
|
||||||
|
expiresIn: 7200,
|
||||||
|
refreshToken: "",
|
||||||
|
},
|
||||||
|
userInfo: {
|
||||||
|
openId: social_uid,
|
||||||
|
nickName: nickname || "",
|
||||||
|
avatar: faceimg || "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async buildLogoutUrl(params: BuildLogoutUrlReq) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,8 +127,9 @@ export class OidcOauthProvider extends BaseAddon implements IOauthProvider {
|
|||||||
},
|
},
|
||||||
userInfo: {
|
userInfo: {
|
||||||
openId: claims.sub,
|
openId: claims.sub,
|
||||||
nickName: claims.nickname || claims.preferred_username || "",
|
nickName: claims.nickname || claims.name || claims.username || claims.preferred_username || "",
|
||||||
avatar: claims.picture,
|
avatar: claims.picture,
|
||||||
|
email: claims.email || "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
export type BuildContentReq = {
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ITemplateProvider<T=any> {
|
||||||
|
buildContent: (params: BuildContentReq) => Promise<T>;
|
||||||
|
|
||||||
|
buildDefaultContent:(params: BuildContentReq) => Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type EmailContent = {
|
||||||
|
subject:string,
|
||||||
|
content?:string,
|
||||||
|
html?:string
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './plugin-common.js'
|
||||||
|
export * from './plugin-register-code.js'
|
||||||
|
export * from './plugin-forgot-password.js'
|
||||||
|
export * from './plugin-pipeline-result.js'
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { AddonInput, BaseAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
|
||||||
|
export class BaseEmailTemplateProvider extends BaseAddon implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "配置说明",
|
||||||
|
component: {
|
||||||
|
name: "a-alert",
|
||||||
|
props: {
|
||||||
|
type: "info",
|
||||||
|
message: "在标题和内容模版中,通过${name}引用参数,例如: 感谢注册,您的注册验证码为:${code}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order: -9,
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
useIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "邮件格式",
|
||||||
|
component: {
|
||||||
|
name: "a-select",
|
||||||
|
props: {
|
||||||
|
options: [
|
||||||
|
{ label: "HTML", value: "html" },
|
||||||
|
{ label: "TEXT", value: "text" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order: 9,
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
formatType = "";
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "邮件标题模版",
|
||||||
|
required: true,
|
||||||
|
order: 10,
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
props: {
|
||||||
|
placeholder: "邮件标题模版",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
titleTemplate = "";
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "邮件内容模版",
|
||||||
|
component: {
|
||||||
|
name: "a-textarea",
|
||||||
|
rows: 6,
|
||||||
|
},
|
||||||
|
order: 20,
|
||||||
|
col: { span: 24 },
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
contentTemplate = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildContent(params: BuildContentReq): Promise<EmailContent> {
|
||||||
|
const data = {
|
||||||
|
title:"",
|
||||||
|
content:"",
|
||||||
|
url:"",
|
||||||
|
...params.data,
|
||||||
|
}
|
||||||
|
const title = this.compile(this.titleTemplate)(data)
|
||||||
|
const content = this.compile(this.contentTemplate)(data)
|
||||||
|
|
||||||
|
const body: any = {
|
||||||
|
subject: title,
|
||||||
|
}
|
||||||
|
if (this.formatType === "html") {
|
||||||
|
body.html = content
|
||||||
|
} else {
|
||||||
|
body.content = content
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
};
|
||||||
|
|
||||||
|
async buildDefaultContent(params: BuildContentReq): Promise<EmailContent> {
|
||||||
|
throw new Error("请实现 buildDefaultContent 方法")
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile(templateString: string) {
|
||||||
|
// return function (data: any): string {
|
||||||
|
// return templateString.replace(/\${(.*?)}/g, (match, key) => {
|
||||||
|
// const value = get(data, key?.trim(), '');
|
||||||
|
// return String(value);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
compile(templateString:string) {
|
||||||
|
return new Function('data', ` with(data || {}) {
|
||||||
|
return \`${templateString}\`;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'common',
|
||||||
|
title: '通用邮件模版',
|
||||||
|
desc: '使用通用的邮件标题和内容,内容外部可以自定义html进行美化',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class CommonEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{label:"标题",value:"title"},
|
||||||
|
{label:"内容",value:"content"},
|
||||||
|
{label:"URL",value:"url"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new CommonEmailTemplateProvider()
|
||||||
|
defaultTemplate.titleTemplate = "${title}"
|
||||||
|
defaultTemplate.contentTemplate = "${content} \n\n 查看详情:${url}"
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'forgotPassword',
|
||||||
|
title: '忘记密码邮件模版',
|
||||||
|
desc: '您正在重置密码,您的验证码为xxxx,请勿泄露',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class ForgotPasswordEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{label:"验证码",value:"code"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new ForgotPasswordEmailTemplateProvider()
|
||||||
|
defaultTemplate.titleTemplate = "忘记密码"
|
||||||
|
defaultTemplate.contentTemplate = "您的验证码是:${code},请勿泄露"
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'pipelineResult',
|
||||||
|
title: '流水线执行结果邮件模版',
|
||||||
|
desc: '执行失败,xxxx自动化【流水线id】;运行ID:xxx,错误信息:xxxx',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class PipelineResultEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{label:"运行结果",value:"pipelineResult"},
|
||||||
|
{label:"流水线标题",value:"pipelineTitle"},
|
||||||
|
{label:"流水线ID",value:"pipelineId"},
|
||||||
|
{label:"运行Id",value:"historyId"},
|
||||||
|
{label:"错误信息",value:"errors"},
|
||||||
|
{label:"URL",value:"url"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new PipelineResultEmailTemplateProvider()
|
||||||
|
|
||||||
|
const subject = "${result},${pipelineTitle}【${pipelineId}】";
|
||||||
|
const content = "流水线ID:${pipelineId},运行ID:${runtimeId} \n\n ${errors}";
|
||||||
|
defaultTemplate.titleTemplate = subject
|
||||||
|
defaultTemplate.contentTemplate = content
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'registerCode',
|
||||||
|
title: '注册验证码邮件模版',
|
||||||
|
desc: '您的注册验证码为:xxxx,请勿泄露',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class RegisterCodeEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{label:"验证码",value:"code"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new RegisterCodeEmailTemplateProvider()
|
||||||
|
defaultTemplate.titleTemplate = "注册验证码"
|
||||||
|
defaultTemplate.contentTemplate = "您的注册验证码是:${code},请勿泄露"
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./email/index.js"
|
||||||
@@ -158,6 +158,9 @@ export class WestAccess extends BaseAccess {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.msg !== 'success') {
|
if (res.msg !== 'success') {
|
||||||
|
if(res.msg.includes('500 already exists')){
|
||||||
|
return res;
|
||||||
|
}
|
||||||
throw new Error(`${JSON.stringify(res.msg)}`);
|
throw new Error(`${JSON.stringify(res.msg)}`);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@@ -253,6 +256,9 @@ token=md5(zhangsan + 5dh232kfg!* + 1554691950854)=cfcd208495d565ef66e7dff9f98764
|
|||||||
});
|
});
|
||||||
this.ctx.logger.info(`request ${url} ${method} res:${JSON.stringify(res)}`);
|
this.ctx.logger.info(`request ${url} ${method} res:${JSON.stringify(res)}`);
|
||||||
if (res.msg !== 'success' && res.result!= 200) {
|
if (res.msg !== 'success' && res.result!= 200) {
|
||||||
|
if(res.msg.includes('500 already exists')){
|
||||||
|
return res;
|
||||||
|
}
|
||||||
throw new Error(`${JSON.stringify(res.msg)}`);
|
throw new Error(`${JSON.stringify(res.msg)}`);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||