mirror of
https://github.com/certd/certd.git
synced 2026-04-05 23:40:55 +08:00
Compare commits
87 Commits
v2-dev-aut
...
v1.36.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e03a2e553 | ||
|
|
fda7c6f67a | ||
|
|
fabb7982ff | ||
|
|
cbf206be60 | ||
|
|
aa0c282205 | ||
|
|
9759365329 | ||
|
|
e3738f6422 | ||
|
|
9746d169f9 | ||
|
|
2e6d03ff00 | ||
|
|
f7b7d3d65e | ||
|
|
4037cf11aa | ||
|
|
02aeb321ce | ||
|
|
0012619257 | ||
|
|
6f3ade0d94 | ||
|
|
cf572f328a | ||
|
|
d1ce36038c | ||
|
|
b382351c7b | ||
|
|
4e5e862f58 | ||
|
|
ab84835362 | ||
|
|
41ce8489dc | ||
|
|
ef3faf5832 | ||
|
|
edf089ec9e | ||
|
|
0ae9a3605c | ||
|
|
7f9c4e52ac | ||
|
|
35947f96a8 | ||
|
|
b0f91f1eea | ||
|
|
13dfca1749 | ||
|
|
9d9cd8a362 | ||
|
|
5e5c41fda5 | ||
|
|
3ebdc52b3e | ||
|
|
af54f48cec | ||
|
|
8656059151 | ||
|
|
a6d38f2458 | ||
|
|
085bdf5cfa | ||
|
|
6883bcacee | ||
|
|
2ecc6e0368 | ||
|
|
8fb5ca2fe1 | ||
|
|
e40345095f | ||
|
|
ffc0c7bb7b | ||
|
|
58fadc8928 | ||
|
|
d96a607c04 | ||
|
|
2ea2c8c05f | ||
|
|
b15f514018 | ||
|
|
05a33a0ec9 | ||
|
|
747d266742 | ||
|
|
522d30545b | ||
|
|
6135a44a8d | ||
|
|
7c7d646792 | ||
|
|
4a36fd2ec3 | ||
|
|
b1bcc287cb | ||
|
|
6f5868a9d7 | ||
|
|
75863441f4 | ||
|
|
9763cb00e5 | ||
|
|
521599ef39 | ||
|
|
1921a64f4b | ||
|
|
6b73f5d555 | ||
|
|
e0408f30ba | ||
|
|
dca44fa093 | ||
|
|
bbacb76581 | ||
|
|
1da8617a53 | ||
|
|
e5967f7e9d | ||
|
|
65d84f9e9d | ||
|
|
93e9498b41 | ||
|
|
95332d5db9 | ||
|
|
9864792bbf | ||
|
|
ca9d1eed7a | ||
|
|
38e867c917 | ||
|
|
1ff6daaa27 | ||
|
|
3ee1dbb8a5 | ||
|
|
b4571d5c98 | ||
|
|
29d49d72f9 | ||
|
|
81de0fc7e4 | ||
|
|
9d5d266d2a | ||
|
|
b97935299f | ||
|
|
32a7ea1c16 | ||
|
|
9fd95e6a1e | ||
|
|
61ba83c775 | ||
|
|
6369fed5fc | ||
|
|
42f4d1477d | ||
|
|
609ac9c9a2 | ||
|
|
79f2367472 | ||
|
|
dfc9362084 | ||
|
|
487b469603 | ||
|
|
19e1df1e5d | ||
|
|
fc55010888 | ||
|
|
902d246d1a | ||
|
|
dbf69bcd98 |
77
CHANGELOG.md
77
CHANGELOG.md
@@ -3,6 +3,83 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复阿里云发送短信验证码失败的bug ([2e6d03f](https://github.com/certd/certd/commit/2e6d03ff001f521f57368e7a62b97ed7b122e8d0))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云部分插件优化 [@nicheng-he](https://github.com/nicheng-he) ([e3738f6](https://github.com/certd/certd/commit/e3738f6422270d75ec414c15a343248cc4cad6e1))
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 上传到阿里云cas,证书前缀无效的bug ([b382351](https://github.com/certd/certd/commit/b382351c7b91ec10e1f61d94bec5aad075207ec8))
|
||||
* 修复自定义插件onlyAdmin报错的bug ([4e5e862](https://github.com/certd/certd/commit/4e5e862f5834ad180e4428959c272d444a6f78ab))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署到k8s,tke,ack忽悠证书校验 ([ab84835](https://github.com/certd/certd/commit/ab848353621869464a2c9a45fdb5e28d998b8a58))
|
||||
* 首页增加更新日志按钮 ([41ce848](https://github.com/certd/certd/commit/41ce8489dc2f03a705dfa3fbb357769defb56c60))
|
||||
* 增加版本过低提示 ([d1ce360](https://github.com/certd/certd/commit/d1ce36038cab72b5dc1b320d0a708c261ffbdacb))
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 安全更新,备份数据库插件仅限管理员运行 ([13dfca1](https://github.com/certd/certd/commit/13dfca1749275526c82465a17c482b607c820fdd))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1 +1 @@
|
||||
10:51
|
||||
00:18
|
||||
|
||||
@@ -3,6 +3,73 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 上传到阿里云cas,证书前缀无效的bug ([b382351](https://github.com/certd/certd/commit/b382351c7b91ec10e1f61d94bec5aad075207ec8))
|
||||
* 修复自定义插件onlyAdmin报错的bug ([4e5e862](https://github.com/certd/certd/commit/4e5e862f5834ad180e4428959c272d444a6f78ab))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署到k8s,tke,ack忽悠证书校验 ([ab84835](https://github.com/certd/certd/commit/ab848353621869464a2c9a45fdb5e28d998b8a58))
|
||||
* 首页增加更新日志按钮 ([41ce848](https://github.com/certd/certd/commit/41ce8489dc2f03a705dfa3fbb357769defb56c60))
|
||||
* 增加版本过低提示 ([d1ce360](https://github.com/certd/certd/commit/d1ce36038cab72b5dc1b320d0a708c261ffbdacb))
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 安全更新,备份数据库插件仅限管理员运行 ([13dfca1](https://github.com/certd/certd/commit/13dfca1749275526c82465a17c482b607c820fdd))
|
||||
|
||||
## [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
|
||||
|
||||
BIN
docs/guide/use/tencent/images/opaque.png
Normal file
BIN
docs/guide/use/tencent/images/opaque.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/guide/use/tencent/images/tcpssl.png
Normal file
BIN
docs/guide/use/tencent/images/tcpssl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -8,8 +8,11 @@
|
||||

|
||||
|
||||
|
||||
|
||||
## 如何避免收到腾讯云证书过期邮件
|
||||
|
||||
> 新版本已经自动将证书设置为免提醒,certd上传的证书后续都不会再提醒了。
|
||||
|
||||
腾讯云在证书有效期还剩28天时会发送过期通知邮件
|
||||
您可以通过配置“腾讯云过期证书删除”任务来避免收到此类邮件。
|
||||
|
||||
@@ -18,4 +21,17 @@
|
||||
注意点:
|
||||
> 1. 选择腾讯云授权,需授权`服务角色SSL_QCSLinkedRoleInReplaceLoadCertificate`权限
|
||||
> 2. `1.26.14`版本之前Certd创建的证书流水线默认是到期前20天才更新证书,需要将之前创建的证书申请任务的更新天数修改为35天,保证删除之前就已经替换掉即将过期证书
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
## TKE service 的 TCP_SSL Opaque类型证书授权
|
||||
|
||||
部署证书到腾讯云TKE,如果报以下错误:
|
||||
`is forbidden: User "xxxxxx-xxxxx" cannot get resource "secrets" in API group "" in the namespace "default"'`
|
||||
则需要单独从授权管理侧再授权子用户的权限
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.36.5"
|
||||
"version": "1.36.13"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/publishlab/node-acme-client/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.36.12](https://github.com/publishlab/node-acme-client/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.36.11](https://github.com/publishlab/node-acme-client/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1 +1 @@
|
||||
10:46
|
||||
23:37
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -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.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复自定义插件onlyAdmin报错的bug ([4e5e862](https://github.com/certd/certd/commit/4e5e862f5834ad180e4428959c272d444a6f78ab))
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 安全更新,备份数据库插件仅限管理员运行 ([13dfca1](https://github.com/certd/certd/commit/13dfca1749275526c82465a17c482b607c820fdd))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@certd/plus-core": "^1.36.13",
|
||||
"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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -352,6 +352,7 @@ export class Executor {
|
||||
pipeline: this.pipeline,
|
||||
runtime: this.runtime,
|
||||
step,
|
||||
define: cloneDeep(define),
|
||||
lastStatus,
|
||||
http,
|
||||
download,
|
||||
|
||||
@@ -59,6 +59,7 @@ export type PluginDefine = Registrable & {
|
||||
form: any;
|
||||
};
|
||||
};
|
||||
onlyAdmin?: boolean;
|
||||
needPlus?: boolean;
|
||||
showRunStrategy?: boolean;
|
||||
pluginType?: string; //类型
|
||||
@@ -85,6 +86,7 @@ export type TaskInstanceContext = {
|
||||
runtime: RunHistory;
|
||||
//步骤定义
|
||||
step: Step;
|
||||
define: PluginDefine;
|
||||
//日志
|
||||
logger: ILogger;
|
||||
//当前步骤输入参数跟上一次执行比较是否有变化
|
||||
@@ -162,6 +164,12 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||
this.registerSecret(cert.key);
|
||||
this.registerSecret(cert.one);
|
||||
}
|
||||
|
||||
if (this.ctx?.define?.onlyAdmin) {
|
||||
if (!this.isAdmin()) {
|
||||
throw new Error("只有管理员才能运行此任务");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getAccess<T = any>(accessId: string | number, isCommon = false) {
|
||||
|
||||
@@ -30,4 +30,5 @@ export const pluginGroups = {
|
||||
qiniu: new PluginGroup("qiniu", "七牛云", 5, "svg:icon-qiniuyun"),
|
||||
aws: new PluginGroup("aws", "亚马逊云", 6, "svg:icon-aws"),
|
||||
other: new PluginGroup("other", "其他", 10, "clarity:plugin-line"),
|
||||
admin: new PluginGroup("admin", "管理", 11, "ion:settings-outline"),
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export type EmailSend = {
|
||||
subject: string;
|
||||
content: string;
|
||||
receivers: string[];
|
||||
content?: string;
|
||||
attachments?: any[];
|
||||
html?: string;
|
||||
};
|
||||
|
||||
export interface IEmailService {
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
@@ -61,5 +61,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
|
||||
"gitHead": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署到k8s,tke,ack忽悠证书校验 ([ab84835](https://github.com/certd/certd/commit/ab848353621869464a2c9a45fdb5e28d998b8a58))
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -32,5 +32,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "c2a95a13fe6edf05ea0f72f5f7c76f9eea3ab0bd"
|
||||
"gitHead": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './lib/k8s.client.js';
|
||||
export * from "./lib/k8s.client.js";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CoreV1Api, KubeConfig, NetworkingV1Api, V1Ingress, V1Secret } from '@kubernetes/client-node';
|
||||
import dns from 'dns';
|
||||
import { ILogger } from '@certd/basic';
|
||||
import _ from 'lodash-es';
|
||||
import { CoreV1Api, KubeConfig, NetworkingV1Api, V1Ingress, V1Secret } from "@kubernetes/client-node";
|
||||
import dns from "dns";
|
||||
import { ILogger } from "@certd/basic";
|
||||
import _ from "lodash-es";
|
||||
|
||||
export type K8sClientOpts = {
|
||||
kubeConfigStr: string;
|
||||
@@ -9,6 +9,7 @@ export type K8sClientOpts = {
|
||||
//{ [domain]:{ip:'xxx.xx.xxx'} }
|
||||
//暂时没用
|
||||
lookup?: any;
|
||||
skipTLSVerify?: boolean;
|
||||
};
|
||||
export class K8sClient {
|
||||
kubeconfig!: KubeConfig;
|
||||
@@ -16,10 +17,12 @@ export class K8sClient {
|
||||
lookup!: (hostnameReq: any, options: any, callback: any) => void;
|
||||
client!: CoreV1Api;
|
||||
logger: ILogger;
|
||||
skipTLSVerify?: boolean;
|
||||
constructor(opts: K8sClientOpts) {
|
||||
this.kubeConfigStr = opts.kubeConfigStr;
|
||||
this.logger = opts.logger;
|
||||
this.setLookup(opts.lookup);
|
||||
this.skipTLSVerify = opts.skipTLSVerify;
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -27,6 +30,18 @@ export class K8sClient {
|
||||
const kubeconfig = new KubeConfig();
|
||||
kubeconfig.loadFromString(this.kubeConfigStr);
|
||||
this.kubeconfig = kubeconfig;
|
||||
|
||||
try {
|
||||
if (this.skipTLSVerify == true) {
|
||||
for (const cluster of kubeconfig.getClusters()) {
|
||||
// @ts-ignore
|
||||
cluster["skipTLSVerify"] = this.skipTLSVerify;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.warn("skipTLSVerify error", e);
|
||||
}
|
||||
|
||||
this.client = kubeconfig.makeApiClient(CoreV1Api);
|
||||
|
||||
// const reqOpts = { kubeconfig, request: {} } as any;
|
||||
@@ -47,9 +62,9 @@ export class K8sClient {
|
||||
return;
|
||||
}
|
||||
this.lookup = (hostnameReq: any, options: any, callback: any) => {
|
||||
this.logger.info('custom lookup', hostnameReq, localRecords);
|
||||
this.logger.info("custom lookup", hostnameReq, localRecords);
|
||||
if (localRecords[hostnameReq]) {
|
||||
this.logger.info('local record', hostnameReq, localRecords[hostnameReq]);
|
||||
this.logger.info("local record", hostnameReq, localRecords[hostnameReq]);
|
||||
callback(null, localRecords[hostnameReq].ip, 4);
|
||||
} else {
|
||||
dns.lookup(hostnameReq, options, callback);
|
||||
@@ -63,7 +78,7 @@ export class K8sClient {
|
||||
* @returns secretsList
|
||||
*/
|
||||
async getSecrets(opts: { namespace: string }) {
|
||||
const namespace = opts.namespace || 'default';
|
||||
const namespace = opts.namespace || "default";
|
||||
return await this.client.listNamespacedSecret(namespace);
|
||||
}
|
||||
|
||||
@@ -73,9 +88,9 @@ export class K8sClient {
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async createSecret(opts: { namespace: string; body: V1Secret }) {
|
||||
const namespace = opts.namespace || 'default';
|
||||
const namespace = opts.namespace || "default";
|
||||
const created = await this.client.createNamespacedSecret(namespace, opts.body);
|
||||
this.logger.info('new secrets:', opts.body);
|
||||
this.logger.info("new secrets:", opts.body);
|
||||
return created.body;
|
||||
}
|
||||
|
||||
@@ -89,24 +104,24 @@ export class K8sClient {
|
||||
// }
|
||||
|
||||
async patchSecret(opts: { namespace: string; secretName: string; body: V1Secret }) {
|
||||
const namespace = opts.namespace || 'default';
|
||||
const namespace = opts.namespace || "default";
|
||||
const secretName = opts.secretName;
|
||||
if (secretName == null) {
|
||||
throw new Error('secretName 不能为空');
|
||||
throw new Error("secretName 不能为空");
|
||||
}
|
||||
this.logger.info('patch secret:', secretName, namespace);
|
||||
this.logger.info("patch secret:", secretName, namespace);
|
||||
const oldSecret = await this.client.readNamespacedSecret(secretName, namespace);
|
||||
const newSecret = _.merge(oldSecret.body, opts.body);
|
||||
const res = await this.client.replaceNamespacedSecret(secretName, namespace, newSecret);
|
||||
this.logger.info('secret updated');
|
||||
this.logger.info("secret updated");
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async getIngressList(opts: { namespace: string }) {
|
||||
const namespace = opts.namespace || 'default';
|
||||
const namespace = opts.namespace || "default";
|
||||
const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
|
||||
const res = await client.listNamespacedIngress(namespace);
|
||||
this.logger.info('ingress list get:', res.body);
|
||||
this.logger.info("ingress list get:", res.body);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
@@ -122,17 +137,17 @@ export class K8sClient {
|
||||
// }
|
||||
|
||||
async patchIngress(opts: { namespace: string; ingressName: string; body: V1Ingress }) {
|
||||
const namespace = opts.namespace || 'default';
|
||||
const namespace = opts.namespace || "default";
|
||||
const ingressName = opts.ingressName;
|
||||
if (!ingressName) {
|
||||
throw new Error('ingressName 不能为空');
|
||||
throw new Error("ingressName 不能为空");
|
||||
}
|
||||
this.logger.info('patch ingress:', ingressName, namespace);
|
||||
this.logger.info("patch ingress:", ingressName, namespace);
|
||||
const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
|
||||
const oldIngress = await client.readNamespacedIngress(ingressName, namespace);
|
||||
const newIngress = _.merge(oldIngress.body, opts.body);
|
||||
const res = await client.replaceNamespacedIngress(ingressName, namespace, newIngress);
|
||||
this.logger.info('ingress patched', opts.body);
|
||||
this.logger.info("ingress patched", opts.body);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@certd/basic": "^1.36.13",
|
||||
"@certd/pipeline": "^1.36.13",
|
||||
"@certd/plus-core": "^1.36.13",
|
||||
"@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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,5 +107,17 @@ export const Constants = {
|
||||
code: 20012,
|
||||
message: '证书还未生成',
|
||||
},
|
||||
openCertApplying: {
|
||||
code: 20013,
|
||||
message: '证书正在申请中,请稍后重新获取',
|
||||
},
|
||||
openDomainNoVerifier:{
|
||||
code: 20014,
|
||||
message: '域名校验方式未配置',
|
||||
},
|
||||
openEmailNotFound: {
|
||||
code: 20021,
|
||||
message: '用户邮箱还未配置',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,39 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@certd/basic": "^1.36.13",
|
||||
"@certd/pipeline": "^1.36.13",
|
||||
"@certd/plugin-lib": "^1.36.13",
|
||||
"@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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复阿里云发送短信验证码失败的bug ([2e6d03f](https://github.com/certd/certd/commit/2e6d03ff001f521f57368e7a62b97ed7b122e8d0))
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@certd/pipeline": "^1.36.13",
|
||||
"@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": "02aeb321ced3a6ac3e24e77964c497f3cc64f6a8"
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class AliyunClient {
|
||||
}
|
||||
|
||||
checkRet(ret: any) {
|
||||
if (ret.Code != null) {
|
||||
if (ret.Code != null && ret.Code !== "OK" && ret.Message !== "OK") {
|
||||
throw new Error("执行失败:" + ret.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
});
|
||||
|
||||
1
packages/ui/certd-client/.gitignore
vendored
1
packages/ui/certd-client/.gitignore
vendored
@@ -9,3 +9,4 @@ yarn.lock
|
||||
/.idea/
|
||||
yarn-error.log
|
||||
vite-profile.cpuprofile
|
||||
!build
|
||||
@@ -3,6 +3,58 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云部分插件优化 [@nicheng-he](https://github.com/nicheng-he) ([e3738f6](https://github.com/certd/certd/commit/e3738f6422270d75ec414c15a343248cc4cad6e1))
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 上传到阿里云cas,证书前缀无效的bug ([b382351](https://github.com/certd/certd/commit/b382351c7b91ec10e1f61d94bec5aad075207ec8))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 首页增加更新日志按钮 ([41ce848](https://github.com/certd/certd/commit/41ce8489dc2f03a705dfa3fbb357769defb56c60))
|
||||
* 增加版本过低提示 ([d1ce360](https://github.com/certd/certd/commit/d1ce36038cab72b5dc1b320d0a708c261ffbdacb))
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-client
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@certd/pipeline": "^1.36.13",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="remote-auto-complete">
|
||||
<div class="flex flex-row">
|
||||
<a-auto-complete class="remote-auto-complete-input" :filter-option="filterOption" :options="optionsRef" :value="value" v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)">
|
||||
</a-auto-complete>
|
||||
<div class="ml-5">
|
||||
<fs-button :loading="loading" title="刷新选项" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="helper" :class="{ error: hasError }">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
||||
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
|
||||
import { PluginDefine } from "@certd/pipeline";
|
||||
|
||||
defineOptions({
|
||||
name: "RemoteAutoComplete",
|
||||
});
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
watches: string[];
|
||||
} & ComponentPropsType
|
||||
>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:value": any;
|
||||
}>();
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => {
|
||||
return {};
|
||||
});
|
||||
const getScope: any = inject("get:scope", () => {
|
||||
return {};
|
||||
});
|
||||
const getPluginType: any = inject("get:plugin:type", () => {
|
||||
return "plugin";
|
||||
});
|
||||
|
||||
const optionsRef = ref([]);
|
||||
const message = ref("");
|
||||
const hasError = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const getOptions = async () => {
|
||||
if (loading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getCurrentPluginDefine) {
|
||||
return;
|
||||
}
|
||||
|
||||
const define: PluginDefine = getCurrentPluginDefine()?.value;
|
||||
if (!define) {
|
||||
return;
|
||||
}
|
||||
const pluginType = getPluginType();
|
||||
const { form } = getScope();
|
||||
const input = (pluginType === "plugin" ? form?.input : form) || {};
|
||||
|
||||
for (let key in define.input) {
|
||||
const inWatches = props.watches.includes(key);
|
||||
const inputDefine = define.input[key];
|
||||
if (inWatches && inputDefine.required) {
|
||||
const value = input[key];
|
||||
if (value == null || value === "") {
|
||||
console.log("remote-select required", key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.value = "";
|
||||
hasError.value = false;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await doRequest(
|
||||
{
|
||||
type: pluginType,
|
||||
typeName: form.type,
|
||||
action: props.action,
|
||||
input,
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
onError(err: any) {
|
||||
hasError.value = true;
|
||||
message.value = `获取选项出错:${err.message}`;
|
||||
},
|
||||
showErrorNotify: false,
|
||||
}
|
||||
);
|
||||
const list = res?.list || res || [];
|
||||
if (list.length > 0) {
|
||||
message.value = "获取数据成功,请从下拉框中选择";
|
||||
}
|
||||
optionsRef.value = list;
|
||||
|
||||
return res;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 || String(option.value).toLowerCase().indexOf(input.toLowerCase());
|
||||
};
|
||||
|
||||
async function onClick() {
|
||||
if (optionsRef.value?.length === 0) {
|
||||
await refreshOptions();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshOptions() {
|
||||
await getOptions();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => {
|
||||
const pluginType = getPluginType();
|
||||
const { form, key } = getScope();
|
||||
const input = (pluginType === "plugin" ? form?.input : form) || {};
|
||||
const watches = {};
|
||||
for (const key of props.watches) {
|
||||
//@ts-ignore
|
||||
watches[key] = input[key];
|
||||
}
|
||||
return {
|
||||
form: watches,
|
||||
key,
|
||||
};
|
||||
},
|
||||
async (value, oldValue) => {
|
||||
const { form } = value;
|
||||
const oldForm: any = oldValue?.form;
|
||||
let changed = oldForm == null || optionsRef.value.length == 0;
|
||||
for (const key of props.watches) {
|
||||
//@ts-ignore
|
||||
if (oldForm && form[key] != oldForm[key]) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
await getOptions();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -1,4 +1,5 @@
|
||||
import SynologyIdDeviceGetter from "./synology/device-id-getter.vue";
|
||||
import RemoteAutoComplete from "./common/remote-auto-complete.vue";
|
||||
import RemoteSelect from "./common/remote-select.vue";
|
||||
import RemoteInput from "./common/remote-input.vue";
|
||||
import CertDomainsGetter from "./common/cert-domains-getter.vue";
|
||||
@@ -21,6 +22,7 @@ export default {
|
||||
app.component("ApiTest", ApiTest);
|
||||
|
||||
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
|
||||
app.component("RemoteAutoComplete", RemoteAutoComplete);
|
||||
app.component("RemoteSelect", RemoteSelect);
|
||||
app.component("RemoteInput", RemoteInput);
|
||||
app.component("CertDomainsGetter", CertDomainsGetter);
|
||||
|
||||
@@ -78,6 +78,7 @@ export default {
|
||||
runCount: "Run Count",
|
||||
expiringCerts: "Soon-to-Expire Certificates",
|
||||
supportedTasks: "Overview of Supported Deployment Tasks",
|
||||
changeLog: "Change Log",
|
||||
},
|
||||
steps: {
|
||||
createPipeline: "Create Certificate Pipeline",
|
||||
@@ -449,7 +450,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",
|
||||
|
||||
@@ -84,6 +84,7 @@ export default {
|
||||
runCount: "运行次数",
|
||||
expiringCerts: "最快到期证书",
|
||||
supportedTasks: "已支持的部署任务总览",
|
||||
changeLog: "更新日志",
|
||||
},
|
||||
steps: {
|
||||
createPipeline: "创建证书流水线",
|
||||
@@ -415,7 +416,7 @@ export default {
|
||||
is_present_no: "否",
|
||||
basicInfo: "基础信息",
|
||||
titlea: "名称",
|
||||
disabled: "是否禁用",
|
||||
disabled: "禁用",
|
||||
ordera: "排序",
|
||||
supportBuy: "支持购买",
|
||||
intro: "介绍",
|
||||
@@ -455,7 +456,7 @@ export default {
|
||||
batchDeleteConfirm: "确定要批量删除这{count}条记录吗",
|
||||
selectRecordFirst: "请先勾选记录",
|
||||
subdomainHosted: "托管的子域名",
|
||||
subdomainHelpText: "如果您不理解什么是子域托管,可以参考文档",
|
||||
subdomainHelpText: "如果您不理解什么是子域托管,请不要随意设置,可能导致证书无法申请,可以参考文档",
|
||||
subdomainManagement: "子域管理",
|
||||
isDisabled: "是否禁用",
|
||||
enabled: "启用",
|
||||
|
||||
@@ -46,6 +46,10 @@ export interface SettingState {
|
||||
price3: number;
|
||||
tooltip?: string;
|
||||
};
|
||||
app?: {
|
||||
minVersion?: string;
|
||||
minVersionTip?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,6 +111,10 @@ export const useSettingStore = defineStore({
|
||||
price: 399,
|
||||
price3: 899,
|
||||
},
|
||||
app: {
|
||||
minVersion: "",
|
||||
minVersionTip: "",
|
||||
},
|
||||
},
|
||||
}),
|
||||
getters: {
|
||||
|
||||
@@ -112,7 +112,8 @@ function menuIcon(menu: MenuRecordRaw) {
|
||||
|
||||
.vben-normal-menu__name,
|
||||
.vben-normal-menu__icon {
|
||||
@apply text-primary-foreground font-semibold;
|
||||
//@apply text-primary-foreground font-semibold;
|
||||
@apply font-semibold;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -26,43 +26,45 @@
|
||||
</template>
|
||||
<div class="flex-col h-100 w-100 overflow-hidden">
|
||||
<a-tabs v-model:active-key="pluginGroupActive" tab-position="left" class="flex-1 overflow-hidden">
|
||||
<a-tab-pane v-for="group of computedPluginGroups" :key="group.key" class="scroll-y">
|
||||
<template #tab>
|
||||
<div class="cd-step-form-tab-label">
|
||||
<fs-icon :icon="group.icon" class="mr-2" />
|
||||
<div>{{ group.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-row v-if="!group.plugins || group.plugins.length === 0" :gutter="10">
|
||||
<a-col class="flex-o">
|
||||
<div class="flex-o m-10">没有找到插件</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-else :gutter="10">
|
||||
<a-col v-for="item of group.plugins" :key="item.key" class="step-plugin w-full md:w-[50%]">
|
||||
<a-card
|
||||
hoverable
|
||||
:class="{ current: item.name === currentStep.type }"
|
||||
@click="stepTypeSelected(item)"
|
||||
@dblclick="
|
||||
stepTypeSelected(item);
|
||||
stepTypeSave();
|
||||
"
|
||||
>
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<fs-icon class="plugin-icon" :icon="item.icon || 'clarity:plugin-line'"></fs-icon>
|
||||
<span class="title" :title="item.title">{{ item.title }}</span>
|
||||
<vip-button v-if="item.needPlus" mode="icon" />
|
||||
</template>
|
||||
<template #description>
|
||||
<span :title="item.desc" v-html="transformDesc(item.desc)"></span>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
<template v-for="group of computedPluginGroups" :key="group.key">
|
||||
<a-tab-pane v-if="(group.key === 'admin' && userStore.isAdmin) || group.key !== 'admin'" :key="group.key" class="scroll-y">
|
||||
<template #tab>
|
||||
<div class="cd-step-form-tab-label">
|
||||
<fs-icon :icon="group.icon" class="mr-2" />
|
||||
<div>{{ group.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-row v-if="!group.plugins || group.plugins.length === 0" :gutter="10">
|
||||
<a-col class="flex-o">
|
||||
<div class="flex-o m-10">没有找到插件</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-else :gutter="10">
|
||||
<a-col v-for="item of group.plugins" :key="item.key" class="step-plugin w-full md:w-[50%]">
|
||||
<a-card
|
||||
hoverable
|
||||
:class="{ current: item.name === currentStep.type }"
|
||||
@click="stepTypeSelected(item)"
|
||||
@dblclick="
|
||||
stepTypeSelected(item);
|
||||
stepTypeSave();
|
||||
"
|
||||
>
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<fs-icon class="plugin-icon" :icon="item.icon || 'clarity:plugin-line'"></fs-icon>
|
||||
<span class="title" :title="item.title">{{ item.title }}</span>
|
||||
<vip-button v-if="item.needPlus" mode="icon" />
|
||||
</template>
|
||||
<template #description>
|
||||
<span :title="item.desc" v-html="transformDesc(item.desc)"></span>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -124,6 +126,7 @@ import { useReference } from "/@/use/use-refrence";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { mitter } from "/@/utils/util.mitt";
|
||||
import { utils } from "/@/utils";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
|
||||
defineOptions({
|
||||
name: "PiStepForm",
|
||||
@@ -138,7 +141,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(["update"]);
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
|
||||
const userStore = useUserStore();
|
||||
function transformDesc(desc: string = "") {
|
||||
return utils.transformLink(desc);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
@@ -298,10 +298,11 @@ import { FsIcon } from "@fast-crud/fast-crud";
|
||||
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 { eachSteps, findStep, eachStages } from "../utils";
|
||||
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,23 @@ 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);
|
||||
eachStages(pipeline.value.stages, item => {
|
||||
item.status = null;
|
||||
});
|
||||
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 +420,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 +443,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 +490,7 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
const detail: PipelineDetail = await props.options.getPipelineDetail({ pipelineId: value });
|
||||
currentPipeline.value = _.merge(
|
||||
currentPipeline.value = merge(
|
||||
{
|
||||
title: "新管道流程",
|
||||
stages: [],
|
||||
@@ -540,7 +553,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 +573,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 +679,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 +811,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 +825,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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
</a-tag>
|
||||
</a-badge>
|
||||
<a-divider type="vertical" />
|
||||
<a-tag color="blue" class="flex-inline pointer mr-0" @click="openChangeLogUrl()">
|
||||
{{ t("certd.dashboard.changeLog") }}
|
||||
</a-tag>
|
||||
<a-divider type="vertical" />
|
||||
<vip-button mode="nav" style="font-size: 12px"></vip-button>
|
||||
</template>
|
||||
<template v-if="settingsStore.isComm">
|
||||
@@ -152,32 +156,48 @@ import * as api from "./api";
|
||||
import { useI18n } from "/src/locales";
|
||||
const { t } = useI18n();
|
||||
import { usePluginStore } from "/@/store/plugin";
|
||||
import { notification } from "ant-design-vue";
|
||||
defineOptions({
|
||||
name: "DashboardUser",
|
||||
});
|
||||
|
||||
const version = ref(import.meta.env.VITE_APP_VERSION);
|
||||
const latestVersion = ref("");
|
||||
const hasNewVersion = computed(() => {
|
||||
if (!latestVersion.value) {
|
||||
|
||||
function isNewVersion(version: string, latestVersion: string) {
|
||||
if (!latestVersion) {
|
||||
return false;
|
||||
}
|
||||
if (latestVersion.value === version.value) {
|
||||
if (latestVersion === version) {
|
||||
return false;
|
||||
}
|
||||
//分段比较
|
||||
const current = version.value.split(".");
|
||||
const latest = latestVersion.value.split(".");
|
||||
const current = version.split(".");
|
||||
const latest = latestVersion.split(".");
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
if (parseInt(latest[i]) < parseInt(current[i])) {
|
||||
return false;
|
||||
if (parseInt(latest[i]) > parseInt(current[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasNewVersion = computed(() => {
|
||||
return isNewVersion(version.value, latestVersion.value);
|
||||
});
|
||||
async function loadLatestVersion() {
|
||||
latestVersion.value = await api.GetLatestVersion();
|
||||
console.log("latestVersion", latestVersion.value);
|
||||
|
||||
const minVersion = settingsStore.productInfo?.app?.minVersion;
|
||||
if (minVersion) {
|
||||
//
|
||||
if (isNewVersion(version.value, minVersion)) {
|
||||
notification.error({
|
||||
message: settingsStore.productInfo?.app?.minVersionTip ?? "版本过低,为了您的数据安全,请尽快升级",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const settingStore = useSettingStore();
|
||||
const siteInfo: Ref<SiteInfo> = computed(() => {
|
||||
@@ -214,6 +234,7 @@ function transformStatusCount() {
|
||||
{ name: "error", label: "失败" },
|
||||
{ name: "canceled", label: "已取消" },
|
||||
{ name: null, label: "未执行" },
|
||||
{ name: "skip", label: "跳过" },
|
||||
];
|
||||
const result = [];
|
||||
for (const item of sorted) {
|
||||
@@ -254,6 +275,9 @@ onMounted(async () => {
|
||||
function openUpgradeUrl() {
|
||||
window.open("https://certd.docmirror.cn/guide/install/upgrade.html");
|
||||
}
|
||||
function openChangeLogUrl() {
|
||||
window.open("https://certd.docmirror.cn/guide/changelogs/CHANGELOG.html");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -3,6 +3,66 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云部分插件优化 [@nicheng-he](https://github.com/nicheng-he) ([e3738f6](https://github.com/certd/certd/commit/e3738f6422270d75ec414c15a343248cc4cad6e1))
|
||||
|
||||
## [1.36.12](https://github.com/certd/certd/compare/v1.36.11...v1.36.12) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 上传到阿里云cas,证书前缀无效的bug ([b382351](https://github.com/certd/certd/commit/b382351c7b91ec10e1f61d94bec5aad075207ec8))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署到k8s,tke,ack忽悠证书校验 ([ab84835](https://github.com/certd/certd/commit/ab848353621869464a2c9a45fdb5e28d998b8a58))
|
||||
|
||||
## [1.36.11](https://github.com/certd/certd/compare/v1.36.10...v1.36.11) (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 安全更新,备份数据库插件仅限管理员运行 ([13dfca1](https://github.com/certd/certd/commit/13dfca1749275526c82465a17c482b607c820fdd))
|
||||
|
||||
## [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
|
||||
|
||||
20
packages/ui/certd-server/db/migration-mysql/v10027__auto.sql
Normal file
20
packages/ui/certd-server/db/migration-mysql/v10027__auto.sql
Normal 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`);
|
||||
|
||||
20
packages/ui/certd-server/db/migration-pg/v10027__auto.sql
Normal file
20
packages/ui/certd-server/db/migration-pg/v10027__auto.sql
Normal 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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.36.5",
|
||||
"version": "1.36.13",
|
||||
"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.13",
|
||||
"@certd/basic": "^1.36.13",
|
||||
"@certd/commercial-core": "^1.36.13",
|
||||
"@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.13",
|
||||
"@certd/lib-huawei": "^1.36.13",
|
||||
"@certd/lib-k8s": "^1.36.13",
|
||||
"@certd/lib-server": "^1.36.13",
|
||||
"@certd/midway-flyway-js": "^1.36.13",
|
||||
"@certd/pipeline": "^1.36.13",
|
||||
"@certd/plugin-cert": "^1.36.13",
|
||||
"@certd/plugin-lib": "^1.36.13",
|
||||
"@certd/plugin-plus": "^1.36.13",
|
||||
"@certd/plus-core": "^1.36.13",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||
"@koa/cors": "^5.0.0",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {EmailService} from '../../../modules/basic/service/email-service.js';
|
||||
import {http, HttpRequestConfig, logger, mergeUtils, utils} from '@certd/basic';
|
||||
import {NotificationService} from '../../../modules/pipeline/service/notification-service.js';
|
||||
import {TaskServiceBuilder} from "../../../modules/pipeline/service/getter/task-service-getter.js";
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/pi/handle')
|
||||
@@ -103,6 +104,7 @@ export class HandleController extends BaseController {
|
||||
const taskCtx: TaskInstanceContext = {
|
||||
pipeline: undefined,
|
||||
step: undefined,
|
||||
define: cloneDeep( pluginDefine.define),
|
||||
lastStatus: undefined,
|
||||
http,
|
||||
download,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,3 +30,4 @@ export * from './plugin-github/index.js'
|
||||
export * from './plugin-namesilo/index.js'
|
||||
export * from './plugin-proxmox/index.js'
|
||||
export * from './plugin-wangsu/index.js'
|
||||
export * from './plugin-admin/index.js'
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './plugin-restart.js';
|
||||
export * from './plugin-script.js';
|
||||
export * from './plugin-db-backup.js';
|
||||
@@ -14,14 +14,15 @@ const defaultFilePrefix = 'db_backup';
|
||||
name: 'DBBackupPlugin',
|
||||
title: '数据库备份',
|
||||
icon: 'lucide:database-backup',
|
||||
desc: '仅支持备份SQLite数据库',
|
||||
group: pluginGroups.other.key,
|
||||
desc: '【仅管理员可用】仅支持备份SQLite数据库',
|
||||
group: pluginGroups.admin.key,
|
||||
showRunStrategy: true,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.AlwaysRun,
|
||||
},
|
||||
},
|
||||
onlyAdmin:true,
|
||||
needPlus: true,
|
||||
})
|
||||
export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||
@@ -157,6 +158,11 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||
}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
|
||||
if (!this.isAdmin()) {
|
||||
throw new Error('只有管理员才能运行此任务');
|
||||
}
|
||||
|
||||
this.logger.info('开始备份数据库');
|
||||
|
||||
let dbPath = process.env.certd_typeorm_dataSource_default_database;
|
||||
@@ -1,12 +1,13 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy } from '@certd/pipeline';
|
||||
import { httpsServer } from '../../../modules/auto/https/server.js';
|
||||
import { httpsServer } from '../../modules/auto/https/server.js';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'RestartCertd',
|
||||
title: '重启 Certd',
|
||||
icon: 'mdi:restart',
|
||||
desc: '【仅管理员可用】 重启 certd的https服务,用于更新 Certd 的 ssl 证书',
|
||||
group: pluginGroups.other.key,
|
||||
group: pluginGroups.admin.key,
|
||||
onlyAdmin:true,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
@@ -11,8 +11,9 @@ export type CustomScriptContext = {
|
||||
title: '自定义js脚本',
|
||||
icon: 'ri:javascript-line',
|
||||
desc: '【仅管理员】运行自定义js脚本执行',
|
||||
group: pluginGroups.other.key,
|
||||
group: pluginGroups.admin.key,
|
||||
showRunStrategy: true,
|
||||
onlyAdmin: true,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
@@ -26,7 +26,7 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
helper: "请选择证书申请任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames]
|
||||
from: [...CertApplyPluginNames, 'uploadCertToAliyun']
|
||||
},
|
||||
required: true
|
||||
})
|
||||
@@ -58,8 +58,8 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
name: "a-select",
|
||||
options: [
|
||||
{ value: "cas.aliyuncs.com", label: "中国大陆" },
|
||||
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
|
||||
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" }
|
||||
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" }
|
||||
// { value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" }
|
||||
]
|
||||
},
|
||||
required: true
|
||||
|
||||
@@ -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',
|
||||
@@ -59,6 +59,15 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: 'Bucket',
|
||||
helper: '存储桶名称',
|
||||
component: {
|
||||
name: 'remote-auto-complete',
|
||||
vModel: 'value',
|
||||
type: 'plugin',
|
||||
action: 'onGetBucketList',
|
||||
search: false,
|
||||
pager: false,
|
||||
watches: ['accessId', 'region']
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
bucket!: string;
|
||||
@@ -82,11 +91,28 @@ 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: 'cn-hangzhou',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
options: [
|
||||
{ value: 'cn-hangzhou', label: '中国大陆' },
|
||||
{ value: 'southeast-1', label: '新加坡' },
|
||||
{ value: 'eu-central-1', label: '德国(法兰克福)' },
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
order: -99,
|
||||
})
|
||||
casRegion!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: 'Access授权',
|
||||
@@ -96,6 +122,7 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
|
||||
type: 'aliyun',
|
||||
},
|
||||
required: true,
|
||||
order: -98,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@@ -103,12 +130,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');
|
||||
@@ -124,18 +181,47 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
|
||||
});
|
||||
}
|
||||
|
||||
async onGetBucketList(data: any) {
|
||||
console.log('data', data)
|
||||
|
||||
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
|
||||
const client = await this.getClient(access);
|
||||
|
||||
let res;
|
||||
const buckets = []
|
||||
do{
|
||||
const requestData = {'marker': res?.nextMarker || null, 'max-keys': 1000};
|
||||
res = await client.listBuckets(requestData)
|
||||
buckets.push(...(res?.buckets || []))
|
||||
} while (!!res?.nextMarker)
|
||||
return buckets
|
||||
.filter(bucket => bucket?.region === this.region)
|
||||
.map(bucket => ({label: `${bucket.name}<${bucket.region}>`, value: bucket.name}));
|
||||
}
|
||||
|
||||
async doRequest(client: any, params: any) {
|
||||
params = client._bucketRequestParams('POST', this.bucket, {
|
||||
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>`;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
|
||||
const regionDict = [
|
||||
{ value: 'cn-hangzhou', endpoint: 'cas.aliyuncs.com', label: 'cn-hangzhou-中国大陆' },
|
||||
{ value: 'eu-central-1', endpoint: 'cas.eu-central-1.aliyuncs.com', label: 'eu-central-1-德国(法兰克福)' },
|
||||
{ value: 'ap-southeast-1', endpoint: 'cas.ap-southeast-1.aliyuncs.com', label: 'ap-southeast-1-新加坡' },
|
||||
{ value: 'ap-southeast-1', endpoint: 'cas.ap-southeast-1.aliyuncs.com', label: 'ap-southeast-1-新加坡(国际版选这个)' },
|
||||
{ value: 'ap-southeast-3', endpoint: 'cas.ap-southeast-3.aliyuncs.com', label: 'ap-southeast-3-马来西亚(吉隆坡)' },
|
||||
{ value: 'ap-southeast-5', endpoint: 'cas.ap-southeast-5.aliyuncs.com', label: 'ap-southeast-5-印度尼西亚(雅加达)' },
|
||||
{ value: 'cn-hongkong', endpoint: 'cas.cn-hongkong.aliyuncs.com', label: 'cn-hongkong-中国香港' },
|
||||
@@ -23,7 +23,7 @@ const regionDict = [
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'uploadCertToAliyun',
|
||||
title: '阿里云-上传证书到阿里云',
|
||||
title: '阿里云-上传证书到阿里云CAS',
|
||||
icon: 'svg:icon-aliyun',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '上传证书到阿里云数字证书管理服务(CAS),注意:不会部署到任何应用上;如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出',
|
||||
@@ -82,7 +82,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
async onInstance() {}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info('开始部署证书到阿里云cdn');
|
||||
this.logger.info('开始上传证书到阿里云证书管理CAS');
|
||||
const access: AliyunAccess = await this.getAccess(this.accessId);
|
||||
|
||||
let endpoint = '';
|
||||
@@ -97,7 +97,12 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
logger: this.logger,
|
||||
endpoint,
|
||||
});
|
||||
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
|
||||
let certName = ""
|
||||
if (this.name){
|
||||
certName = this.appendTimeSuffix(this.name)
|
||||
}else{
|
||||
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
|
||||
}
|
||||
this.aliyunCertId = await client.uploadCert({
|
||||
name: certName,
|
||||
cert: this.cert,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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: 'pem(crt),Nginx等大部分应用' },
|
||||
{ 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();
|
||||
@@ -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,
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
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';
|
||||
|
||||
@@ -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();
|
||||
@@ -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: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user