Compare commits

...

50 Commits

Author SHA1 Message Date
xiaojunnuo
085bdf5cfa v1.36.10 2025-07-18 23:08:03 +08:00
xiaojunnuo
6883bcacee build: prepare to build 2025-07-18 23:02:57 +08:00
xiaojunnuo
2ecc6e0368 build: prepare to build 2025-07-18 23:00:39 +08:00
xiaojunnuo
8fb5ca2fe1 chore: ip检查新旧ip变化对比逻辑修复 2025-07-18 18:24:01 +08:00
xiaojunnuo
e40345095f perf: 账号即将过期通知 2025-07-18 18:18:01 +08:00
xiaojunnuo
ffc0c7bb7b perf: 子域名托管重复域名不允许添加 2025-07-18 16:36:56 +08:00
xiaojunnuo
58fadc8928 chore: 2025-07-18 16:32:19 +08:00
xiaojunnuo
d96a607c04 chore: 2025-07-18 16:00:34 +08:00
xiaojunnuo
2ea2c8c05f perf: 部署到阿里云oss插件支持选择上传到阿里云cas中的证书 2025-07-18 15:35:35 +08:00
xiaojunnuo
b15f514018 perf: 优化子域名托管的说明 2025-07-18 14:47:15 +08:00
xiaojunnuo
05a33a0ec9 fix: api接口获取不到证书的bug 2025-07-18 14:46:58 +08:00
xiaojunnuo
747d266742 fix: 企业微信通知改成text类型,因为markdown类型不支持@用户 2025-07-15 17:15:43 +08:00
xiaojunnuo
6135a44a8d build: publish 2025-07-15 16:54:52 +08:00
xiaojunnuo
7c7d646792 build: trigger build image 2025-07-15 16:54:35 +08:00
xiaojunnuo
4a36fd2ec3 v1.36.9 2025-07-15 16:53:06 +08:00
xiaojunnuo
b1bcc287cb build: prepare to build 2025-07-15 16:46:42 +08:00
xiaojunnuo
6f5868a9d7 build: prepare to build 2025-07-15 16:43:58 +08:00
xiaojunnuo
75863441f4 build: prepare to build 2025-07-15 16:41:37 +08:00
xiaojunnuo
9763cb00e5 fix: 修复ssh无法执行命令的bug 2025-07-15 16:41:15 +08:00
xiaojunnuo
1921a64f4b build: publish 2025-07-15 15:36:31 +08:00
xiaojunnuo
6b73f5d555 build: trigger build image 2025-07-15 15:36:14 +08:00
xiaojunnuo
e0408f30ba v1.36.7 2025-07-15 15:33:36 +08:00
xiaojunnuo
dca44fa093 build: prepare to build 2025-07-15 15:21:52 +08:00
xiaojunnuo
bbacb76581 build: prepare to build 2025-07-15 15:19:18 +08:00
xiaojunnuo
1da8617a53 perf: 支持上传证书到各种对象存储,oss、cos、七牛、s3、minio等 2025-07-15 15:18:35 +08:00
xiaojunnuo
e5967f7e9d chore: 2025-07-15 15:17:11 +08:00
xiaojunnuo
65d84f9e9d chore: 2025-07-15 15:06:59 +08:00
xiaojunnuo
93e9498b41 fix: 修复流水线页面状态没有刷新的bug 2025-07-15 15:05:09 +08:00
xiaojunnuo
95332d5db9 perf: 支持邮箱发送证书 2025-07-15 13:58:01 +08:00
xiaojunnuo
9864792bbf fix: 修复流水线列表页报length错误的bug 2025-07-15 10:53:11 +08:00
xiaojunnuo
ca9d1eed7a Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-07-15 10:45:34 +08:00
xiaojunnuo
38e867c917 fix: 修复自定义证书检查时间重启之后不生效的bug 2025-07-15 10:42:56 +08:00
xiaojunnuo
3ee1dbb8a5 build: publish 2025-07-14 23:54:12 +08:00
xiaojunnuo
b4571d5c98 build: trigger build image 2025-07-14 23:53:51 +08:00
xiaojunnuo
29d49d72f9 v1.36.6 2025-07-14 23:52:25 +08:00
xiaojunnuo
81de0fc7e4 build: prepare to build 2025-07-14 23:48:40 +08:00
xiaojunnuo
9d5d266d2a build: prepare to build 2025-07-14 23:46:40 +08:00
xiaojunnuo
b97935299f chore: auto功能 数据库升级脚本 2025-07-14 23:46:24 +08:00
xiaojunnuo
32a7ea1c16 chore: 2025-07-14 23:29:35 +08:00
xiaojunnuo
9fd95e6a1e chore: 2025-07-14 23:26:54 +08:00
xiaojunnuo
61ba83c775 perf: 通知和定时器的删除按钮显示为红色更显眼 2025-07-14 23:25:56 +08:00
xiaojunnuo
6369fed5fc chore: 2025-07-14 23:15:22 +08:00
xiaojunnuo
42f4d1477d perf: OpenAPI支持autoApply参数 2025-07-14 23:02:47 +08:00
xiaojunnuo
609ac9c9a2 perf: 优化流水线列表页面、详情页面性能,精简返回数据 2025-07-14 01:36:40 +08:00
xiaojunnuo
79f2367472 chore: 2025-07-14 00:34:47 +08:00
xiaojunnuo
dfc9362084 fix: 修复运行流水线后会闪烁一下的bug 2025-07-14 00:33:42 +08:00
xiaojunnuo
487b469603 chore: 增强cname安全性 2025-07-14 00:15:46 +08:00
xiaojunnuo
19e1df1e5d chore: 2025-07-13 23:58:07 +08:00
xiaojunnuo
fc55010888 Merge branch 'v2-dev-auto' into v2-dev 2025-07-13 23:23:47 +08:00
xiaojunnuo
902d246d1a perf: 部署plesk证书,支持删除未使用的证书 2025-07-13 17:10:15 +08:00
79 changed files with 1797 additions and 511 deletions

View File

@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
### Bug Fixes
* 企业微信通知改成text类型因为markdown类型不支持@用户 ([747d266](https://github.com/certd/certd/commit/747d26674248082e678a3fd5ecc94712641a2716))
* api接口获取不到证书的bug ([05a33a0](https://github.com/certd/certd/commit/05a33a0ec9999e2802f6c7b23cc1c61a2b9e963d))
### Performance Improvements
* 部署到阿里云oss插件支持选择上传到阿里云cas中的证书 ([2ea2c8c](https://github.com/certd/certd/commit/2ea2c8c05fc40f79595f1bbde67c1413558bf684))
* 优化子域名托管的说明 ([b15f514](https://github.com/certd/certd/commit/b15f514018b728acb0922ee3f93c1f302eb5d471))
* 账号即将过期通知 ([e403450](https://github.com/certd/certd/commit/e40345095f31e2fb8e2333a6647466659133fa0c))
* 子域名托管重复域名不允许添加 ([ffc0c7b](https://github.com/certd/certd/commit/ffc0c7bb7b16d9904fd2d905d1c4e1d4854e92a9))
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
### Bug Fixes
* 修复ssh无法执行命令的bug ([9763cb0](https://github.com/certd/certd/commit/9763cb00e5d95b2fa5d1c2d3d4a8eecac71600e6))
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
### Bug Fixes
* 修复流水线列表页报length错误的bug ([9864792](https://github.com/certd/certd/commit/9864792bbfd149e770d6e1ffa809573694f99dd3))
* 修复流水线页面状态没有刷新的bug ([93e9498](https://github.com/certd/certd/commit/93e9498b410353f504e11e264db62468895d7290))
* 修复自定义证书检查时间重启之后不生效的bug ([38e867c](https://github.com/certd/certd/commit/38e867c917bbc68bd228bdd8064f3e7358d6413d))
### Performance Improvements
* 支持上传证书到各种对象存储oss、cos、七牛、s3、minio等 ([1da8617](https://github.com/certd/certd/commit/1da8617a53a675776635bbc3bcb3c6d7dff83e27))
* 支持邮箱发送证书 ([95332d5](https://github.com/certd/certd/commit/95332d5db96cd54ddab6ab737332417a09169b39))
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
### Bug Fixes
* 修复某些页面翻译不全显示错误的bug ([0b3158f](https://github.com/certd/certd/commit/0b3158fdd5fe5bb0a98c4e65715dbc3de2c38047))
* 修复运行流水线后会闪烁一下的bug ([dfc9362](https://github.com/certd/certd/commit/dfc9362084082ee535b898f23b2609c1d946a6fd))
### Performance Improvements
* 部署plesk证书支持删除未使用的证书 ([902d246](https://github.com/certd/certd/commit/902d246d1a7473ad90f604028c4eb09c8c67d99c))
* 通知和定时器的删除按钮显示为红色更显眼 ([61ba83c](https://github.com/certd/certd/commit/61ba83c77546c3d505d081e19a3d68c127662bf1))
* 优化流水线列表页面、详情页面性能,精简返回数据 ([609ac9c](https://github.com/certd/certd/commit/609ac9c9a2dde605eb09834ae59693c1cb238765))
* 支持自动选择校验方式申请证书 ([3f99432](https://github.com/certd/certd/commit/3f9943270cfb12946e38e6272bc5e8d95ad6ab9e))
* OpenAPI支持autoApply参数 ([42f4d14](https://github.com/certd/certd/commit/42f4d1477dc791520a874aed56035abcbc8c433b))
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
### Bug Fixes

View File

@@ -1 +1 @@
10:51
16:54

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
### Bug Fixes
* 修复ssh无法执行命令的bug ([9763cb0](https://github.com/certd/certd/commit/9763cb00e5d95b2fa5d1c2d3d4a8eecac71600e6))
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
### Bug Fixes
* 修复流水线列表页报length错误的bug ([9864792](https://github.com/certd/certd/commit/9864792bbfd149e770d6e1ffa809573694f99dd3))
* 修复流水线页面状态没有刷新的bug ([93e9498](https://github.com/certd/certd/commit/93e9498b410353f504e11e264db62468895d7290))
* 修复自定义证书检查时间重启之后不生效的bug ([38e867c](https://github.com/certd/certd/commit/38e867c917bbc68bd228bdd8064f3e7358d6413d))
### Performance Improvements
* 支持上传证书到各种对象存储oss、cos、七牛、s3、minio等 ([1da8617](https://github.com/certd/certd/commit/1da8617a53a675776635bbc3bcb3c6d7dff83e27))
* 支持邮箱发送证书 ([95332d5](https://github.com/certd/certd/commit/95332d5db96cd54ddab6ab737332417a09169b39))
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
### Bug Fixes
* 修复某些页面翻译不全显示错误的bug ([0b3158f](https://github.com/certd/certd/commit/0b3158fdd5fe5bb0a98c4e65715dbc3de2c38047))
* 修复运行流水线后会闪烁一下的bug ([dfc9362](https://github.com/certd/certd/commit/dfc9362084082ee535b898f23b2609c1d946a6fd))
### Performance Improvements
* 部署plesk证书支持删除未使用的证书 ([902d246](https://github.com/certd/certd/commit/902d246d1a7473ad90f604028c4eb09c8c67d99c))
* 通知和定时器的删除按钮显示为红色更显眼 ([61ba83c](https://github.com/certd/certd/commit/61ba83c77546c3d505d081e19a3d68c127662bf1))
* 优化流水线列表页面、详情页面性能,精简返回数据 ([609ac9c](https://github.com/certd/certd/commit/609ac9c9a2dde605eb09834ae59693c1cb238765))
* 支持自动选择校验方式申请证书 ([3f99432](https://github.com/certd/certd/commit/3f9943270cfb12946e38e6272bc5e8d95ad6ab9e))
* OpenAPI支持autoApply参数 ([42f4d14](https://github.com/certd/certd/commit/42f4d1477dc791520a874aed56035abcbc8c433b))
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
### Bug Fixes

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.36.5"
"version": "1.36.10"
}

View File

@@ -36,6 +36,7 @@
},
"license": "AGPL-3.0",
"dependencies": {
"@certd/ui-server": "link:packages/ui/certd-server",
"axios": "^1.7.7",
"copyfiles": "^2.4.1",
"lodash-es": "^4.17.21",

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/publishlab/node-acme-client/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/acme-client
## [1.36.9](https://github.com/publishlab/node-acme-client/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/acme-client
## [1.36.7](https://github.com/publishlab/node-acme-client/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/acme-client
## [1.36.6](https://github.com/publishlab/node-acme-client/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/acme-client
## [1.36.5](https://github.com/publishlab/node-acme-client/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.36.5",
"version": "1.36.10",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.36.5",
"@certd/basic": "^1.36.10",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
@@ -69,5 +69,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/basic
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/basic
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/basic
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/basic
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
10:46
23:02

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.36.5",
"version": "1.36.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/pipeline
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/pipeline
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
### Performance Improvements
* 支持邮箱发送证书 ([95332d5](https://github.com/certd/certd/commit/95332d5db96cd54ddab6ab737332417a09169b39))
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/pipeline
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.36.5",
"version": "1.36.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,8 +17,8 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.5",
"@certd/plus-core": "^1.36.5",
"@certd/basic": "^1.36.10",
"@certd/plus-core": "^1.36.10",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -44,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -1,7 +1,9 @@
export type EmailSend = {
subject: string;
content: string;
receivers: string[];
content?: string;
attachments?: any[];
html?: string;
};
export interface IEmailService {

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.36.5",
"version": "1.36.10",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -24,5 +24,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.36.5",
"version": "1.36.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.36.5",
"version": "1.36.10",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -61,5 +61,5 @@
"fetch"
]
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/lib-k8s
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/lib-k8s
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/lib-k8s
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/lib-k8s
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.36.5",
"version": "1.36.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,7 +17,7 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.5",
"@certd/basic": "^1.36.10",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -32,5 +32,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/lib-server
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/lib-server
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/lib-server
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
### Performance Improvements
* 优化流水线列表页面、详情页面性能,精简返回数据 ([609ac9c](https://github.com/certd/certd/commit/609ac9c9a2dde605eb09834ae59693c1cb238765))
* OpenAPI支持autoApply参数 ([42f4d14](https://github.com/certd/certd/commit/42f4d1477dc791520a874aed56035abcbc8c433b))
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.36.5",
"version": "1.36.10",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -27,10 +27,10 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.36.5",
"@certd/basic": "^1.36.5",
"@certd/pipeline": "^1.36.5",
"@certd/plus-core": "^1.36.5",
"@certd/acme-client": "^1.36.10",
"@certd/basic": "^1.36.10",
"@certd/pipeline": "^1.36.10",
"@certd/plus-core": "^1.36.10",
"@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.20.3",
"@midwayjs/i18n": "~3.20.3",
@@ -61,5 +61,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -164,8 +164,11 @@ export abstract class BaseService<T> {
}
private buildListQuery(listReq: ListReq<T>) {
const { query, sort, buildQuery } = listReq;
const { query, sort, buildQuery,select } = listReq;
const qb = this.getRepository().createQueryBuilder('main');
if (select) {
qb.setFindOptions({select});
}
if (query) {
const keys = Object.keys(query);
for (const key of keys) {
@@ -191,6 +194,7 @@ export abstract class BaseService<T> {
if (buildQuery) {
buildQuery(qb);
}
return qb;
}

View File

@@ -107,5 +107,17 @@ export const Constants = {
code: 20012,
message: '证书还未生成',
},
openCertApplying: {
code: 20013,
message: '证书正在申请中,请稍后重新获取',
},
openDomainNoVerifier:{
code: 20014,
message: '域名校验方式未配置',
},
openEmailNotFound: {
code: 20021,
message: '用户邮箱还未配置',
},
},
};

View File

@@ -11,13 +11,13 @@ export class CommonException extends BaseException {
}
export class CodeException extends BaseException {
constructor(res: { code: number; message: string }) {
super("CodeException", res.code, res.message);
constructor(res: { code: number; message: string; data?: any }) {
super("CodeException", res.code, res.message, res.data);
}
}
export class TextException extends BaseException {
constructor(name, code,message, data?) {
constructor(name, code, message, data?) {
super(name, code, message, data);
}
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.36.5",
"version": "1.36.10",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -3,6 +3,27 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/plugin-cert
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/plugin-cert
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
### Performance Improvements
* 支持邮箱发送证书 ([95332d5](https://github.com/certd/certd/commit/95332d5db96cd54ddab6ab737332417a09169b39))
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
### Performance Improvements
* 支持自动选择校验方式申请证书 ([3f99432](https://github.com/certd/certd/commit/3f9943270cfb12946e38e6272bc5e8d95ad6ab9e))
* OpenAPI支持autoApply参数 ([42f4d14](https://github.com/certd/certd/commit/42f4d1477dc791520a874aed56035abcbc8c433b))
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.36.5",
"version": "1.36.10",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -16,10 +16,10 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/acme-client": "^1.36.5",
"@certd/basic": "^1.36.5",
"@certd/pipeline": "^1.36.5",
"@certd/plugin-lib": "^1.36.5",
"@certd/acme-client": "^1.36.10",
"@certd/basic": "^1.36.10",
"@certd/pipeline": "^1.36.10",
"@certd/plugin-lib": "^1.36.10",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -43,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -1,4 +1,4 @@
import { AbstractTaskPlugin, IContext, Step, TaskInput, TaskOutput } from "@certd/pipeline";
import { AbstractTaskPlugin, FileItem, IContext, Step, TaskInput, TaskOutput } from "@certd/pipeline";
import dayjs from "dayjs";
import type { CertInfo } from "./acme.js";
import { CertReader } from "./cert-reader.js";
@@ -71,6 +71,12 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
})
cert?: CertInfo;
@TaskOutput({
title: "域名证书压缩文件",
type: "certZip",
})
certZip?: FileItem;
async onInstance() {
this.userContext = this.ctx.userContext;
this.lastStatus = this.ctx.lastStatus as Step;
@@ -131,6 +137,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
} else {
this.extendsFiles();
}
this.certZip = this._result.files[0];
}
async zipCert(cert: CertInfo, filename: string) {

View File

@@ -470,6 +470,9 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
const domain = fullDomain.replaceAll("*.", "");
const mainDomain = await domainParser.parse(domain);
const planSetting: DomainVerifyPlanInput = verifyPlanSetting[mainDomain];
if (planSetting == null) {
throw new Error(`没有找到域名(${domain})的校验计划`);
}
if (planSetting.type === "dns") {
plan[domain] = await this.createDnsDomainVerifyPlan(planSetting, domain, mainDomain);
} else if (planSetting.type === "cname") {
@@ -498,6 +501,9 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
for (const domain in verifiers) {
const verifier = verifiers[domain];
if (verifier == null) {
throw new Error(`没有找到与该域名(${domain})匹配的校验方式,请先到‘域名管理’页面添加校验方式`);
}
if (verifier.type === "dns") {
plan[domain] = await this.createDnsDomainVerifyPlan(verifier.dns, domain, verifier.mainDomain);
} else if (verifier.type === "cname") {

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
**Note:** Version bump only for package @certd/plugin-lib
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
### Bug Fixes
* 修复ssh无法执行命令的bug ([9763cb0](https://github.com/certd/certd/commit/9763cb00e5d95b2fa5d1c2d3d4a8eecac71600e6))
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
### Bug Fixes
* 修复流水线页面状态没有刷新的bug ([93e9498](https://github.com/certd/certd/commit/93e9498b410353f504e11e264db62468895d7290))
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
**Note:** Version bump only for package @certd/plugin-lib
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/plugin-lib

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.36.5",
"version": "1.36.10",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -21,8 +21,8 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-s3": "^3.787.0",
"@certd/basic": "^1.36.5",
"@certd/pipeline": "^1.36.5",
"@certd/basic": "^1.36.10",
"@certd/pipeline": "^1.36.10",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -53,5 +53,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
"gitHead": "4a36fd2ec343794c5442e4402b21fa6869dfd90e"
}

View File

@@ -16,6 +16,10 @@ export default class SshOssClientImpl extends BaseOssClient<SshAccess> {
throw new Error("Method not implemented.");
}
async upload(filePath: string, fileContent: Buffer) {
if (!filePath) {
filePath = "";
}
filePath = filePath.trim();
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
// Write file to temp path

View File

@@ -506,10 +506,6 @@ export class SshClient {
isWinCmd = await this.isCmd(conn);
}
if (isLinux && options.stopOnError !== false) {
script = "set -e\n" + script;
}
if (options.env) {
for (const key in options.env) {
if (isLinux) {
@@ -525,10 +521,10 @@ export class SshClient {
}
if (isWinCmd) {
//组合成&&的形式
if (typeof script === "string") {
script = script.split("\n");
}
//组合成&&的形式
script = envScripts.concat(script);
script = script as Array<string>;
script = script.join(" && ");
@@ -543,6 +539,10 @@ export class SshClient {
}
}
if (isLinux && options.stopOnError !== false) {
script = "set -e\n" + script;
}
return await conn.exec(script as string, { throwOnStdErr });
},
});

View File

@@ -3,6 +3,37 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
### Performance Improvements
* 优化子域名托管的说明 ([b15f514](https://github.com/certd/certd/commit/b15f514018b728acb0922ee3f93c1f302eb5d471))
* 账号即将过期通知 ([e403450](https://github.com/certd/certd/commit/e40345095f31e2fb8e2333a6647466659133fa0c))
* 子域名托管重复域名不允许添加 ([ffc0c7b](https://github.com/certd/certd/commit/ffc0c7bb7b16d9904fd2d905d1c4e1d4854e92a9))
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/ui-client
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
### Bug Fixes
* 修复流水线页面状态没有刷新的bug ([93e9498](https://github.com/certd/certd/commit/93e9498b410353f504e11e264db62468895d7290))
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
### Bug Fixes
* 修复某些页面翻译不全显示错误的bug ([0b3158f](https://github.com/certd/certd/commit/0b3158fdd5fe5bb0a98c4e65715dbc3de2c38047))
* 修复运行流水线后会闪烁一下的bug ([dfc9362](https://github.com/certd/certd/commit/dfc9362084082ee535b898f23b2609c1d946a6fd))
### Performance Improvements
* 通知和定时器的删除按钮显示为红色更显眼 ([61ba83c](https://github.com/certd/certd/commit/61ba83c77546c3d505d081e19a3d68c127662bf1))
* 优化流水线列表页面、详情页面性能,精简返回数据 ([609ac9c](https://github.com/certd/certd/commit/609ac9c9a2dde605eb09834ae59693c1cb238765))
* OpenAPI支持autoApply参数 ([42f4d14](https://github.com/certd/certd/commit/42f4d1477dc791520a874aed56035abcbc8c433b))
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
**Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.36.5",
"version": "1.36.10",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -103,8 +103,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.36.5",
"@certd/pipeline": "^1.36.5",
"@certd/lib-iframe": "^1.36.10",
"@certd/pipeline": "^1.36.10",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -449,7 +449,7 @@ export default {
batchDeleteConfirm: "Are you sure to batch delete these {count} records?",
selectRecordFirst: "Please select records first",
subdomainHosted: "Hosted Subdomain",
subdomainHelpText: "If you don't understand what subdomain hosting is, please refer to the documentation ",
subdomainHelpText: "If you don't understand what subdomain hosting is,Do not set it randomly, as it may result in the inability to apply for the certificate. please refer to the documentation ",
subdomainManagement: "Subdomain Management",
isDisabled: "Is Disabled",
enabled: "Enabled",

View File

@@ -415,7 +415,7 @@ export default {
is_present_no: "否",
basicInfo: "基础信息",
titlea: "名称",
disabled: "是否禁用",
disabled: "禁用",
ordera: "排序",
supportBuy: "支持购买",
intro: "介绍",
@@ -455,7 +455,7 @@ export default {
batchDeleteConfirm: "确定要批量删除这{count}条记录吗",
selectRecordFirst: "请先勾选记录",
subdomainHosted: "托管的子域名",
subdomainHelpText: "如果您不理解什么是子域托管,可以参考文档",
subdomainHelpText: "如果您不理解什么是子域托管,请不要随意设置,可能导致证书无法申请,可以参考文档",
subdomainManagement: "子域管理",
isDisabled: "是否禁用",
enabled: "启用",

View File

@@ -46,6 +46,20 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const { openSiteIpMonitorDialog } = useSiteIpMonitor();
const { openSiteImportDialog } = useSiteImport();
function checkAll() {
Modal.confirm({
title: t("certd.monitor.confirmTitle"), // "确认"
content: t("certd.monitor.confirmContent"), // "确认触发检查全部站点证书吗?"
onOk: async () => {
await siteInfoApi.CheckAll();
notification.success({
message: t("certd.monitor.checkSubmitted"), // "检查任务已提交"
description: t("certd.monitor.pleaseRefresh"), // "请稍后刷新页面查看结果"
});
},
});
}
return {
id: "siteMonitorCrud",
crudOptions: {
@@ -114,6 +128,14 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
});
},
},
checkAll: {
show: true,
text: t("certd.monitor.checkAll"),
type: "primary",
click() {
checkAll();
},
},
},
},
rowHandle: {

View File

@@ -14,9 +14,6 @@
</div>
</div>
</div>
<div class="more">
<a-button type="primary" @click="checkAll">{{ t("certd.monitor.checkAll") }}</a-button>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
@@ -35,19 +32,6 @@ defineOptions({
name: "SiteCertMonitor",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
function checkAll() {
Modal.confirm({
title: t("certd.monitor.confirmTitle"), // "确认"
content: t("certd.monitor.confirmContent"), // "确认触发检查全部站点证书吗?"
onOk: async () => {
await siteInfoApi.CheckAll();
notification.success({
message: t("certd.monitor.checkSubmitted"), // "检查任务已提交"
description: t("certd.monitor.pleaseRefresh"), // "请稍后刷新页面查看结果"
});
},
});
}
// 页面打开后获取列表数据
onMounted(() => {

View File

@@ -125,6 +125,7 @@ export function useCertPipelineCreator() {
const pluginStore = usePluginStore();
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
const groupDictRef = dict({
url: "/pi/pipeline/group/all",
@@ -193,7 +194,7 @@ export function useCertPipelineCreator() {
title: t("certd.pipelineForm.triggerCronTitle"),
type: "text",
form: {
value: `0 ${randomMin} ${randomHour} * * *`,
value: randomCron,
component: {
name: "cron-editor",
vModel: "modelValue",

View File

@@ -335,7 +335,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
}
},
},
_triggerCount: {
triggerCount: {
title: t("certd.fields.scheduledTaskCount"),
type: "number",
column: {
@@ -346,7 +346,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
show: false,
},
},
_stepCount: {
stepCount: {
title: t("certd.fields.deployTaskCount"),
type: "number",
form: { show: false },

View File

@@ -4,7 +4,7 @@
<fs-icon v-bind="status" :color="status.iconColor || status.color" />
</template>
<p>
<fs-date-format :model-value="runnable.status?.startTime"></fs-date-format>
<fs-date-format :model-value="runnable.createTime"></fs-date-format>
<a-tag class="ml-5" :color="status.color" :closable="status.value === 'start'" @close="cancelTask">
{{ status.label }}
</a-tag>
@@ -46,7 +46,7 @@ export default defineComponent({
emits: ["view", "cancel"],
setup(props: any, ctx: any) {
const status = computed(() => {
return statusUtil.get(props.runnable?.status?.result);
return statusUtil.get(props.runnable?.status);
});
function view() {

View File

@@ -3,7 +3,7 @@
<template #title>
<div>
{{ t("certd.edit_notification") }}
<a-button v-if="mode === 'edit'" @click="notificationDelete()">
<a-button v-if="mode === 'edit'" danger @click="notificationDelete()">
<template #icon>
<DeleteOutlined />
</template>

View File

@@ -3,7 +3,7 @@
<template #title>
<div>
编辑任务
<a-button v-if="editMode" @click="taskDelete()">
<a-button v-if="editMode" danger @click="taskDelete()">
<template #icon><DeleteOutlined /></template>
</a-button>
</div>

View File

@@ -3,7 +3,7 @@
<template #title>
<div>
{{ t("certd.editTrigger") }}
<a-button v-if="mode === 'edit'" @click="triggerDelete()">
<a-button v-if="mode === 'edit'" danger @click="triggerDelete()">
<template #icon>
<DeleteOutlined />
</template>

View File

@@ -258,7 +258,7 @@
<a-timeline class="mt-10">
<template v-for="item of histories" :key="item.id">
<pi-history-timeline-item
:runnable="item.pipeline"
:runnable="item"
:history-id="item.id"
:is-current="currentHistory?.id === item.id"
:edit-mode="editMode"
@@ -280,7 +280,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, provide, Ref, ref, watch, computed } from "vue";
import { computed, defineComponent, onMounted, onUnmounted, provide, ref, Ref, watch } from "vue";
import { useRouter } from "vue-router";
import PiTaskForm from "./component/task-form/index.vue";
import PiTriggerForm from "./component/trigger-form/index.vue";
@@ -288,7 +288,7 @@ import PiNotificationForm from "./component/notification-form/index.vue";
import PiTaskView from "./component/task-view/index.vue";
import PiStatusShow from "./component/status-show.vue";
import VDraggable from "vuedraggable";
import * as _ from "lodash-es";
import { cloneDeep, merge, remove } from "lodash-es";
import { message, Modal, notification } from "ant-design-vue";
import { nanoid } from "nanoid";
import { PipelineDetail, PipelineOptions, RunHistory } from "./type";
@@ -299,9 +299,10 @@ import { useSettingStore } from "/@/store/settings";
import { useUserStore } from "/@/store/user";
import TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
import { eachSteps, findStep } from "../utils";
import { PluginGroups, usePluginStore } from "/@/store/plugin";
import { usePluginStore } from "/@/store/plugin";
import { getCronNextTimes } from "/@/components/cron-editor/utils";
import { useCertViewer } from "/@/views/certd/pipeline/use";
import { useI18n } from "/@/locales";
export default defineComponent({
name: "PipelineEdit",
@@ -339,6 +340,7 @@ export default defineComponent({
},
emits: ["update:modelValue", "update:editMode"],
setup(props, ctx) {
const { t } = useI18n();
const currentPipeline: Ref<any> = ref({});
const pipeline: Ref<any> = ref({});
@@ -371,18 +373,20 @@ export default defineComponent({
const loadCurrentHistoryDetail = async () => {
const detail: RunHistory = await props.options?.getHistoryDetail({ historyId: currentHistory.value.id });
currentHistory.value.logs = detail.logs;
_.merge(currentHistory.value.pipeline, detail.pipeline);
currentHistory.value.pipeline = detail.pipeline;
currentHistory.value.status = detail.pipeline.status.result;
};
const changeCurrentHistory = async (history?: RunHistory) => {
if (!history) {
//取消历史记录查看模式
currentHistory.value = null;
pipeline.value = currentPipeline.value;
pipeline.value = cloneDeep(currentPipeline.value);
return;
}
currentHistory.value = history;
pipeline.value = history.pipeline;
await loadCurrentHistoryDetail();
pipeline.value = currentHistory.value.pipeline;
currentPipeline.value = currentHistory.value.pipeline;
};
async function loadHistoryList(reload = false) {
@@ -413,7 +417,8 @@ export default defineComponent({
return true;
}
}
if (historyList[0].pipeline?.version === pipeline.value.version) {
//@ts-ignore
if (historyList[0]?.version === pipeline.value.version) {
await changeCurrentHistory(historyList[0]);
}
}
@@ -435,8 +440,13 @@ export default defineComponent({
}
if (currentHistory.value != null) {
if (currentHistory.value.pipeline?.status?.status === "start") {
if (currentHistory.value.status === "start") {
await loadCurrentHistoryDetail();
pipeline.value = currentHistory.value.pipeline;
// if (currentHistory.value.pipeline?.status?.status !== "start") {
// 不传true好像不会刷新
// await loadHistoryList(true);
// }
}
}
} catch (e) {
@@ -477,7 +487,7 @@ export default defineComponent({
return;
}
const detail: PipelineDetail = await props.options.getPipelineDetail({ pipelineId: value });
currentPipeline.value = _.merge(
currentPipeline.value = merge(
{
title: "新管道流程",
stages: [],
@@ -540,7 +550,7 @@ export default defineComponent({
};
const taskCopy = (stage: any, stageIndex: number, task: any) => {
task = _.cloneDeep(task);
task = cloneDeep(task);
task.id = nanoid();
task.title = task.title + "_copy";
for (const step of task.steps) {
@@ -560,7 +570,7 @@ export default defineComponent({
if (type === "delete") {
stage.tasks.splice(taskIndex, 1);
if (stage.tasks.length === 0) {
_.remove(pipeline.value.stages, (item: Runnable) => {
remove(pipeline.value.stages, (item: Runnable) => {
return item.id === stage.id;
});
}
@@ -666,9 +676,19 @@ export default defineComponent({
notificationFormRef.value.notificationView(notification, (type: string, value: any) => {});
}
};
const notificationDelete = (notification: any, index: any) => {
Modal.confirm({
title: t("certd.confirm"),
content: t("certd.confirm_delete_trigger"),
async onOk() {
pipeline.value.notifications.splice(index, 1);
},
});
};
return {
notificationAdd,
notificationEdit,
notificationDelete,
notificationFormRef,
};
}
@@ -788,7 +808,7 @@ export default defineComponent({
currentPipeline.value = pipeline.value;
//移除空阶段
_.remove(pipeline.value.stages, (item: Stage) => {
remove(pipeline.value.stages, (item: Stage) => {
return item.tasks.length === 0;
});
@@ -802,12 +822,12 @@ export default defineComponent({
}
};
const edit = () => {
pipeline.value = _.cloneDeep(currentPipeline.value);
pipeline.value = cloneDeep(currentPipeline.value);
currentHistory.value = null;
toggleEditMode(true);
};
const cancel = () => {
pipeline.value = currentPipeline.value;
pipeline.value = cloneDeep(currentPipeline.value);
toggleEditMode(false);
};

View File

@@ -3,7 +3,12 @@
<template #header>
<div class="title">
{{ t("certd.subdomainHosting") }}
<span class="sub">{{ t("certd.subdomainHostingHint") }}</span>
<span class="sub">
{{ t("certd.subdomainHostingHint") }} {{ t("certd.subdomainHelpText") }}
<a href="https://help.aliyun.com/zh/dns/subdomain-management" target="_blank">
{{ t("certd.subdomainManagement") }}
</a>
</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">

View File

@@ -214,6 +214,7 @@ function transformStatusCount() {
{ name: "error", label: "失败" },
{ name: "canceled", label: "已取消" },
{ name: null, label: "未执行" },
{ name: "skip", label: "跳过" },
];
const result = [];
for (const item of sorted) {

View File

@@ -43,7 +43,7 @@ const slots = defineSlots();
.data-item {
display: flex;
flex-direction: column;
height: 180px;
height: 188px;
.header {
display: flex;
justify-content: space-between;

View File

@@ -1,10 +1,11 @@
import * as api from "./api";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/user";
import { Modal, notification } from "ant-design-vue";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/settings";
import { useI18n } from "/src/locales";
import { computed } from "vue";
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -26,7 +27,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
const userStore = useUserStore();
const settingStore = useSettingStore();
const userValidTimeEnabled = compute(() => {
const userValidTimeEnabled = computed(() => {
return settingStore.sysPublic.userValidTimeEnabled === true;
});
return {
@@ -226,7 +227,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
column: {
align: "center",
sorter: true,
width: 100,
width: 160,
show: userValidTimeEnabled,
cellRender({ value }) {
if (value == null || value === 0) {

View File

@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.10](https://github.com/certd/certd/compare/v1.36.9...v1.36.10) (2025-07-18)
### Bug Fixes
* 企业微信通知改成text类型因为markdown类型不支持@用户 ([747d266](https://github.com/certd/certd/commit/747d26674248082e678a3fd5ecc94712641a2716))
* api接口获取不到证书的bug ([05a33a0](https://github.com/certd/certd/commit/05a33a0ec9999e2802f6c7b23cc1c61a2b9e963d))
### Performance Improvements
* 部署到阿里云oss插件支持选择上传到阿里云cas中的证书 ([2ea2c8c](https://github.com/certd/certd/commit/2ea2c8c05fc40f79595f1bbde67c1413558bf684))
* 账号即将过期通知 ([e403450](https://github.com/certd/certd/commit/e40345095f31e2fb8e2333a6647466659133fa0c))
* 子域名托管重复域名不允许添加 ([ffc0c7b](https://github.com/certd/certd/commit/ffc0c7bb7b16d9904fd2d905d1c4e1d4854e92a9))
## [1.36.9](https://github.com/certd/certd/compare/v1.36.7...v1.36.9) (2025-07-15)
**Note:** Version bump only for package @certd/ui-server
## [1.36.7](https://github.com/certd/certd/compare/v1.36.6...v1.36.7) (2025-07-15)
### Bug Fixes
* 修复流水线列表页报length错误的bug ([9864792](https://github.com/certd/certd/commit/9864792bbfd149e770d6e1ffa809573694f99dd3))
* 修复流水线页面状态没有刷新的bug ([93e9498](https://github.com/certd/certd/commit/93e9498b410353f504e11e264db62468895d7290))
* 修复自定义证书检查时间重启之后不生效的bug ([38e867c](https://github.com/certd/certd/commit/38e867c917bbc68bd228bdd8064f3e7358d6413d))
### Performance Improvements
* 支持上传证书到各种对象存储oss、cos、七牛、s3、minio等 ([1da8617](https://github.com/certd/certd/commit/1da8617a53a675776635bbc3bcb3c6d7dff83e27))
* 支持邮箱发送证书 ([95332d5](https://github.com/certd/certd/commit/95332d5db96cd54ddab6ab737332417a09169b39))
## [1.36.6](https://github.com/certd/certd/compare/v1.36.5...v1.36.6) (2025-07-14)
### Performance Improvements
* 优化流水线列表页面、详情页面性能,精简返回数据 ([609ac9c](https://github.com/certd/certd/commit/609ac9c9a2dde605eb09834ae59693c1cb238765))
* 支持自动选择校验方式申请证书 ([3f99432](https://github.com/certd/certd/commit/3f9943270cfb12946e38e6272bc5e8d95ad6ab9e))
* OpenAPI支持autoApply参数 ([42f4d14](https://github.com/certd/certd/commit/42f4d1477dc791520a874aed56035abcbc8c433b))
## [1.36.5](https://github.com/certd/certd/compare/v1.36.4...v1.36.5) (2025-07-11)
### Bug Fixes

View File

@@ -0,0 +1,20 @@
DROP TABLE IF EXISTS `cd_domain`;
CREATE TABLE `cd_domain`
(
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
`user_id` bigint,
`domain` varchar(512),
challenge_type varchar(50),
dns_provider_type varchar(50),
dns_provider_access bigint,
http_uploader_type varchar(50),
http_uploader_access bigint,
http_upload_root_dir varchar(512),
`disabled` boolean NOT NULL DEFAULT false,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX `index_domain_user_id` ON `cd_domain` (`user_id`);
CREATE INDEX `index_domain_domain` ON `cd_domain` (`domain`);

View File

@@ -0,0 +1,20 @@
DROP TABLE IF EXISTS "cd_domain";
CREATE TABLE "cd_domain"
(
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
"user_id" bigint,
"domain" varchar(512),
challenge_type varchar(50),
dns_provider_type varchar(50),
dns_provider_access bigint,
http_uploader_type varchar(50),
http_uploader_access bigint,
http_upload_root_dir varchar(512),
"disabled" boolean NOT NULL DEFAULT (false),
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);
CREATE INDEX "index_domain_user_id" ON "cd_domain" ("user_id");
CREATE INDEX "index_domain_domain" ON "cd_domain" ("domain");

View File

@@ -1,8 +1,9 @@
DROP TABLE IF EXISTS "cd_domain";
CREATE TABLE "cd_domain"
(
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"user_id" integer,
"domain" varchar(1024),
"domain" varchar(512),
challenge_type varchar(50),
dns_provider_type varchar(50),
dns_provider_access bigint,

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.36.5",
"version": "1.36.10",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -42,20 +42,20 @@
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.36.5",
"@certd/basic": "^1.36.5",
"@certd/commercial-core": "^1.36.5",
"@certd/acme-client": "^1.36.10",
"@certd/basic": "^1.36.10",
"@certd/commercial-core": "^1.36.10",
"@certd/cv4pve-api-javascript": "^8.4.1",
"@certd/jdcloud": "^1.36.5",
"@certd/lib-huawei": "^1.36.5",
"@certd/lib-k8s": "^1.36.5",
"@certd/lib-server": "^1.36.5",
"@certd/midway-flyway-js": "^1.36.5",
"@certd/pipeline": "^1.36.5",
"@certd/plugin-cert": "^1.36.5",
"@certd/plugin-lib": "^1.36.5",
"@certd/plugin-plus": "^1.36.5",
"@certd/plus-core": "^1.36.5",
"@certd/jdcloud": "^1.36.10",
"@certd/lib-huawei": "^1.36.10",
"@certd/lib-k8s": "^1.36.10",
"@certd/lib-server": "^1.36.10",
"@certd/midway-flyway-js": "^1.36.10",
"@certd/pipeline": "^1.36.10",
"@certd/plugin-cert": "^1.36.10",
"@certd/plugin-lib": "^1.36.10",
"@certd/plugin-plus": "^1.36.10",
"@certd/plus-core": "^1.36.10",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",

View File

@@ -1,13 +1,15 @@
import { ALL, Body, Controller, Get, Inject, Post, Provide, Query } from '@midwayjs/core';
import { CodeException, Constants, EncryptService } from '@certd/lib-server';
import { CertInfoService } from '../../../modules/monitor/service/cert-info-service.js';
import { CertInfo } from '@certd/plugin-cert';
import { OpenKey } from '../../../modules/open/service/open-key-service.js';
import { BaseOpenController } from '../base-open-controller.js';
import { ALL, Body, Controller, Get, Inject, Post, Provide, Query } from "@midwayjs/core";
import { CodeException, Constants, EncryptService } from "@certd/lib-server";
import { CertInfo } from "@certd/plugin-cert";
import { OpenKey } from "../../../modules/open/service/open-key-service.js";
import { BaseOpenController } from "../base-open-controller.js";
import { CertInfoFacade } from "../../../modules/monitor/facade/cert-info-facade.js";
import { merge } from "lodash-es";
export type CertGetReq = {
domains?: string;
certId: number;
autoApply?:boolean;
};
/**
@@ -16,7 +18,7 @@ export type CertGetReq = {
@Controller('/api/v1/cert')
export class OpenCertController extends BaseOpenController {
@Inject()
certInfoService: CertInfoService;
certInfoFacade: CertInfoFacade;
@Inject()
encryptService: EncryptService;
@@ -29,10 +31,13 @@ export class OpenCertController extends BaseOpenController {
throw new CodeException(Constants.res.openKeyError);
}
const res: CertInfo = await this.certInfoService.getCertInfo({
const req = merge({}, bean, query)
const res: CertInfo = await this.certInfoFacade.getCertInfo({
userId,
domains: bean.domains || query.domains,
certId: bean.certId || query.certId,
domains: req.domains,
certId: req.certId,
autoApply: req.autoApply??false,
});
return this.ok(res);
}

View File

@@ -1,16 +1,15 @@
import { ALL, Body, Controller, Get, Inject, Post, Provide, Query } from '@midwayjs/core';
import { CommonException, Constants, CrudController, PermissionException } from '@certd/lib-server';
import { PipelineEntity } from '../../../modules/pipeline/entity/pipeline.js';
import { HistoryService } from '../../../modules/pipeline/service/history-service.js';
import { HistoryLogService } from '../../../modules/pipeline/service/history-log-service.js';
import { HistoryEntity } from '../../../modules/pipeline/entity/history.js';
import { HistoryLogEntity } from '../../../modules/pipeline/entity/history-log.js';
import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js';
import * as fs from 'fs';
import { logger } from '@certd/basic';
import { AuthService } from '../../../modules/sys/authority/service/auth-service.js';
import { SysSettingsService } from '@certd/lib-server';
import { In } from 'typeorm';
import { ALL, Body, Controller, Get, Inject, Post, Provide, Query } from "@midwayjs/core";
import { CommonException, Constants, CrudController, PermissionException, SysSettingsService } from "@certd/lib-server";
import { PipelineEntity } from "../../../modules/pipeline/entity/pipeline.js";
import { HistoryService } from "../../../modules/pipeline/service/history-service.js";
import { HistoryLogService } from "../../../modules/pipeline/service/history-log-service.js";
import { HistoryEntity } from "../../../modules/pipeline/entity/history.js";
import { HistoryLogEntity } from "../../../modules/pipeline/entity/history-log.js";
import { PipelineService } from "../../../modules/pipeline/service/pipeline-service.js";
import * as fs from "fs";
import { logger } from "@certd/basic";
import { AuthService } from "../../../modules/sys/authority/service/auth-service.js";
import { In } from "typeorm";
/**
* 证书
@@ -88,11 +87,30 @@ export class HistoryController extends CrudController<HistoryService> {
const buildQuery = qb => {
qb.limit(20);
};
const withDetail = body.withDetail;
delete body.withDetail;
let select:any = null
if (!withDetail) {
select = {
pipeline: true, // 后面这里改成false
id: true,
userId: true,
pipelineId: true,
status: true,
// startTime: true,
triggerType: true,
endTime: true,
createTime: true,
updateTime: true
};
}
const listRet = await this.getService().list({
query: body,
sort: { prop: 'id', asc: false },
buildQuery,
select
});
for (const item of listRet) {
if (!item.pipeline) {
continue;
@@ -100,7 +118,13 @@ export class HistoryController extends CrudController<HistoryService> {
const json = JSON.parse(item.pipeline);
delete json.stages;
item.pipeline = json;
//@ts-ignore
item.version = json.version;
item.status = json.status.result
delete item.pipeline;
}
return this.ok(listRet);
}

View File

@@ -1,15 +1,16 @@
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { PipelineService } from '../pipeline/service/pipeline-service.js';
import { logger } from '@certd/basic';
import {Autoload, Config, Init, Inject, Scope, ScopeEnum} from '@midwayjs/core';
import {PipelineService} from '../pipeline/service/pipeline-service.js';
import {logger} from '@certd/basic';
import {SysSettingsService, SysSiteInfo} from '@certd/lib-server';
import { SiteInfoService } from '../monitor/index.js';
import { Cron } from '../cron/cron.js';
import {SiteInfoService} from '../monitor/index.js';
import {Cron} from '../cron/cron.js';
import {UserSettingsService} from "../mine/service/user-settings-service.js";
import {UserSiteMonitorSetting} from "../mine/service/models.js";
import {getPlusInfo} from "@certd/plus-core";
import dayjs from "dayjs";
import {NotificationService} from "../pipeline/service/notification-service.js";
import {UserService} from "../sys/authority/service/user-service.js";
import {Between} from "typeorm";
@Autoload()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@@ -58,6 +59,8 @@ export class AutoCRegisterCron {
await this.registerPlusExpireCheckCron();
await this.registerUserExpireCheckCron()
}
async registerSiteMonitorCron() {
@@ -71,7 +74,7 @@ export class AutoCRegisterCron {
}
})
for (const item of monitorSettingList) {
const setting = item.setting ?? JSON.parse(item.setting)
const setting = item.setting ? JSON.parse(item.setting):{}
if(!setting?.cron){
continue
}
@@ -137,4 +140,61 @@ export class AutoCRegisterCron {
}
})
}
registerUserExpireCheckCron() {
// 添加plus即将到期检查任务
this.cron.register({
name: 'user-expire-check',
cron: `0 20 9 * * *`, // 一天只能检查一次,否则会重复发送通知
job: async () => {
const getExpiresDaysUsers = async (days: number) => {
const targetDate = dayjs().add(days, 'day')
const startTime = targetDate.startOf('day').valueOf()
const endTime = targetDate.endOf('day').valueOf()
return await this.userService.find({
where: {
validTime: Between(startTime, endTime),
status: 1
}
})
}
const notifyExpiresDaysUsers = async (days: number) => {
const list = await getExpiresDaysUsers(days)
if (list.length === 0) {
return
}
let title = `账号即将到期`
let content = `您的账号剩余${days}天到期,请及时续期,以免影响业务`
if (days <= 0) {
title = `账号已过期`
content = `您的账号已过期${Math.abs(days)}天,请尽快续期,以免影响业务`
}
const url = await this.notificationService.getBindUrl("");
for (const user of list) {
logger.info(`发送到期通知给用户:${user.username}`)
await this.notificationService.send({
useDefault: true,
logger: logger,
body: {
title,
content,
errorMessage: title,
url
}
}, user.id)
}
}
await notifyExpiresDaysUsers(7)
await notifyExpiresDaysUsers(3)
await notifyExpiresDaysUsers(1)
await notifyExpiresDaysUsers(0)
await notifyExpiresDaysUsers(-1)
await notifyExpiresDaysUsers(-3)
}
})
}
}

View File

@@ -98,6 +98,8 @@ export class EmailService implements IEmailService {
to: email.receivers.join(', '), // list of receivers
subject: subject,
text: email.content,
html: email.html,
attachments: email.attachments,
};
await transporter.sendMail(mailOptions);
}

View File

@@ -154,7 +154,7 @@ export class DomainService extends BaseService<DomainEntity> {
}
continue
}
const cnameRecord:CnameRecordEntity = cnameMap[mainDomain]
const cnameRecord:CnameRecordEntity = cnameMap[domain]
if (cnameRecord) {
domainVerifiers[domain] = {
domain,
@@ -168,7 +168,7 @@ export class DomainService extends BaseService<DomainEntity> {
}
continue
}
const httpRecord = httpMap[mainDomain]
const httpRecord = httpMap[domain]
if (httpRecord) {
domainVerifiers[domain] = {
domain,

View File

@@ -1,19 +1,27 @@
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
import {InjectEntityModel} from '@midwayjs/typeorm';
import {Repository} from 'typeorm';
import {AccessService, BaseService, PlusService, ValidateException} from '@certd/lib-server';
import {CnameRecordEntity, CnameRecordStatusType} from '../entity/cname-record.js';
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
import {CnameProvider, CnameRecord} from '@certd/pipeline';
import {cache, http, isDev, logger, utils} from '@certd/basic';
import {getAuthoritativeDnsResolver, walkTxtRecord} from '@certd/acme-client';
import {CnameProviderService} from './cname-provider-service.js';
import {CnameProviderEntity} from '../entity/cname-provider.js';
import {CommonDnsProvider} from './common-provider.js';
import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
import punycode from 'punycode.js'
import {SubDomainService} from "../../pipeline/service/sub-domain-service.js";
import {SubDomainsGetter} from "../../pipeline/service/getter/sub-domain-getter.js";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import {
AccessService,
BaseService,
PlusService,
SysInstallInfo,
SysSettingsService,
ValidateException
} from "@certd/lib-server";
import { CnameRecordEntity, CnameRecordStatusType } from "../entity/cname-record.js";
import { createDnsProvider, IDnsProvider } from "@certd/plugin-cert";
import { CnameProvider, CnameRecord } from "@certd/pipeline";
import { cache, http, isDev, logger, utils } from "@certd/basic";
import { getAuthoritativeDnsResolver, walkTxtRecord } from "@certd/acme-client";
import { CnameProviderService } from "./cname-provider-service.js";
import { CnameProviderEntity } from "../entity/cname-provider.js";
import { CommonDnsProvider } from "./common-provider.js";
import { DomainParser } from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
import punycode from "punycode.js";
import { SubDomainService } from "../../pipeline/service/sub-domain-service.js";
import { SubDomainsGetter } from "../../pipeline/service/getter/sub-domain-getter.js";
type CnameCheckCacheValue = {
validating: boolean;
pass: boolean;
@@ -35,6 +43,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
@Inject()
cnameProviderService: CnameProviderService;
@Inject()
sysSettingsService: SysSettingsService;
@Inject()
accessService: AccessService;
@@ -101,7 +111,17 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
}
param.hostRecord = hostRecord;
const cnameKey = utils.id.simpleNanoId().toLowerCase();
const randomKey = utils.id.simpleNanoId(6).toLowerCase();
let userIdHash = ""
if(param.cnameProviderId < 0){
//公共cname服务
userIdHash = utils.hash.md5(`userId${userId}_${randomKey}`).substring(0, 10)
}else{
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo)
userIdHash = utils.hash.md5(`${installInfo.siteId}_${randomKey}`).substring(0, 10)
}
const cnameKey = `${userIdHash}-${randomKey}`;
const safeDomain = param.domain.replaceAll('.', '-');
param.recordValue = `${safeDomain}.${cnameKey}.${cnameProvider.domain}`;
}

View File

@@ -0,0 +1,135 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { CodeException, Constants } from "@certd/lib-server";
import { CertInfoEntity } from "../entity/cert-info.js";
import { utils } from "@certd/basic";
import { PipelineService } from "../../pipeline/service/pipeline-service.js";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
import { UserEmailSetting } from "../../mine/service/models.js";
import { PipelineEntity } from "../../pipeline/entity/pipeline.js";
import { CertInfoService } from "../service/cert-info-service.js";
import { DomainService } from "../../cert/service/domain-service.js";
import { DomainVerifierGetter } from "../../pipeline/service/getter/domain-verifier-getter.js";
@Provide("CertInfoFacade")
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class CertInfoFacade {
@Inject()
pipelineService: PipelineService;
@Inject()
certInfoService: CertInfoService ;
@Inject()
domainService: DomainService
@Inject()
userSettingsService : UserSettingsService
async getCertInfo(req: { domains?: string; certId?: number; userId: number,autoApply?:boolean }) {
const { domains, certId, userId } = req;
if (certId) {
return await this.certInfoService.getCertInfoById({ id: certId, userId });
}
if (!domains) {
throw new CodeException({
...Constants.res.openParamError,
message: "参数错误certId和domains必须传一个",
});
}
const domainArr = domains.split(',');
const matchedList = await this.certInfoService.getMatchCertList({domains:domainArr,userId})
let matched: CertInfoEntity = null
if (matchedList.length === 0 ) {
if(req.autoApply === true){
//自动申请,先创建自动申请流水线
const pipeline:PipelineEntity = await this.createAutoPipeline({domains:domainArr,userId})
await this.triggerApplyPipeline({pipelineId:pipeline.id})
}else{
throw new CodeException({
...Constants.res.openCertNotFound,
message:"在证书仓库中没有找到匹配域名的证书请先创建证书流水线或传入autoApply参数自动创建"
});
}
}
matched = null;
for (const item of matchedList) {
if (item.expiresTime>0 && item.expiresTime > new Date().getTime()) {
matched = item;
break
}
}
if (!matched) {
if(req.autoApply === true){
//如果没有找到有效期内的证书,则自动触发一次申请
const first = matchedList[0]
await this.triggerApplyPipeline({pipelineId:first.pipelineId})
return
}else{
throw new CodeException({
...Constants.res.openCertNotFound,
message:"证书已过期请触发流水线申请或者传入autoApply参数自动触发"
});
}
}
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId });
}
async createAutoPipeline(req:{domains:string[],userId:number}){
const verifierGetter = new DomainVerifierGetter(req.userId, this.domainService)
const allDomains = []
for (const item of req.domains) {
allDomains.push(item.replaceAll("*.",""))
}
const verifiers = await verifierGetter.getVerifiers(allDomains)
for (const item of allDomains) {
if (!verifiers[item]){
throw new CodeException({
...Constants.res.openDomainNoVerifier,
message:`域名${item}没有配置校验方式,请先在域名管理页面配置`,
data:{
domain:item
}
});
}
}
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId,UserEmailSetting)
if(!userEmailSetting.list){
throw new CodeException(Constants.res.openEmailNotFound)
}
const email = userEmailSetting.list[0]
return await this.pipelineService.createAutoPipeline({
domains: req.domains,
email,
userId: req.userId,
from:"OpenAPI"
})
}
async triggerApplyPipeline(req:{pipelineId:number}){
//查询流水线状态
const status = await this.pipelineService.getStatus(req.pipelineId)
if (status != 'running') {
await this.pipelineService.trigger(req.pipelineId)
await utils.sleep(1000)
}
const certInfo = await this.certInfoService.getByPipelineId(req.pipelineId)
throw new CodeException({
...Constants.res.openCertApplying,
data:{
pipelineId:req.pipelineId,
certId:certInfo?.id
}
});
}
}

View File

@@ -1,10 +1,10 @@
import {Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, CodeException, Constants, PageReq} from "@certd/lib-server";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {MoreThan, Repository} from "typeorm";
import {CertInfoEntity} from "../entity/cert-info.js";
import {utils} from "@certd/basic";
import {CertInfo, CertReader} from "@certd/plugin-cert";
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, CodeException, Constants, PageReq } from "@certd/lib-server";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { CertInfoEntity } from "../entity/cert-info.js";
import { utils } from "@certd/basic";
import { CertInfo, CertReader } from "@certd/plugin-cert";
export type UploadCertReq = {
id?: number;
@@ -71,6 +71,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
}
await this.addOrUpdate(bean);
return bean.id
}
async deleteByPipelineId(id: number) {
@@ -82,44 +83,34 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
});
}
async getCertInfo(params: { domains?: string; certId?: number; userId: number }) {
const { domains, certId, userId } = params;
if (certId) {
return await this.getCertInfoById({ id: certId, userId });
}
return await this.getCertInfoByDomains({
domains,
userId,
});
}
private async getCertInfoByDomains(params: { domains: string; userId: number }) {
async getMatchCertList(params: { domains: string[]; userId: number }) {
const { domains, userId } = params;
if (!domains) {
throw new CodeException(Constants.res.openCertNotFound);
throw new CodeException({
...Constants.res.openCertNotFound,
message:"域名不能为空"
});
}
const domainArr = domains.split(',');
const list = await this.find({
select: {
id: true,
domains: true,
expiresTime:true,
pipelineId:true,
},
where: {
userId,
expiresTime: MoreThan(new Date().getTime())
},
order: {
id: 'DESC',
},
});
//遍历查找
const matched = list.find(item => {
return list.filter(item => {
const itemDomains = item.domains.split(',');
return utils.domain.match(domainArr, itemDomains);
return utils.domain.match(domains, itemDomains);
});
if (!matched) {
throw new CodeException(Constants.res.openCertNotFound);
}
return await this.getCertInfoById({ id: matched.id, userId: userId });
}
async getCertInfoById(req: { id: number; userId: number }) {
@@ -176,4 +167,12 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
return bean;
}
async getByPipelineId(pipelineId: number) {
return await this.repository.findOne({
where: {
pipelineId,
},
});
}
}

View File

@@ -93,7 +93,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
if (oldIps.length === ips.length ){
//检查是否有变化
const oldIpList = oldIps.map(ip=>ip.ipAddress).sort().join(",")
const newIpList = ips.filter(ip=>!oldIpList.includes(ip)).sort().join(",")
const newIpList = ips.sort().join(",")
if(oldIpList === newIpList){
//无变化
hasChanged = false

View File

@@ -1,22 +1,21 @@
import {IServiceGetter} from "@certd/pipeline";
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {AccessGetter, AccessService} from "@certd/lib-server";
import {CnameProxyService} from "./cname-proxy-service.js";
import {NotificationGetter} from "./notification-getter.js";
import {NotificationService} from "../notification-service.js";
import {CnameRecordService} from "../../../cname/service/cname-record-service.js";
import {SubDomainsGetter} from './sub-domain-getter.js'
import {DomainVerifierGetter} from "./domain-verifier-getter.js";
import {Context} from "@midwayjs/koa";
import {DomainService} from "../../../cert/service/domain-service.js";
import {SubDomainService} from "../sub-domain-service.js";
import { IServiceGetter } from "@certd/pipeline";
import { ApplicationContext, IMidwayContainer, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { AccessGetter, AccessService } from "@certd/lib-server";
import { CnameProxyService } from "./cname-proxy-service.js";
import { NotificationGetter } from "./notification-getter.js";
import { NotificationService } from "../notification-service.js";
import { CnameRecordService } from "../../../cname/service/cname-record-service.js";
import { SubDomainsGetter } from "./sub-domain-getter.js";
import { DomainVerifierGetter } from "./domain-verifier-getter.js";
import { DomainService } from "../../../cert/service/domain-service.js";
import { SubDomainService } from "../sub-domain-service.js";
export class TaskServiceGetter implements IServiceGetter{
private userId: number;
private ctx : Context;
constructor(userId:number,ctx:Context) {
private appCtx : IMidwayContainer;
constructor(userId:number,appCtx:IMidwayContainer) {
this.userId = userId;
this.ctx = ctx
this.appCtx = appCtx
}
async get<T>(serviceName: string): Promise<T> {
@@ -36,27 +35,27 @@ export class TaskServiceGetter implements IServiceGetter{
}
async getSubDomainsGetter(): Promise<SubDomainsGetter> {
const subDomainsService:SubDomainService = await this.ctx.requestContext.getAsync("subDomainService")
const subDomainsService:SubDomainService = await this.appCtx.getAsync("subDomainService")
return new SubDomainsGetter(this.userId, subDomainsService)
}
async getAccessService(): Promise<AccessGetter> {
const accessService:AccessService = await this.ctx.requestContext.getAsync("accessService")
const accessService:AccessService = await this.appCtx.getAsync("accessService")
return new AccessGetter(this.userId, accessService.getById.bind(accessService));
}
async getCnameProxyService(): Promise<CnameProxyService> {
const cnameRecordService:CnameRecordService = await this.ctx.requestContext.getAsync("cnameRecordService")
const cnameRecordService:CnameRecordService = await this.appCtx.getAsync("cnameRecordService")
return new CnameProxyService(this.userId, cnameRecordService.getWithAccessByDomain.bind(cnameRecordService));
}
async getNotificationService(): Promise<NotificationGetter> {
const notificationService:NotificationService = await this.ctx.requestContext.getAsync("notificationService")
const notificationService:NotificationService = await this.appCtx.getAsync("notificationService")
return new NotificationGetter(this.userId, notificationService);
}
async getDomainVerifierGetter(): Promise<DomainVerifierGetter> {
const domainService:DomainService = await this.ctx.requestContext.getAsync("domainService")
const domainService:DomainService = await this.appCtx.getAsync("domainService")
return new DomainVerifierGetter(this.userId, domainService);
}
}
@@ -67,12 +66,12 @@ export type TaskServiceCreateReq = {
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class TaskServiceBuilder {
@Inject()
ctx: Context;
@ApplicationContext()
appCtx: IMidwayContainer;
create(req:TaskServiceCreateReq){
const userId = req.userId;
return new TaskServiceGetter(userId,this.ctx)
return new TaskServiceGetter(userId,this.appCtx)
}
}

View File

@@ -1,6 +1,6 @@
import {Config, Inject, Provide, Scope, ScopeEnum, sleep} from "@midwayjs/core";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {In, MoreThan, Repository} from "typeorm";
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from "@midwayjs/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { In, MoreThan, Repository } from "typeorm";
import {
AccessService,
BaseService,
@@ -11,8 +11,8 @@ import {
SysSettingsService,
SysSiteInfo
} from "@certd/lib-server";
import {PipelineEntity} from "../entity/pipeline.js";
import {PipelineDetail} from "../entity/vo/pipeline-detail.js";
import { PipelineEntity } from "../entity/pipeline.js";
import { PipelineDetail } from "../entity/vo/pipeline-detail.js";
import {
Executor,
IAccessService,
@@ -25,33 +25,32 @@ import {
SysInfo,
UserInfo
} from "@certd/pipeline";
import {DbStorage} from "./db-storage.js";
import {StorageService} from "./storage-service.js";
import {Cron} from "../../cron/cron.js";
import {HistoryService} from "./history-service.js";
import {HistoryEntity} from "../entity/history.js";
import {HistoryLogEntity} from "../entity/history-log.js";
import {HistoryLogService} from "./history-log-service.js";
import {EmailService} from "../../basic/service/email-service.js";
import {UserService} from "../../sys/authority/service/user-service.js";
import {CnameRecordService} from "../../cname/service/cname-record-service.js";
import {PluginConfigGetter} from "../../plugin/service/plugin-config-getter.js";
import { DbStorage } from "./db-storage.js";
import { StorageService } from "./storage-service.js";
import { Cron } from "../../cron/cron.js";
import { HistoryService } from "./history-service.js";
import { HistoryEntity } from "../entity/history.js";
import { HistoryLogEntity } from "../entity/history-log.js";
import { HistoryLogService } from "./history-log-service.js";
import { EmailService } from "../../basic/service/email-service.js";
import { UserService } from "../../sys/authority/service/user-service.js";
import { CnameRecordService } from "../../cname/service/cname-record-service.js";
import { PluginConfigGetter } from "../../plugin/service/plugin-config-getter.js";
import dayjs from "dayjs";
import {DbAdapter} from "../../db/index.js";
import {isComm, isPlus} from "@certd/plus-core";
import {logger} from "@certd/basic";
import {UrlService} from "./url-service.js";
import {NotificationService} from "./notification-service.js";
import {UserSuiteEntity, UserSuiteService} from "@certd/commercial-core";
import {CertInfoService} from "../../monitor/service/cert-info-service.js";
import {TaskServiceBuilder} from "./getter/task-service-getter.js";
import {nanoid} from "nanoid";
import {set} from "lodash-es";
import { DbAdapter } from "../../db/index.js";
import { isComm, isPlus } from "@certd/plus-core";
import { logger } from "@certd/basic";
import { UrlService } from "./url-service.js";
import { NotificationService } from "./notification-service.js";
import { UserSuiteEntity, UserSuiteService } from "@certd/commercial-core";
import { CertInfoService } from "../../monitor/service/cert-info-service.js";
import { TaskServiceBuilder } from "./getter/task-service-getter.js";
import { nanoid } from "nanoid";
import { set } from "lodash-es";
const runningTasks: Map<string | number, Executor> = new Map();
/**
* 证书申请
*/
@@ -91,7 +90,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
@Inject()
cron: Cron;
@Config('certd')
@Config("certd")
private certdConfig: any;
@Inject()
@@ -112,17 +111,35 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
async add(bean: PipelineEntity) {
bean.status = ResultType.none
bean.status = ResultType.none;
await this.save(bean);
return bean;
}
async page(pageReq: PageReq<PipelineEntity>) {
//模版流水线不要被查询出来
set(pageReq,"query.isTemplate",false)
set(pageReq, "query.isTemplate", false);
const result = await super.page(pageReq);
await this.fillLastVars(result.records);
for (const item of result.records) {
if (!item.content) {
continue;
}
const pipeline = JSON.parse(item.content);
let stepCount = 0;
if(pipeline.stages){
RunnableCollection.each(pipeline.stages, (runnable: any) => {
stepCount++;
});
}
// @ts-ignore
item.stepCount = stepCount;
// @ts-ignore
item.triggerCount = pipeline.triggers?.length;
delete item.content;
}
return result;
}
@@ -132,7 +149,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
for (const record of records) {
pipelineIds.push(record.id);
recordMap[record.id] = record;
record.title = record.title + '';
record.title = record.title + "";
}
if (pipelineIds?.length > 0) {
const vars = await this.storageService.findPipelineVars(pipelineIds);
@@ -153,17 +170,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
const info = await this.info(pipelineId);
if (info && !info.disabled) {
const pipeline = JSON.parse(info.content);
this.registerTriggers(pipeline,false);
this.registerTriggers(pipeline, false);
}
}
public async registerTrigger(info:PipelineEntity) {
public async registerTrigger(info: PipelineEntity) {
if (info == null) {
return;
}
if (info && !info.disabled) {
const pipeline = JSON.parse(info.content);
this.registerTriggers(pipeline,false);
this.registerTriggers(pipeline, false);
}
}
@@ -190,12 +207,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
const isUpdate = bean.id > 0 && old != null;
const pipeline = JSON.parse(bean.content || '{}');
const pipeline = JSON.parse(bean.content || "{}");
RunnableCollection.initPipelineRunnableType(pipeline);
let domains = [];
if (pipeline.stages) {
RunnableCollection.each(pipeline.stages, (runnable: any) => {
if (runnable.runnableType === 'step' && runnable.type.indexOf('CertApply')>=0) {
if (runnable.runnableType === "step" && runnable.type.indexOf("CertApply") >= 0) {
domains = runnable.input.domains || [];
}
});
@@ -206,7 +223,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.checkMaxPipelineCount(bean, pipeline, domains);
}
if (!bean.status ){
if (!bean.status) {
bean.status = ResultType.none;
}
if (!isUpdate) {
@@ -217,9 +234,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.doUpdatePipelineJson(bean, pipeline);
//保存域名信息到certInfo表
let fromType = 'pipeline';
if (bean.type === 'cert_upload') {
fromType = 'upload';
let fromType = "pipeline";
if (bean.type === "cert_upload") {
fromType = "upload";
}else if (bean.type === "cert_auto") {
fromType = "auto";
}
await this.certInfoService.updateDomains(pipeline.id, pipeline.userId || bean.userId, domains, fromType);
return bean;
@@ -230,7 +249,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
* @param bean
* @param pipeline
*/
async doUpdatePipelineJson(bean: PipelineEntity, pipeline:Pipeline) {
async doUpdatePipelineJson(bean: PipelineEntity, pipeline: Pipeline) {
await this.clearTriggers(bean);
if (pipeline.title) {
bean.title = pipeline.title;
@@ -261,7 +280,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
}
}
}else{
} else {
//非商业版校验用户最大流水线数量
const userId = bean.userId;
const userIsAdmin = await this.userService.isAdmin(userId);
@@ -280,12 +299,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
async foreachPipeline(callback: (pipeline: PipelineEntity) => void) {
const idEntityList = await this.repository.find({
select: {
id: true,
id: true
},
where: {
disabled: false,
templateId: 0,
},
templateId: 0
}
});
const ids = idEntityList.map(item => {
return item.id;
@@ -305,7 +324,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
//分段加载记录
for (const idArr of idsSpan) {
const list = await this.repository.findBy({
id: In(idArr),
id: In(idArr)
});
for (const entity of list) {
@@ -330,14 +349,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (onlyAdminUser && entity.userId !== 1) {
return;
}
const pipeline = JSON.parse(entity.content ?? '{}');
const pipeline = JSON.parse(entity.content ?? "{}");
try {
await this.registerTriggers(pipeline, immediateTriggerOnce);
} catch (e) {
logger.error('加载定时trigger失败', e);
logger.error("加载定时trigger失败", e);
}
});
logger.info('定时器数量:', this.cron.getTaskSize());
logger.info("定时器数量:", this.cron.getTaskSize());
}
async registerTriggers(pipeline?: Pipeline, immediateTriggerOnce = false) {
@@ -359,18 +378,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (isComm()) {
await this.checkHasDeployCount(id, entity.userId);
}
await this.checkUserStatus(entity.userId)
await this.checkUserStatus(entity.userId);
this.cron.register({
name: `pipeline.${id}.trigger.once`,
cron: null,
job: async () => {
logger.info('用户手动启动job');
logger.info("用户手动启动job");
try {
await this.doRun(entity, null, stepId);
} catch (e) {
logger.error('手动job执行失败', e);
logger.error("手动job执行失败", e);
}
},
}
});
}
@@ -382,7 +401,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
logger.error(e.message);
await this.update({
id: pipelineId,
status: 'no_deploy_count',
status: "no_deploy_count"
});
}
throw e;
@@ -390,7 +409,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
//@ts-ignore
async delete(id:any) {
async delete(id: any) {
await this.clearTriggers(id);
//TODO 删除storage
// const storage = new DbStorage(pipeline.userId, this.storageService);
@@ -405,11 +424,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (id == null) {
return;
}
let pipeline:PipelineEntity = null
if (typeof id === 'number') {
let pipeline: PipelineEntity = null;
if (typeof id === "number") {
pipeline = await this.info(id);
}else{
pipeline = id
} else {
pipeline = id;
}
if (!pipeline) {
return;
@@ -429,7 +448,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
registerCron(pipelineId, trigger) {
if (pipelineId == null) {
logger.warn('pipelineId为空无法注册定时任务');
logger.warn("pipelineId为空无法注册定时任务");
return;
}
@@ -438,11 +457,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
return;
}
cron = cron.trim();
if (cron.startsWith('* *')) {
cron = cron.replace('* *', '0 0');
if (cron.startsWith("* *")) {
cron = cron.replace("* *", "0 0");
}
if (cron.startsWith('*')) {
cron = cron.replace('*', '0');
if (cron.startsWith("*")) {
cron = cron.replace("*", "0");
}
const triggerId = trigger.id;
const name = this.buildCronKey(pipelineId, triggerId);
@@ -451,19 +470,19 @@ export class PipelineService extends BaseService<PipelineEntity> {
name,
cron,
job: async () => {
logger.info('定时任务触发:', pipelineId, triggerId);
logger.info("定时任务触发:", pipelineId, triggerId);
if (pipelineId == null) {
logger.warn('pipelineId为空,无法执行');
logger.warn("pipelineId为空,无法执行");
return;
}
try {
await this.run(pipelineId, triggerId);
} catch (e) {
logger.error('定时job执行失败', e);
logger.error("定时job执行失败", e);
}
},
}
});
logger.info('当前定时器数量:', this.cron.getTaskSize());
logger.info("当前定时器数量:", this.cron.getTaskSize());
}
/**
@@ -483,11 +502,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (isComm()) {
suite = await this.checkHasDeployCount(id, entity.userId);
}
try{
await this.checkUserStatus(entity.userId)
}catch (e) {
logger.info(e.message)
return
try {
await this.checkUserStatus(entity.userId);
} catch (e) {
logger.info(e.message);
return;
}
@@ -505,7 +524,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
return;
}
if (triggerType === 'timer') {
if (triggerType === "timer") {
if (entity.disabled) {
return;
}
@@ -514,25 +533,25 @@ export class PipelineService extends BaseService<PipelineEntity> {
const onChanged = async (history: RunHistory) => {
//保存执行历史
try {
logger.info('保存执行历史:', history.id);
logger.info("保存执行历史:", history.id);
await this.saveHistory(history);
} catch (e) {
const pipelineEntity = new PipelineEntity();
pipelineEntity.id = id;
pipelineEntity.status = 'error';
pipelineEntity.status = "error";
pipelineEntity.lastHistoryTime = history.pipeline.status.startTime;
await this.update(pipelineEntity);
logger.error('保存执行历史失败:', e);
logger.error("保存执行历史失败:", e);
throw e;
}
};
const userId = entity.userId;
const historyId = await this.historyService.start(entity,triggerType);
const historyId = await this.historyService.start(entity, triggerType);
const userIsAdmin = await this.userService.isAdmin(userId);
const user: UserInfo = {
id: userId,
role: userIsAdmin ? 'admin' : 'user',
role: userIsAdmin ? "admin" : "user"
};
@@ -543,11 +562,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
const taskServiceGetter = this.taskServiceBuilder.create({
userId,
})
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService")
const notificationGetter =await taskServiceGetter.get<INotificationService>("notificationService")
const cnameProxyService =await taskServiceGetter.get<ICnameProxyService>("cnameProxyService")
userId
});
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService");
const notificationGetter = await taskServiceGetter.get<INotificationService>("notificationService");
const cnameProxyService = await taskServiceGetter.get<ICnameProxyService>("cnameProxyService");
const executor = new Executor({
user,
pipeline,
@@ -561,7 +580,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
notificationService: notificationGetter,
fileRootDir: this.certdConfig.fileRootDir,
sysInfo,
serviceGetter:taskServiceGetter
serviceGetter: taskServiceGetter
});
try {
runningTasks.set(historyId, executor);
@@ -579,7 +598,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
}
} catch (e) {
logger.error('执行失败:', e);
logger.error("执行失败:", e);
// throw e;
} finally {
runningTasks.delete(historyId);
@@ -603,7 +622,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
private getTriggerType(triggerId, pipeline) {
let triggerType = 'user';
let triggerType = "user";
if (triggerId != null) {
//如果不是手动触发
//查找trigger
@@ -613,8 +632,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
this.cron.remove(this.buildCronKey(pipeline.id, triggerId));
triggerType = null;
} else {
logger.info('timer trigger:' + found.id, found.title, found.cron);
triggerType = 'timer';
logger.info("timer trigger:" + found.id, found.title, found.cron);
triggerType = "timer";
}
}
return triggerType;
@@ -637,7 +656,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
//修改pipeline状态
const pipelineEntity = new PipelineEntity();
pipelineEntity.id = parseInt(history.pipeline.id);
pipelineEntity.status = history.pipeline.status.status + '';
pipelineEntity.status = history.pipeline.status.result + "";
pipelineEntity.lastHistoryTime = history.pipeline.status.startTime;
await this.update(pipelineEntity);
@@ -661,8 +680,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
async count(param: { userId?: any }) {
const count = await this.repository.count({
where: {
userId: param.userId,
},
userId: param.userId
}
});
return count;
}
@@ -670,12 +689,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
async statusCount(param: { userId?: any } = {}) {
const statusCount = await this.repository
.createQueryBuilder()
.select('status')
.addSelect('count(1)', 'count')
.select("status")
.addSelect("count(1)", "count")
.where({
userId: param.userId,
userId: param.userId
})
.groupBy('status')
.groupBy("status")
.getRawMany();
return statusCount;
}
@@ -685,11 +704,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
select: {
id: true,
title: true,
status: true,
status: true
},
where: {
userId,
},
userId
}
});
await this.fillLastVars(list);
list = list.filter(item => {
@@ -703,16 +722,16 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
async createCountPerDay(param: { days: number } = { days: 7 }) {
const todayEnd = dayjs().endOf('day');
const todayEnd = dayjs().endOf("day");
const result = await this.getRepository()
.createQueryBuilder('main')
.select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期
.addSelect('COUNT(1) AS count')
.createQueryBuilder("main")
.select(`${this.dbAdapter.date("main.createTime")} AS date`) // 将UNIX时间戳转换为日期
.addSelect("COUNT(1) AS count")
.where({
// 0点
createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()),
createTime: MoreThan(todayEnd.add(-param.days, "day").toDate())
})
.groupBy('date')
.groupBy("date")
.getRawMany();
return result;
@@ -729,48 +748,48 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.repository.update(
{
id: In(ids),
userId,
userId
},
{ groupId }
);
}
async batchUpdateTrigger(ids: number[], trigger: any, userId: any){
async batchUpdateTrigger(ids: number[], trigger: any, userId: any) {
const list = await this.find({
where:{
where: {
id: In(ids),
userId
}
})
});
for (const item of list) {
const pipeline = JSON.parse(item.content);
pipeline.triggers = [{
id: nanoid(),
title: '定时触发',
title: "定时触发",
...trigger
}]
await this.doUpdatePipelineJson(item,pipeline)
}];
await this.doUpdatePipelineJson(item, pipeline);
}
}
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any){
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any) {
const list = await this.find({
where:{
where: {
id: In(ids),
userId
}
})
});
for (const item of list) {
const pipeline = JSON.parse(item.content);
pipeline.notifications = [{
id: nanoid(),
title: '通知',
title: "通知",
/**
* type: NotificationType;
* when: NotificationWhen[];
@@ -781,44 +800,44 @@ export class PipelineService extends BaseService<PipelineEntity> {
*/
type: "other",
...notification
}]
await this.doUpdatePipelineJson(item,pipeline)
}];
await this.doUpdatePipelineJson(item, pipeline);
}
}
async batchRerun(ids: number[], userId: any) {
if (!isPlus()){
throw new NeedVIPException("此功能需要升级专业版")
if (!isPlus()) {
throw new NeedVIPException("此功能需要升级专业版");
}
if (!userId || ids.length === 0) {
return;
}
const list = await this.repository.find({
select:{
id:true
select: {
id: true
},
where:{
where: {
id: In(ids),
userId
}
})
});
ids = list.map(item=>item.id)
ids = list.map(item => item.id);
//异步执行
this.startBatchRerun(ids)
this.startBatchRerun(ids);
}
async startBatchRerun(ids: number[]){
async startBatchRerun(ids: number[]) {
//20条一批
const batchSize = 20;
for (let i = 0; i < ids.length; i += batchSize) {
const batchIds = ids.slice(i, i + batchSize);
const batchPromises = batchIds.map(async (id)=>{
await this.run(id,null,"ALL")
const batchPromises = batchIds.map(async (id) => {
await this.run(id, null, "ALL");
});
await Promise.all(batchPromises)
await Promise.all(batchPromises);
}
}
@@ -831,35 +850,132 @@ export class PipelineService extends BaseService<PipelineEntity> {
return await this.repository.find({
select: {
id: true,
title: true,
title: true
},
where: {
id: In(pipelineIds),
userId,
},
userId
}
});
}
private async checkUserStatus(userId: number) {
const userEntity = await this.userService.info(userId);
if(userEntity == null){
throw new Error('用户不存在');
if (userEntity == null) {
throw new Error("用户不存在");
}
if(userEntity.status === 0){
const message = `账户${userId}已被禁用,禁止运行流水线`
throw new Error(message)
if (userEntity.status === 0) {
const message = `账户${userId}已被禁用,禁止运行流水线`;
throw new Error(message);
}
const sysPublic = await this.sysSettingsService.getPublicSettings()
if(isPlus() && sysPublic.userValidTimeEnabled === true){
const sysPublic = await this.sysSettingsService.getPublicSettings();
if (isPlus() && sysPublic.userValidTimeEnabled === true) {
//校验用户有效期是否设置
if(userEntity.validTime!= null && userEntity.validTime > 0){
if(userEntity.validTime < new Date().getTime()){
if (userEntity.validTime != null && userEntity.validTime > 0) {
if (userEntity.validTime < new Date().getTime()) {
//用户已过期
const message = `账户${userId}已过有效期,禁止运行流水线`
throw new Error(message)
const message = `账户${userId}已过有效期,禁止运行流水线`;
throw new Error(message);
}
}
}
}
async createAutoPipeline(req: { domains: string[]; email: string; userId: number ,from:string}) {
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
let pipeline: any = {
title: req.domains[0] + `证书自动申请【${req.from??"OpenAPI"}`,
runnableType: "pipeline",
triggers: [
{
id: nanoid(),
title: "定时触发",
props:{
cron: randomCron,
},
type: "timer"
}
],
notifications: [
{
id: nanoid(),
type: "custom",
when: ["error", "turnToSuccess", "success"],
notificationId: 0,
title: "默认通知",
}
],
stages: [
{
id: nanoid(),
title: "证书申请阶段",
maxTaskCount: 1,
runnableType: "stage",
tasks: [
{
id: nanoid(),
title: "证书申请任务",
runnableType: "task",
steps: [
{
id: nanoid(),
title: "申请证书",
runnableType: "step",
input: {
renewDays: 35,
domains: req.domains,
email: req.email,
"challengeType": "auto",
"sslProvider": "letsencrypt",
"privateKeyType": "rsa_2048",
"certProfile": "classic",
"useProxy": false,
"skipLocalVerify": false,
"maxCheckRetryCount": 20,
"waitDnsDiffuseTime": 30,
"pfxArgs": "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
"successNotify": true
},
strategy: {
runStrategy: 0 // 正常执行
},
type: "CertApply"
}
]
}
]
}
]
};
const bean = new PipelineEntity();
bean.title = pipeline.title;
bean.content = JSON.stringify(pipeline);
bean.userId = req.userId;
bean.status = "none";
bean.type = "cert_auto";
bean.disabled = false
bean.keepHistoryCount = 30
await this.save(bean)
return bean;
}
async getStatus(pipelineId: number) {
const res = await this.repository.findOne({
select: {
status: true
},
where: {
id: pipelineId
}
});
return res?.status;
}
}

View File

@@ -36,4 +36,25 @@ export class SubDomainService extends BaseService<SubDomainEntity> {
return list.map(item=>item.domain);
}
async add(bean: SubDomainEntity) {
const {domain, userId} = bean;
if (!domain) {
throw new Error('域名不能为空');
}
if (!userId) {
throw new Error('用户ID不能为空');
}
const exist = await this.repository.findOne({
where: {
domain,
userId,
},
});
if (exist) {
throw new Error('域名已存在');
}
return await super.add(bean)
}
}

View File

@@ -1,6 +1,6 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { AliyunAccess } from '@certd/plugin-lib';
import { CertInfo } from '@certd/plugin-cert';
import {AliyunAccess, AliyunSslClient} from '@certd/plugin-lib';
import {CertInfo, CertReader} from '@certd/plugin-cert';
import { CertApplyPluginNames} from '@certd/plugin-cert';
@IsTaskPlugin({
name: 'DeployCertToAliyunOSS',
@@ -82,11 +82,27 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
from: [...CertApplyPluginNames,"uploadCertToAliyun"],
},
required: true,
})
cert!: CertInfo;
cert!: CertInfo | string;
@TaskInput({
title: '证书服务接入点',
helper: '不会选就按默认',
value: 'cas.aliyuncs.com',
component: {
name: 'a-select',
options: [
{ value: 'cn-hangzhou', label: '中国大陆' },
{ value: 'southeast-1', label: '新加坡' },
{ value: 'eu-central-1', label: '德国(法兰克福)' },
],
},
required: true,
})
casRegion!: string;
@TaskInput({
title: 'Access授权',
@@ -103,12 +119,42 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云OSS');
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
await this.getAliyunCertId(access)
this.logger.info(`bucket: ${this.bucket}, region: ${this.region}, domainName: ${this.domainName}`);
const client = await this.getClient(access);
await this.doRequest(client, {});
this.logger.info('部署完成');
}
async getAliyunCertId(access: AliyunAccess) {
let certId: any = this.cert;
let certName: any = this.appendTimeSuffix("certd");
if (typeof this.cert === "object") {
let endpoint = `cas.${this.casRegion}.aliyuncs.com`;
if (this.casRegion === "cn-hangzhou"){
endpoint = "cas.aliyuncs.com";
}
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: endpoint
});
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt));
certId = await sslClient.uploadCert({
name: certName,
cert: this.cert
});
this.logger.info("上传证书成功", certId, certName);
}
return {
certId,
certName
};
}
async getClient(access: AliyunAccess) {
// @ts-ignore
const OSS = await import('ali-oss');
@@ -129,13 +175,24 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
cname: '',
comp: 'add',
});
let certStr = ""
if (typeof this.cert === "object" ){
certStr = `
<PrivateKey>${this.cert.key}</PrivateKey>
<Certificate>${this.cert.crt}</Certificate>
`
}else{
certStr = `<CertId>${this.cert}-${this.casRegion}</CertId>`
}
const xml = `
<BucketCnameConfiguration>
<Cname>
<Domain>${this.domainName}</Domain>
<CertificateConfiguration>
<PrivateKey>${this.cert.key}</PrivateKey>
<Certificate>${this.cert.crt}</Certificate>
${certStr}
<Force>true</Force>
</CertificateConfiguration>
</Cname>
</BucketCnameConfiguration>`;

View File

@@ -23,7 +23,7 @@ const regionDict = [
@IsTaskPlugin({
name: 'uploadCertToAliyun',
title: '阿里云-上传证书到阿里云',
title: '阿里云-上传证书到阿里云CAS',
icon: 'svg:icon-aliyun',
group: pluginGroups.aliyun.key,
desc: '上传证书到阿里云数字证书管理服务CAS注意不会部署到任何应用上如果不想在阿里云上同一份证书上传多次可以把此任务作为前置任务其他阿里云任务证书那一项选择此任务的输出',

View File

@@ -1,3 +1,4 @@
export * from './host-shell-execute/index.js';
export * from './upload-to-host/index.js';
export * from './copy-to-local/index.js'
export * from './plugin-upload-to-oss.js'

View File

@@ -0,0 +1,274 @@
import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
import {CertInfo} from "@certd/plugin-cert";
import {ossClientFactory} from "@certd/plugin-lib";
import {utils} from "@certd/basic";
@IsTaskPlugin({
name: 'UploadCertToOss',
title: '上传证书到对象存储OSS',
icon: 'ion:cloud-upload-outline',
desc: '支持阿里云OSS、腾讯云COS、七牛云KODO、S3、MinIO、FTP、SFTP',
group: pluginGroups.host.key,
showRunStrategy:false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class UploadCertToOssPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [":cert:"],
},
required: true,
})
cert!: CertInfo;
@TaskInput({
title: 'OSS类型',
component: {
name: 'a-select',
vModel:"value",
options: [
{ label: "阿里云OSS", value: "alioss" },
{ label: "腾讯云COS", value: "tencentcos" },
{ label: "七牛OSS", value: "qiniuoss" },
{ label: "S3/Minio", value: "s3" },
{ label: "SFTP", value: "sftp" },
{ label: "FTP", value: "ftp" },
]
},
required: true,
})
uploaderType!: string;
@TaskInput({
title: 'OSS授权',
component: {
name: 'access-selector',
},
required: true,
mergeScript: `
return {
component: {
type: ctx.compute(({form})=>{
return form.uploaderType;
})
}
}
`,
})
accessId!: string;
@TaskInput({
title: '证书格式',
helper: '要部署的证书格式支持pem、pfx、der、jks',
component: {
name: 'a-select',
options: [
{ value: 'pem', label: 'pemcrtNginx等大部分应用' },
{ value: 'pfx', label: 'pfx一般用于IIS' },
{ value: 'der', label: 'der一般用于Apache' },
{ value: 'jks', label: 'jks一般用于JAVA应用' },
{ value: 'one', label: '证书私钥一体crt+key简单合并为一个pem文件' },
],
},
required: true,
})
certType!: string;
@TaskInput({
title: '证书保存路径',
helper: '路径要包含证书文件名,例如:/tmp/cert.pem',
component: {
placeholder: '/root/deploy/nginx/full_chain.pem',
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === 'pem';
})
}
`,
required: true,
rules: [{ type: 'filepath' }],
})
crtPath!: string;
@TaskInput({
title: '私钥保存路径',
helper: '路径要包含私钥文件名,例如:/tmp/cert.key',
component: {
placeholder: '/root/deploy/nginx/cert.key',
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === 'pem';
})
}
`,
required: true,
rules: [{ type: 'filepath' }],
})
keyPath!: string;
@TaskInput({
title: '中间证书保存路径',
helper: '路径要包含文件名,一般情况传上面两个文件即可,极少数情况需要这个中间证书',
component: {
placeholder: '/root/deploy/nginx/intermediate.pem',
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === 'pem';
})
}
`,
rules: [{ type: 'filepath' }],
})
icPath!: string;
@TaskInput({
title: 'PFX证书保存路径',
helper: '路径要包含证书文件名例如D:\\iis\\cert.pfx',
component: {
placeholder: 'D:\\iis\\cert.pfx',
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === 'pfx';
})
}
`,
required: true,
rules: [{ type: 'filepath' }],
})
pfxPath!: string;
@TaskInput({
title: 'DER证书保存路径',
helper: '路径要包含证书文件名,例如:/tmp/cert.der',
component: {
placeholder: '/root/deploy/apache/cert.der',
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === 'der';
})
}
`,
required: true,
rules: [{ type: 'filepath' }],
})
derPath!: string;
@TaskInput({
title: 'jks证书保存路径',
helper: '路径要包含证书文件名,例如:/tmp/cert.jks',
component: {
placeholder: '/root/deploy/java_app/cert.jks',
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === 'jks';
})
}
`,
required: true,
rules: [{ type: 'filepath' }],
})
jksPath!: string;
@TaskInput({
title: '一体证书保存路径',
helper: '路径要包含证书文件名,例如:/tmp/crt_key.pem',
component: {
placeholder: '/app/crt_key.pem',
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === 'one';
})
}
`,
required: true,
rules: [{ type: 'filepath' }],
})
onePath!: string;
async onInstance() {}
async execute(): Promise<void> {
const { accessId } = this;
let { crtPath, keyPath, icPath, pfxPath, derPath, jksPath, onePath } = this;
if (!accessId) {
throw new Error('OSS授权配置不能为空');
}
const uploaderType = this.uploaderType
const uploaderAccess = this.accessId
const httpUploaderContext = {
accessService: this.ctx.accessService,
logger: this.logger,
utils,
};
const access = await this.getAccess(uploaderAccess);
this.logger.info("上传方式", uploaderType);
const httpUploader = await ossClientFactory.createOssClientByType(uploaderType, {
access,
rootDir: "",
ctx: httpUploaderContext,
});
this.logger.info('准备上传文件到OSS');
if (crtPath) {
await httpUploader.upload(crtPath, Buffer.from(this.cert.crt))
this.logger.info(`上传证书:${crtPath}`);
}
if (keyPath) {
await httpUploader.upload(keyPath, Buffer.from(this.cert.key))
this.logger.info(`上传私钥:${keyPath}`);
}
if (icPath) {
await httpUploader.upload(icPath, Buffer.from(this.cert.ic))
this.logger.info(`上传中间证书:${icPath}`);
}
if (pfxPath) {
await httpUploader.upload(pfxPath, Buffer.from(this.cert.pfx, "base64"))
this.logger.info(`上传PFX证书${pfxPath}`);
}
if (derPath) {
await httpUploader.upload(derPath, Buffer.from(this.cert.der, "base64"))
this.logger.info(`上传DER证书${derPath}`);
}
if (this.jksPath) {
await httpUploader.upload(jksPath,Buffer.from(this.cert.jks, "base64"))
this.logger.info(`上传jks证书${jksPath}`);
}
if (onePath) {
await httpUploader.upload(onePath, Buffer.from(this.cert.one))
this.logger.info(`上传一体证书:${onePath}`);
}
this.logger.info('上传文件成功');
}
}
new UploadCertToOssPlugin();

View File

@@ -47,26 +47,35 @@ export class QywxNotification extends BaseNotification {
if (!this.webhook) {
throw new Error('webhook地址不能为空');
}
/**
*
* "msgtype": "text",
* "text": {
* "content": "hello world"
* }
* }
*/
const color = body.errorMessage?'red':'green';
await this.http.request({
url: this.webhook,
method: 'POST',
data: {
msgtype: 'markdown',
markdown: {
content: `<font color='${color}'>${body.title}</font>\n\n\n${body.content}\n\n[查看详情](${body.url})`,
msgtype: 'text',
text: {
content: `${body.title}\n${body.content}\n查看详情: ${body.url}`,
mentioned_list: this.mentionedList,
mentioned_mobile_list: this.mentionedMobileList,
},
},
});
//Markdown 模式不支持@
// const color = body.errorMessage?'red':'green';
// await this.http.request({
// url: this.webhook,
// method: 'POST',
// data: {
// msgtype: 'markdown',
// markdown: {
// content: `<font color='${color}'>${body.title}</font>\n\n\n${body.content}\n\n[查看详情](${body.url})`,
// mentioned_list: this.mentionedList,
// mentioned_mobile_list: this.mentionedMobileList,
// },
// },
// });
}
}

View File

@@ -2,3 +2,4 @@ export * from './plugin-restart.js';
export * from './plugin-script.js';
export * from './plugin-wait.js';
export * from './plugin-db-backup.js';
export * from './plugin-deploy-to-mail.js';

View File

@@ -0,0 +1,186 @@
import {AbstractTaskPlugin, FileItem, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
import {CertInfo, CertReader} from "@certd/plugin-cert";
import dayjs from "dayjs";
@IsTaskPlugin({
name: 'DeployCertToMailPlugin',
title: '邮件发送证书',
icon: 'ion:mail-outline',
desc: '通过邮件发送证书',
group: pluginGroups.other.key,
showRunStrategy:false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class DeployCertToMailPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [":cert:"],
},
required: true,
})
cert!: CertInfo;
@TaskInput({
title: '证书压缩文件',
helper: '请选择前置任务输出的域名证书压缩文件',
component: {
name: 'output-selector',
from: [":certZip:"],
},
required: true,
})
certZip!: FileItem;
@TaskInput({
title: '接收邮箱',
component: {
name: 'EmailSelector',
vModel: 'value',
mode:"tags",
},
required: true,
})
email!: string[];
/**
* title:
* title: 邮件标题
* helper: |-
* 请输入邮件标题否则将使用默认标题
* 域名:${certDomains}
* component:
* name: a-input
* required: false
* template:
* title: 邮件模版
* helper: |-
* 请输入模版内容否则将使用默认模版
* 域名:${certDomains}
* value: |-
* 尊敬的用户你好:
* 以下是域名(${certDomains})证书文件
* component:
* name: a-textarea
* autosize:
* minRows: 6
* maxRows: 10
* required: false
*/
@TaskInput({
title: '邮件标题',
component: {
name: 'a-input',
vModel: 'value',
placeholder:`证书申请成功【$\{mainDomain}】`,
},
helper: '请输入邮件标题否则将使用默认标题\n模板变量主域名=$\{mainDomain}、全部域名=$\{domains}、过期时间=$\{expiresTime}、备注=$\{remark}、证书PEM=$\{crt}、证书私钥=$\{key}、中间证书/CA证书=$\{ic}',
required: false,
})
title!: string;
@TaskInput({
title: '邮件模版',
component: {
name: 'a-textarea',
vModel: 'value',
autosize: {
minRows: 6,
maxRows: 10,
},
placeholder: `
<div>
<p>证书申请成功</p>
<p>域名:$\{domains}</p>
<p>证书有效期:$\{expiresTime}</p>
<p>备注:$\{remark}</p>
</div>
`,
},
helper: `请输入模版内容否则将使用默认模版,模板变量同上`,
required: false,
})
template!: string;
@TaskInput({
title: '备注',
component: {
name: 'a-input',
vModel: 'value',
},
required: false,
})
remark!: string;
async onInstance() {}
async execute(): Promise<void> {
this.logger.info(`开始发送邮件`);
const certReader = new CertReader(this.cert)
const mainDomain = certReader.getMainDomain();
const domains = certReader.getAllDomains().join(',');
const data = {
mainDomain,
domains,
expiresTime: dayjs(certReader.expires).format("YYYY-MM-DD HH:mm:ss"),
remark:this.remark ||"",
crt: this.cert.crt,
key: this.cert.key,
ic: this.cert.ic
}
let title = `证书申请成功【${mainDomain}`;
let html = `
<div>
<p>证书申请成功</p>
<p>域名:${domains}</p>
<p>证书有效期:${data.expiresTime}</p>
<p>备注:${this.remark||""}</p>
</div>
`;
if (this.title) {
const compile = this.compile(this.title);
title = compile(data);
}
if (this.template) {
const compile = this.compile(this.template);
html = compile(data);
}
const file = this.certZip
if (!file) {
throw new Error('证书压缩文件还未生成,重新运行证书任务');
}
await this.ctx.emailService.send({
subject:title,
html: html,
receivers: this.email,
attachments: [
{
filename: file.filename,
path: file.path,
},
],
})
}
compile(templateString:string) {
return new Function('data', ` with(data || {}) {
return \`${templateString}\`;
}
`);
}
}
new DeployCertToMailPlugin();

View File

@@ -5,7 +5,7 @@ import { TencentAccess } from "@certd/plugin-lib";
name: 'DeployCertToTencentEO',
title: '腾讯云-部署到腾讯云EO',
icon: 'svg:icon-tencentcloud',
desc: '腾讯云边缘安全加速平台EO,必须配置上传证书到腾讯云任务',
desc: '腾讯云边缘安全加速平台EdgeOne(EO),必须配置上传证书到腾讯云任务',
group: pluginGroups.tencent.key,
default: {
strategy: {

185
pnpm-lock.yaml generated
View File

@@ -4,10 +4,16 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
'@certd/ui-server': link:packages/ui/certd-server
importers:
.:
dependencies:
'@certd/ui-server':
specifier: link:packages/ui/certd-server
version: link:packages/ui/certd-server
axios:
specifier: ^1.7.7
version: 1.9.0(debug@4.4.1)
@@ -46,7 +52,7 @@ importers:
packages/core/acme-client:
dependencies:
'@certd/basic':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../basic
'@peculiar/x509':
specifier: ^1.11.0
@@ -207,11 +213,11 @@ importers:
packages/core/pipeline:
dependencies:
'@certd/basic':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../basic
'@certd/plus-core':
specifier: ^1.36.1
version: 1.36.1
specifier: ^1.36.5
version: link:../../pro/plus-core
dayjs:
specifier: ^1.11.7
version: 1.11.13
@@ -415,7 +421,7 @@ importers:
packages/libs/lib-k8s:
dependencies:
'@certd/basic':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/basic
'@kubernetes/client-node':
specifier: 0.21.0
@@ -455,17 +461,17 @@ importers:
packages/libs/lib-server:
dependencies:
'@certd/acme-client':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/pipeline
'@certd/plus-core':
specifier: ^1.36.1
version: 1.36.1
specifier: ^1.36.5
version: link:../../pro/plus-core
'@midwayjs/cache':
specifier: ~3.14.0
version: 3.14.0
@@ -607,16 +613,16 @@ importers:
packages/plugins/plugin-cert:
dependencies:
'@certd/acme-client':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../plugin-lib
'@google-cloud/publicca':
specifier: ^1.3.0
@@ -698,10 +704,10 @@ importers:
specifier: ^3.787.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/basic':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/pipeline
'@kubernetes/client-node':
specifier: 0.21.0
@@ -789,19 +795,19 @@ importers:
packages/pro/commercial-core:
dependencies:
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../core/basic
'@certd/lib-server':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../libs/lib-server
'@certd/pipeline':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../core/pipeline
'@certd/plugin-plus':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../plugin-plus
'@certd/plus-core':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../plus-core
'@midwayjs/core':
specifier: ~3.20.3
@@ -886,22 +892,22 @@ importers:
specifier: ^1.0.2
version: 1.0.3
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../core/basic
'@certd/lib-k8s':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../libs/lib-k8s
'@certd/pipeline':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../plus-core
ali-oss:
specifier: ^6.21.0
@@ -1004,7 +1010,7 @@ importers:
packages/pro/plus-core:
dependencies:
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.5
version: link:../../core/basic
dayjs:
specifier: ^1.11.7
@@ -1294,10 +1300,10 @@ importers:
version: 0.1.3(zod@3.24.4)
devDependencies:
'@certd/lib-iframe':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../libs/lib-iframe
'@certd/pipeline':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/pipeline
'@rollup/plugin-commonjs':
specifier: ^25.0.7
@@ -1480,47 +1486,47 @@ importers:
specifier: ^3.705.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/acme-client':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/basic
'@certd/commercial-core':
specifier: ^1.36.1
version: 1.36.1(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))
specifier: ^1.36.5
version: link:../../pro/commercial-core
'@certd/cv4pve-api-javascript':
specifier: ^8.4.1
version: 8.4.1
'@certd/jdcloud':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../libs/lib-jdcloud
'@certd/lib-huawei':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../libs/lib-huawei
'@certd/lib-k8s':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../libs/lib-k8s
'@certd/lib-server':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../libs/lib-server
'@certd/midway-flyway-js':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../libs/midway-flyway-js
'@certd/pipeline':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.36.2
specifier: ^1.36.5
version: link:../../plugins/plugin-lib
'@certd/plugin-plus':
specifier: ^1.36.1
version: 1.36.1(encoding@0.1.13)
specifier: ^1.36.5
version: link:../../pro/plugin-plus
'@certd/plus-core':
specifier: ^1.36.1
version: 1.36.1
specifier: ^1.36.5
version: link:../../pro/plus-core
'@huaweicloud/huaweicloud-sdk-cdn':
specifier: ^3.1.120
version: 3.1.149
@@ -2760,18 +2766,9 @@ packages:
'@better-scroll/zoom@2.5.1':
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
'@certd/commercial-core@1.36.1':
resolution: {integrity: sha512-CpZ7K7s0JoXJ8MD3FyeryKnA62fvX3DKVQ7XkSSomaqgH/a8DgP4QrJGrt2ytIYtu2SzJvJsBfy14UhVM6174Q==}
'@certd/cv4pve-api-javascript@8.4.1':
resolution: {integrity: sha512-jxlRieJmCA0Z9LnwX6Ra6ZekRGJEu8o8RGYoKU0Jjkhc9jm6ChEbVyfE7Iw49/hlpA+2yaHdAXb46au/afCISg==}
'@certd/plugin-plus@1.36.1':
resolution: {integrity: sha512-Bd3tFwn/jDrpSWqUH9488YxFHnEIGI15NFoqQaLv58L+Ycm7mRbyTUC/S3xpiUkfhDI6wqvKL8SOtdpgo0IEzA==}
'@certd/plus-core@1.36.1':
resolution: {integrity: sha512-TiRpqWCC7RGJxmVaDkSkMAFYe2zehRqZFKmNcfG8DZJTgKpQhd98HvXlyXCJD5qUtPmyvFoffTD1Q8bGqN84Vw==}
'@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
@@ -15447,84 +15444,12 @@ snapshots:
dependencies:
'@better-scroll/core': 2.5.1
'@certd/commercial-core@1.36.1(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))':
dependencies:
'@certd/basic': link:packages/core/basic
'@certd/lib-server': link:packages/libs/lib-server
'@certd/pipeline': link:packages/core/pipeline
'@certd/plugin-plus': 1.36.1(encoding@0.1.13)
'@certd/plus-core': 1.36.1
'@midwayjs/core': 3.20.4
'@midwayjs/koa': 3.20.5
'@midwayjs/logger': 3.4.2
'@midwayjs/typeorm': 3.20.4
alipay-sdk: 4.14.0
dayjs: 1.11.13
typeorm: 0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))
wechatpay-node-v3: 2.2.1
transitivePeerDependencies:
- '@google-cloud/spanner'
- '@sap/hana-client'
- babel-plugin-macros
- better-sqlite3
- encoding
- hdb-pool
- ioredis
- mongodb
- mssql
- mysql2
- oracledb
- pg
- pg-native
- pg-query-stream
- proxy-agent
- redis
- reflect-metadata
- sql.js
- sqlite3
- supports-color
- ts-node
- typeorm-aurora-data-api-driver
'@certd/cv4pve-api-javascript@8.4.1':
dependencies:
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
'@certd/plugin-plus@1.36.1(encoding@0.1.13)':
dependencies:
'@alicloud/pop-core': 1.8.0
'@baiducloud/sdk': 1.0.3
'@certd/basic': link:packages/core/basic
'@certd/lib-k8s': link:packages/libs/lib-k8s
'@certd/pipeline': link:packages/core/pipeline
'@certd/plugin-cert': link:packages/plugins/plugin-cert
'@certd/plugin-lib': link:packages/plugins/plugin-lib
'@certd/plus-core': 1.36.1
ali-oss: 6.23.0
baidu-aip-sdk: 4.16.16
basic-ftp: 5.0.5
cos-nodejs-sdk-v5: 2.14.7
crypto-js: 4.2.0
dayjs: 1.11.13
form-data: 4.0.2
https-proxy-agent: 7.0.6
js-yaml: 4.1.0
jsencrypt: 3.3.2
jsrsasign: 11.1.0
qiniu: 7.14.0
tencentcloud-sdk-nodejs: 4.1.37(encoding@0.1.13)
transitivePeerDependencies:
- encoding
- proxy-agent
- supports-color
'@certd/plus-core@1.36.1':
dependencies:
'@certd/basic': link:packages/core/basic
dayjs: 1.11.13
'@colors/colors@1.5.0':
optional: true
@@ -21599,13 +21524,13 @@ snapshots:
resolve: 1.22.10
semver: 6.3.1
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8):
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8):
dependencies:
eslint: 7.32.0
prettier: 2.8.8
prettier-linter-helpers: 1.0.0
optionalDependencies:
eslint-config-prettier: 8.10.0(eslint@8.57.0)
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8):
dependencies:
@@ -24313,7 +24238,7 @@ snapshots:
eslint: 7.32.0
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-plugin-node: 11.1.0(eslint@7.32.0)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8)
execa: 5.1.1
inquirer: 7.3.3
json5: 2.2.3