mirror of
https://github.com/certd/certd.git
synced 2026-04-14 20:40:53 +08:00
Compare commits
98 Commits
v1.36.16
...
v2-dev-add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bdde68ece | ||
|
|
50f92f55e2 | ||
|
|
370db62bf0 | ||
|
|
65f34f1d31 | ||
|
|
00a3908abb | ||
|
|
32034d590a | ||
|
|
3635fb3910 | ||
|
|
d2ecfe5491 | ||
|
|
1f759dce5b | ||
|
|
ae41c6038b | ||
|
|
f41f7eb2ad | ||
|
|
d04f383161 | ||
|
|
cb989d7489 | ||
|
|
b5cba19d26 | ||
|
|
b7271d7a46 | ||
|
|
521083a309 | ||
|
|
6d35325601 | ||
|
|
3c65f37d84 | ||
|
|
d75dd058d6 | ||
|
|
40475e02ec | ||
|
|
f6ea9c1300 | ||
|
|
902359f24e | ||
|
|
bb4d5f1e93 | ||
|
|
1dec3f000e | ||
|
|
6d89814795 | ||
|
|
f339bc9f7f | ||
|
|
bb80bc0c07 | ||
|
|
96677ff8bf | ||
|
|
c7b6a6df79 | ||
|
|
8bb7e8bfb2 | ||
|
|
02ab343e22 | ||
|
|
4d875a18de | ||
|
|
cff2336923 | ||
|
|
0e96bfdfa3 | ||
|
|
a24ef48ad1 | ||
|
|
fe9c4f3391 | ||
|
|
6cbb0739f8 | ||
|
|
79ebabfcfb | ||
|
|
0c8e3262fe | ||
|
|
c24a040c19 | ||
|
|
4f39cb8dfa | ||
|
|
cdd2816642 | ||
|
|
27b6dfa4d2 | ||
|
|
204cbd0209 | ||
|
|
b7980aad5a | ||
|
|
e175729e2c | ||
|
|
c26ad4c807 | ||
|
|
4372adc703 | ||
|
|
8a0c2b9b13 | ||
|
|
4443a1c030 | ||
|
|
39a02235cf | ||
|
|
db89561480 | ||
|
|
a4cbb11693 | ||
|
|
1ceeacc526 | ||
|
|
b59052cc43 | ||
|
|
44019e1042 | ||
|
|
fd0e1da4a2 | ||
|
|
f6c67b475a | ||
|
|
ea18a5ad15 | ||
|
|
4d0cd3f497 | ||
|
|
7dbdeaebe0 | ||
|
|
2085bcceb6 | ||
|
|
c09c962cb6 | ||
|
|
9108459ae4 | ||
|
|
992bac0b1f | ||
|
|
ebd6917a1d | ||
|
|
3e079e3b80 | ||
|
|
2ca20be197 | ||
|
|
17f23f3751 | ||
|
|
8e3d699856 | ||
|
|
f1a168fa53 | ||
|
|
3575113655 | ||
|
|
9feb9d04b3 | ||
|
|
5419b1439a | ||
|
|
e4489343fe | ||
|
|
d9f4a5793d | ||
|
|
70fcdc9ebb | ||
|
|
78e7a81638 | ||
|
|
58e82d5dbd | ||
|
|
06d15be43a | ||
|
|
e1e7011853 | ||
|
|
eff7645035 | ||
|
|
eb75e52278 | ||
|
|
15e6148272 | ||
|
|
ccd448a675 | ||
|
|
db54c019ad | ||
|
|
b762b4d72c | ||
|
|
2f8faa839d | ||
|
|
831c325c63 | ||
|
|
f4f73078c5 | ||
|
|
f7d43ad5af | ||
|
|
a77c777980 | ||
|
|
a34db7449e | ||
|
|
0283bd2f97 | ||
|
|
a8de2f8ae7 | ||
|
|
d5dee75df3 | ||
|
|
79cb5c0631 | ||
|
|
7d9901540f |
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Plugin Apply
|
name: Plugin Apply
|
||||||
about: 请求支持新部署插件
|
about: 部署插件申请支持
|
||||||
title: "[Plugin] "
|
title: "[Plugin] "
|
||||||
labels: feature
|
labels: feature
|
||||||
---
|
---
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: DNS Provider Apply
|
name: DNS Provider Apply
|
||||||
about: 请求支持新的域名提供商
|
about: 域名提供商申请支持
|
||||||
title: "[DNS] "
|
title: "[DNS] "
|
||||||
labels: feature
|
labels: feature
|
||||||
---
|
---
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
about: 报告一个错误或问题
|
about: 错误或问题报告
|
||||||
title: "[BUG] "
|
title: "[BUG] "
|
||||||
labels: bug
|
labels: bug
|
||||||
---
|
---
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Feature Request
|
name: Feature Request
|
||||||
about: 新需求、新特性
|
about: 新需求、新特性申请支持
|
||||||
title: "[Feature] "
|
title: "[Feature] "
|
||||||
labels: feature
|
labels: feature
|
||||||
---
|
---
|
||||||
@@ -9,7 +9,8 @@ labels: feature
|
|||||||
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
|
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
|
||||||
|
|
||||||
|
|
||||||
# 新需求申请
|
# 新特性申请
|
||||||
|
>注意:这里仅供如果是要申请新的部署插件,请提交插件申请
|
||||||
|
|
||||||
## 1. 需求描述,需求背景
|
## 1. 需求描述,需求背景
|
||||||
`请在此处简要描述你所遇到的问题,必要时请贴出相关截图辅助理解`
|
`请在此处简要描述你所遇到的问题,必要时请贴出相关截图辅助理解`
|
||||||
60
CHANGELOG.md
60
CHANGELOG.md
@@ -3,6 +3,66 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 前置任务输出不存在时输出警告提示 ([b59052c](https://github.com/certd/certd/commit/b59052cc43b7b070fabd8b8e914e4c2a5e0ad61c))
|
||||||
|
* 修复批量流水线执行时日志显示错乱的问题 ([4372adc](https://github.com/certd/certd/commit/4372adc703b9a4c785664054ab2a533626d815a8))
|
||||||
|
* 修复远程数据选择无法过滤的bug ([6cbb073](https://github.com/certd/certd/commit/6cbb0739f8428d51b0712f718fe4d236cc087cf9))
|
||||||
|
* 修复mysql下购买套餐加量包无效的bug ([c26ad4c](https://github.com/certd/certd/commit/c26ad4c8075f0606d45b8da13915737968d6191a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 创建证书时支持选择通知时机 ([0e96bfd](https://github.com/certd/certd/commit/0e96bfdfa377824d204e72923d1176408ae6b300))
|
||||||
|
* 创建k8s secret 时设置type为tls ([79ebabf](https://github.com/certd/certd/commit/79ebabfcfb9e5a534049c84f5f1a642b357fc856))
|
||||||
|
* 去掉宝塔url后面的斜杠 ([8a0c2b9](https://github.com/certd/certd/commit/8a0c2b9b13628da750c25757e0cb8ed3038775ba))
|
||||||
|
* 商业版隐藏文档相关链接 ([4443a1c](https://github.com/certd/certd/commit/4443a1c0308fa6b95a05efd73d15d24b65d641c9))
|
||||||
|
* 商业版隐藏文档相关链接 ([db89561](https://github.com/certd/certd/commit/db8956148083bc4f988226ccf719940d08158a27))
|
||||||
|
* 增加健康检查探针 /health/liveliness 和 /health/readiness ([44019e1](https://github.com/certd/certd/commit/44019e104289fedd32a867db00e9c6cb71b389cc))
|
||||||
|
* 支持根据id更新证书(证书Id不变接口),不过该接口为白名单功能,普通腾讯云账户无法使用 ([fe9c4f3](https://github.com/certd/certd/commit/fe9c4f3391ff07c01dd9a252225f69a129c39050))
|
||||||
|
* 支持godaddy ([b7980aa](https://github.com/certd/certd/commit/b7980aad5ab50f58662eaddf5d84aa82876a98eb))
|
||||||
|
* 支持ssl.com证书颁发机构 ([27b6dfa](https://github.com/certd/certd/commit/27b6dfa4d2ab3bddd284c3a34511a72e1a513a4c))
|
||||||
|
* 子域名托管说明 ([39a0223](https://github.com/certd/certd/commit/39a02235cf4416bb5bd1acd3831241efeaa2f602))
|
||||||
|
* ssh 增加超时断开连接,默认10分钟超时 ([c24a040](https://github.com/certd/certd/commit/c24a040c19cacafc79228d7a7649af93837d94a1))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 更新我爱云CDN域名地址,和部分目录结构 [@tyjsjxh](https://github.com/tyjsjxh) ([#514](https://github.com/certd/certd/issues/514)) ([78e7a81](https://github.com/certd/certd/commit/78e7a81638c2ee779f0ab6c3ba7e5c6f6e064151))
|
||||||
|
* 修复cron选择组件星期显示错误的bug ([eb75e52](https://github.com/certd/certd/commit/eb75e52278f94a72643f7317e6740fb42666c68a))
|
||||||
|
* 修复proxmox某些情况下执行卡住的bug ([ebd6917](https://github.com/certd/certd/commit/ebd6917a1d40ae4d94555c32b7e3c093d0599b94))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 部署到k8s支持自动创建secret ([c09c962](https://github.com/certd/certd/commit/c09c962cb676ca261610aa9f3e5105c9dae43f43))
|
||||||
|
* 短信验证码支持腾讯云 ([9108459](https://github.com/certd/certd/commit/9108459ae42bcd95a59acba164a64e82e5f2cfe6))
|
||||||
|
* 商业版支持自定义插件的参数配置 ([17f23f3](https://github.com/certd/certd/commit/17f23f37516af925d5049291d67d41e4271f81f8))
|
||||||
|
* 腾讯云插件支持国际版 ([58e82d5](https://github.com/certd/certd/commit/58e82d5dbd4ebf089ef239578ef9b68454d17b30))
|
||||||
|
* 腾讯云EO插件支持自动获取zoneid和域名列表 ([70fcdc9](https://github.com/certd/certd/commit/70fcdc9ebbfb7c883c0c8a2138f61a0776a9491b))
|
||||||
|
* 支持部署到阿里云云原生API网关、AI网关 ([2ca20be](https://github.com/certd/certd/commit/2ca20be197720201fceabcce9d927f4dbc1cc872))
|
||||||
|
* 支持部署到华为云obs ([9feb9d0](https://github.com/certd/certd/commit/9feb9d04b3c56ec95c06fcf4fd071eb0e88ffc6f))
|
||||||
|
* 支持部署到dokploy ([7dbdeae](https://github.com/certd/certd/commit/7dbdeaebe0bfee7521a863fe5e6b4a712aec5876))
|
||||||
|
* 支持删除宝塔证书夹中的过期证书 ([3575113](https://github.com/certd/certd/commit/3575113655be751d19f88c64491e98a89042d6a2))
|
||||||
|
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
|
||||||
|
* lecdnv2支持api token ([e448934](https://github.com/certd/certd/commit/e4489343fee7754be07bcfc3323969dc3a30e90c))
|
||||||
|
* openapi返回证书时挑选匹配范围最小的那一个;增加format参数,增加返回值p7b格式,增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
|
||||||
|
* ssh 配置sudo免密提示 ([e1e7011](https://github.com/certd/certd/commit/e1e7011853ad0c5bd7b09c3690861d5aa34b2db4))
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复新部署的无法保存公共eab配置的bug ([d5dee75](https://github.com/certd/certd/commit/d5dee75df3bd635a597436e448b2de1407531f3a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 阿里云 FC3.0 不在要求证书加密方式为旧版, 修复支持的协议类型可以正常选择 ([a34db74](https://github.com/certd/certd/commit/a34db7449eff6ad1dda01de673bf85579fa3865a))
|
||||||
|
* 部署到腾讯云cdn,每个域名增加3每秒延迟 ([f7d43ad](https://github.com/certd/certd/commit/f7d43ad5af4663d4be369820a80d1fd9817ca4ab))
|
||||||
|
* 腾讯云关闭证书通知增加开关选项,在腾讯云授权里面 ([a77c777](https://github.com/certd/certd/commit/a77c777980dd38d97d983124eeed1596879bba95))
|
||||||
|
* 证书申请任务默认不发送申请成功通知 ([0283bd2](https://github.com/certd/certd/commit/0283bd2f978dbcd13d361129135e439dd9fbc180))
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Certd
|
# Certd
|
||||||
|
|
||||||
[English](./README_en.md) | [中文](./README.md)
|
中文 | [English](./README_en.md)
|
||||||
|
|
||||||
Certd® 是一个免费的全自动证书管理系统,让你的网站证书永不过期。
|
Certd® 是一个免费的全自动证书管理系统,让你的网站证书永不过期。
|
||||||
后缀d取自linux守护进程的命名风格,意为证书守护进程
|
后缀d取自linux守护进程的命名风格,意为证书守护进程
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Certd
|
# Certd
|
||||||
|
|
||||||
[English](./README_en.md) | [中文](./README.md)
|
[中文](./README.md) | English
|
||||||
|
|
||||||
Certd® is a free, fully automated certificate management system that ensures your website certificates never expire. The suffix 'd' is inspired by the naming convention of Linux daemons, representing a certificate daemon.
|
Certd® is a free, fully automated certificate management system that ensures your website certificates never expire. The suffix 'd' is inspired by the naming convention of Linux daemons, representing a certificate daemon.
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
23:23
|
00:34
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ services:
|
|||||||
# 设置环境变量即可自定义certd配置
|
# 设置环境变量即可自定义certd配置
|
||||||
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
||||||
# 配置规则: certd_ + 配置项, 点号用_代替
|
# 配置规则: certd_ + 配置项, 点号用_代替
|
||||||
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,重启之后,管理员密码将改成123456,然后请及时修改回false
|
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,docker compose up -d 重建容器之后,管理员密码将改成123456,然后请及时修改回false
|
||||||
- certd_system_resetAdminPasswd=false
|
- certd_system_resetAdminPasswd=false
|
||||||
|
|
||||||
# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ export default defineConfig({
|
|||||||
text: "常见问题",
|
text: "常见问题",
|
||||||
items: [
|
items: [
|
||||||
{text: "QA", link: "/guide/qa/use.md"},
|
{text: "QA", link: "/guide/qa/use.md"},
|
||||||
{text: "常见报错处理", link: "/guide/qa/"},
|
|
||||||
{text: "群晖证书部署", link: "/guide/use/synology/"},
|
{text: "群晖证书部署", link: "/guide/use/synology/"},
|
||||||
{text: "腾讯云密钥获取", link: "/guide/use/tencent/"},
|
{text: "腾讯云密钥获取", link: "/guide/use/tencent/"},
|
||||||
{text: "连接windows主机", link: "/guide/use/host/windows.md"},
|
{text: "连接windows主机", link: "/guide/use/host/windows.md"},
|
||||||
@@ -120,6 +119,7 @@ export default defineConfig({
|
|||||||
{text: "邮箱配置", link: "/guide/use/email/index.md"},
|
{text: "邮箱配置", link: "/guide/use/email/index.md"},
|
||||||
{text: "IPv6支持", link: "/guide/use/setting/ipv6.md"},
|
{text: "IPv6支持", link: "/guide/use/setting/ipv6.md"},
|
||||||
{text: "ESXi", link: "/guide/use/ESXi/index.md"},
|
{text: "ESXi", link: "/guide/use/ESXi/index.md"},
|
||||||
|
{text: "子域名托管", link: "/guide/use/cert/subdomain.md"},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,83 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 前置任务输出不存在时输出警告提示 ([b59052c](https://github.com/certd/certd/commit/b59052cc43b7b070fabd8b8e914e4c2a5e0ad61c))
|
||||||
|
* 修复批量流水线执行时日志显示错乱的问题 ([4372adc](https://github.com/certd/certd/commit/4372adc703b9a4c785664054ab2a533626d815a8))
|
||||||
|
* 修复远程数据选择无法过滤的bug ([6cbb073](https://github.com/certd/certd/commit/6cbb0739f8428d51b0712f718fe4d236cc087cf9))
|
||||||
|
* 修复mysql下购买套餐加量包无效的bug ([c26ad4c](https://github.com/certd/certd/commit/c26ad4c8075f0606d45b8da13915737968d6191a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 创建证书时支持选择通知时机 ([0e96bfd](https://github.com/certd/certd/commit/0e96bfdfa377824d204e72923d1176408ae6b300))
|
||||||
|
* 创建k8s secret 时设置type为tls ([79ebabf](https://github.com/certd/certd/commit/79ebabfcfb9e5a534049c84f5f1a642b357fc856))
|
||||||
|
* 去掉宝塔url后面的斜杠 ([8a0c2b9](https://github.com/certd/certd/commit/8a0c2b9b13628da750c25757e0cb8ed3038775ba))
|
||||||
|
* 商业版隐藏文档相关链接 ([4443a1c](https://github.com/certd/certd/commit/4443a1c0308fa6b95a05efd73d15d24b65d641c9))
|
||||||
|
* 商业版隐藏文档相关链接 ([db89561](https://github.com/certd/certd/commit/db8956148083bc4f988226ccf719940d08158a27))
|
||||||
|
* 增加健康检查探针 /health/liveliness 和 /health/readiness ([44019e1](https://github.com/certd/certd/commit/44019e104289fedd32a867db00e9c6cb71b389cc))
|
||||||
|
* 支持根据id更新证书(证书Id不变接口),不过该接口为白名单功能,普通腾讯云账户无法使用 ([fe9c4f3](https://github.com/certd/certd/commit/fe9c4f3391ff07c01dd9a252225f69a129c39050))
|
||||||
|
* 支持godaddy ([b7980aa](https://github.com/certd/certd/commit/b7980aad5ab50f58662eaddf5d84aa82876a98eb))
|
||||||
|
* 支持ssl.com证书颁发机构 ([27b6dfa](https://github.com/certd/certd/commit/27b6dfa4d2ab3bddd284c3a34511a72e1a513a4c))
|
||||||
|
* 子域名托管说明 ([39a0223](https://github.com/certd/certd/commit/39a02235cf4416bb5bd1acd3831241efeaa2f602))
|
||||||
|
* ssh 增加超时断开连接,默认10分钟超时 ([c24a040](https://github.com/certd/certd/commit/c24a040c19cacafc79228d7a7649af93837d94a1))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 更新我爱云CDN域名地址,和部分目录结构 [@tyjsjxh](https://github.com/tyjsjxh) ([#514](https://github.com/certd/certd/issues/514)) ([78e7a81](https://github.com/certd/certd/commit/78e7a81638c2ee779f0ab6c3ba7e5c6f6e064151))
|
||||||
|
* 修复cron选择组件星期显示错误的bug ([eb75e52](https://github.com/certd/certd/commit/eb75e52278f94a72643f7317e6740fb42666c68a))
|
||||||
|
* 修复proxmox某些情况下执行卡住的bug ([ebd6917](https://github.com/certd/certd/commit/ebd6917a1d40ae4d94555c32b7e3c093d0599b94))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 部署到k8s支持自动创建secret ([c09c962](https://github.com/certd/certd/commit/c09c962cb676ca261610aa9f3e5105c9dae43f43))
|
||||||
|
* 短信验证码支持腾讯云 ([9108459](https://github.com/certd/certd/commit/9108459ae42bcd95a59acba164a64e82e5f2cfe6))
|
||||||
|
* 商业版支持自定义插件的参数配置 ([17f23f3](https://github.com/certd/certd/commit/17f23f37516af925d5049291d67d41e4271f81f8))
|
||||||
|
* 腾讯云插件支持国际版 ([58e82d5](https://github.com/certd/certd/commit/58e82d5dbd4ebf089ef239578ef9b68454d17b30))
|
||||||
|
* 腾讯云EO插件支持自动获取zoneid和域名列表 ([70fcdc9](https://github.com/certd/certd/commit/70fcdc9ebbfb7c883c0c8a2138f61a0776a9491b))
|
||||||
|
* 支持部署到阿里云云原生API网关、AI网关 ([2ca20be](https://github.com/certd/certd/commit/2ca20be197720201fceabcce9d927f4dbc1cc872))
|
||||||
|
* 支持部署到华为云obs ([9feb9d0](https://github.com/certd/certd/commit/9feb9d04b3c56ec95c06fcf4fd071eb0e88ffc6f))
|
||||||
|
* 支持部署到dokploy ([7dbdeae](https://github.com/certd/certd/commit/7dbdeaebe0bfee7521a863fe5e6b4a712aec5876))
|
||||||
|
* 支持删除宝塔证书夹中的过期证书 ([3575113](https://github.com/certd/certd/commit/3575113655be751d19f88c64491e98a89042d6a2))
|
||||||
|
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
|
||||||
|
* lecdnv2支持api token ([e448934](https://github.com/certd/certd/commit/e4489343fee7754be07bcfc3323969dc3a30e90c))
|
||||||
|
* openapi返回证书时挑选匹配范围最小的那一个;增加format参数,增加返回值p7b格式,增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
|
||||||
|
* ssh 配置sudo免密提示 ([e1e7011](https://github.com/certd/certd/commit/e1e7011853ad0c5bd7b09c3690861d5aa34b2db4))
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复新部署的无法保存公共eab配置的bug ([d5dee75](https://github.com/certd/certd/commit/d5dee75df3bd635a597436e448b2de1407531f3a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 阿里云 FC3.0 不在要求证书加密方式为旧版, 修复支持的协议类型可以正常选择 ([a34db74](https://github.com/certd/certd/commit/a34db7449eff6ad1dda01de673bf85579fa3865a))
|
||||||
|
* 部署到腾讯云cdn,每个域名增加3每秒延迟 ([f7d43ad](https://github.com/certd/certd/commit/f7d43ad5af4663d4be369820a80d1fd9817ca4ab))
|
||||||
|
* 腾讯云关闭证书通知增加开关选项,在腾讯云授权里面 ([a77c777](https://github.com/certd/certd/commit/a77c777980dd38d97d983124eeed1596879bba95))
|
||||||
|
* 证书申请任务默认不发送申请成功通知 ([0283bd2](https://github.com/certd/certd/commit/0283bd2f978dbcd13d361129135e439dd9fbc180))
|
||||||
|
|
||||||
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复授权配置复制功能,无法复制已加密字段的问题 ([221e068](https://github.com/certd/certd/commit/221e068bac3af6cd5d1794f8cd4c2ec5c0bc3f45))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 百度云支持上传到证书托管,支持部署到负载均衡 ([798a48a](https://github.com/certd/certd/commit/798a48aa9686fd5d11cfffb6cd93eadfc40aacb3))
|
||||||
|
* 部署到百度cdn支持自动获取域名列表选择 ([4e432ed](https://github.com/certd/certd/commit/4e432ed03f4fb564e85a2f284ee26b58400b82f5))
|
||||||
|
* 验证码可重试次数设置为3次 ([1bdceee](https://github.com/certd/certd/commit/1bdceeecf4b5daecdd621a05a2596b6eb45ce8ea))
|
||||||
|
* 增加找回密码的验证码可重试次数 [@nicheng-he](https://github.com/nicheng-he) ([#496](https://github.com/certd/certd/issues/496)) ([fe03f99](https://github.com/certd/certd/commit/fe03f9942b5662fb90cad86da10782f5dc3603f5))
|
||||||
|
* 支持阿里云API网关 ([9e1e4ee](https://github.com/certd/certd/commit/9e1e4eeec2859759ca5b07834c9d24cf88a6ad33))
|
||||||
|
* 支持部署到金山云CDN ([dfa74a6](https://github.com/certd/certd/commit/dfa74a69f7cbb9009d3e20c7eecfa1b905a00cf0))
|
||||||
|
* 支持更新金山云cdn证书 ([462e22a](https://github.com/certd/certd/commit/462e22a3b0a94887462fe6aa68e4671a365e0737))
|
||||||
|
* 支持apisix证书部署 ([9b63fb4](https://github.com/certd/certd/commit/9b63fb4ee2c6b56139160c5bf63482dab0869c2b))
|
||||||
|
|
||||||
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
|
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -58,3 +58,18 @@ kill -9 $(lsof -t -i:7001)
|
|||||||
## 四、备份恢复
|
## 四、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`及同目录下的其他文件覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件覆盖到原来的位置,重启certd即可
|
||||||
|
|
||||||
|
## 六、常见问题
|
||||||
|
|
||||||
|
### 1. npm install better-sqlite3 时,提示node-gyp需要vscode环境编译
|
||||||
|
|
||||||
|
1. 首先确保node版本为22以上
|
||||||
|
2. 将下面两行加到 ~/.npmrc 里面
|
||||||
|
3. 重新install
|
||||||
|
> better_sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
|
> better_sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
# 常见报错解决
|
|
||||||
|
|
||||||
## 1. getaddrinfo ENOTFOUND错误
|
|
||||||
如果出现`getaddrinfo ENOTFOUND`/`getaddrinfo EAI_AGAIN`错误,可以尝试在`docker-compose.yaml`中设置dns
|
|
||||||
```yaml
|
|
||||||
version: '3.3' # 兼容旧版docker-compose
|
|
||||||
services:
|
|
||||||
certd:
|
|
||||||
#↓↓↓↓ ------------ # 如果出现getaddrinfo ENOTFOUND 或 EAI_AGAIN错误,可以尝试设置dns
|
|
||||||
dns:
|
|
||||||
- 223.5.5.5 # 阿里云公共dns
|
|
||||||
- 223.6.6.6
|
|
||||||
# # ↓↓↓↓ ------- # 如果你服务器在腾讯云,可以用这个替换上面阿里云的公共dns
|
|
||||||
# - 119.29.29.29 # 腾讯云公共dns
|
|
||||||
# - 182.254.116.116
|
|
||||||
# # ↓↓↓↓ ------- # 如果你服务器部署在国外,可以用这个替换上面阿里云的公共dns
|
|
||||||
# - 8.8.8.8 # 谷歌公共dns
|
|
||||||
# - 8.8.4.4
|
|
||||||
```
|
|
||||||
|
|
||||||
如果仍然有问题,按如下步骤检查是否能够ping通域名
|
|
||||||
```shell
|
|
||||||
docker exec -it certd /bin/sh
|
|
||||||
ping www.baidu.com
|
|
||||||
ping gg.px.certd.handfree.work
|
|
||||||
ping app.handfree.work
|
|
||||||
```
|
|
||||||
|
|
||||||
如果您是宝塔部署的
|
|
||||||
可以试试将容器网络加入brige网络,看是否解决问题
|
|
||||||

|
|
||||||
|
|
||||||
如果还是不行,请联系我们
|
|
||||||
|
|
||||||
|
|
||||||
## 2. 连接IPv6超时
|
|
||||||
docker-compose 需要放开IPv6网络的配置
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
certd:
|
|
||||||
networks:
|
|
||||||
- ip6net
|
|
||||||
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络,还需要把上面networks的注释放开
|
|
||||||
networks:
|
|
||||||
ip6net:
|
|
||||||
enable_ipv6: true
|
|
||||||
ipam:
|
|
||||||
config:
|
|
||||||
- subnet: 2001:db8::/64
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. SSL_CERT_NOT_MATCH_DOMAIN_ERROR
|
|
||||||
部署证书任务报类似 `SSL_CERT_NOT_MATCH_DOMAIN_ERROR`错误
|
|
||||||
这是由于当前流水线的证书域名与要部署的目标站点的域名不匹配导致的,在申请证书任务中,增加目标站点域名,重新运行流水线即可
|
|
||||||
|
|
||||||
|
|
||||||
## 4. 没有服务器配置文件,请检查是否开启了外网映射!
|
|
||||||
宝塔网站证书部署报错:`Error: 没有服务器配置文件,请检查是否开启了外网映射!`
|
|
||||||
解决方案:先手动在宝塔网站中设置一次证书
|
|
||||||
|
|
||||||
|
|
||||||
## 5. 如何查看容器日志
|
|
||||||
```shell
|
|
||||||
docker logs -f --tail 200 certd
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. 容器内走时不准,或者时区不对
|
|
||||||
走时不准确,慢慢偏差越来越大
|
|
||||||
或者整个时区都不对
|
|
||||||
可以尝试挂载localtime文件
|
|
||||||
```yaml
|
|
||||||
volumes:
|
|
||||||
# ↓↓↓↓↓ -------------------- 如果走时不准,请尝试挂载localtime文件
|
|
||||||
- /etc/localtime:/etc/localtime
|
|
||||||
- /etc/timezone:/etc/timezone
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# 使用问题
|
# 常见问题
|
||||||
|
|
||||||
|
|
||||||
## 1. 是否支持IP证书
|
## 1. 是否支持IP证书
|
||||||
@@ -12,3 +12,9 @@
|
|||||||
当证书到期前35天(创建流水线时可以修改),将会自动重新申请证书,自动部署
|
当证书到期前35天(创建流水线时可以修改),将会自动重新申请证书,自动部署
|
||||||
|
|
||||||
|
|
||||||
|
## 3. too many certificates 错误
|
||||||
|
当出现如下报错时,说明相同的域名短时间内申请超过5次
|
||||||
|
解决方案:可以加多一个子域名,重新执行就可以规避次错误
|
||||||
|
```
|
||||||
|
"detail": too many certificates (5) already issued for this exact set of idantifiers in the last 168hm0s
|
||||||
|
```
|
||||||
BIN
docs/guide/use/cert/images/subdomain1.png
Normal file
BIN
docs/guide/use/cert/images/subdomain1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/guide/use/cert/images/subdomain2.png
Normal file
BIN
docs/guide/use/cert/images/subdomain2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
10
docs/guide/use/cert/subdomain.md
Normal file
10
docs/guide/use/cert/subdomain.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 二级子域名托管
|
||||||
|
如果你的域名是免费的二级域名(比如:sub.handsfree.work),托管在CF或者阿里云上
|
||||||
|
在使用DNS方式校验时需要设置子域名托管
|
||||||
|
|
||||||
|
[阿里云子域名托管说明](https://help.aliyun.com/zh/dns/pubz-subdomain-management)
|
||||||
|
|
||||||
|
|
||||||
|

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

|
||||||
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.36.16"
|
"version": "1.36.19"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"init": "lerna run build",
|
"init": "lerna run build",
|
||||||
"init:dev": "lerna run build",
|
"init:dev": "lerna run build",
|
||||||
"docs:dev": "vitepress dev docs",
|
"docs:dev": "vitepress dev docs",
|
||||||
"docs:build": "vitepress build docs",
|
"docs:build": "npm run copylogs && vitepress build docs",
|
||||||
"docs:preview": "vitepress preview docs",
|
"docs:preview": "vitepress preview docs",
|
||||||
"pub": "echo 1"
|
"pub": "echo 1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,20 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/publishlab/node-acme-client/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 支持ssl.com证书颁发机构 ([27b6dfa](https://github.com/publishlab/node-acme-client/commit/27b6dfa4d2ab3bddd284c3a34511a72e1a513a4c))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/publishlab/node-acme-client/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/publishlab/node-acme-client/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
## [1.36.16](https://github.com/publishlab/node-acme-client/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/publishlab/node-acme-client/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"private": false,
|
"private": false,
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "scr/index.js",
|
"module": "scr/index.js",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.36.16",
|
"@certd/basic": "^1.36.19",
|
||||||
"@peculiar/x509": "^1.11.0",
|
"@peculiar/x509": "^1.11.0",
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
@@ -69,5 +69,5 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export const directory = {
|
|||||||
staging: 'https://acme.zerossl.com/v2/DV90',
|
staging: 'https://acme.zerossl.com/v2/DV90',
|
||||||
production: 'https://acme.zerossl.com/v2/DV90',
|
production: 'https://acme.zerossl.com/v2/DV90',
|
||||||
},
|
},
|
||||||
|
sslcom:{
|
||||||
|
staging: 'https://acme.ssl.com/sslcom-dv-rsa',
|
||||||
|
production: 'https://acme.ssl.com/sslcom-dv-rsa',
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复批量流水线执行时日志显示错乱的问题 ([4372adc](https://github.com/certd/certd/commit/4372adc703b9a4c785664054ab2a533626d815a8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 去掉宝塔url后面的斜杠 ([8a0c2b9](https://github.com/certd/certd/commit/8a0c2b9b13628da750c25757e0cb8ed3038775ba))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/basic
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
12:46
|
00:30
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -45,5 +45,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,4 @@
|
|||||||
import log4js, { LoggingEvent, Logger } from "log4js";
|
import log4js, { CallStack, Level } from "log4js";
|
||||||
|
|
||||||
const OutputAppender = {
|
|
||||||
configure: (config: any, layouts: any, findAppender: any, levels: any) => {
|
|
||||||
let layout = layouts.basicLayout;
|
|
||||||
if (config.layout) {
|
|
||||||
layout = layouts.layout(config.layout.type, config.layout);
|
|
||||||
}
|
|
||||||
function customAppender(layout: any, timezoneOffset: any) {
|
|
||||||
return (loggingEvent: LoggingEvent) => {
|
|
||||||
if (loggingEvent.context.outputHandler?.write) {
|
|
||||||
const text = `${layout(loggingEvent, timezoneOffset)}\n`;
|
|
||||||
loggingEvent.context.outputHandler.write(text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return customAppender(layout, config.timezoneOffset);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let logFilePath = "./logs/app.log";
|
let logFilePath = "./logs/app.log";
|
||||||
export function resetLogConfigure() {
|
export function resetLogConfigure() {
|
||||||
@@ -24,7 +6,6 @@ export function resetLogConfigure() {
|
|||||||
log4js.configure({
|
log4js.configure({
|
||||||
appenders: {
|
appenders: {
|
||||||
std: { type: "stdout" },
|
std: { type: "stdout" },
|
||||||
output: { type: OutputAppender },
|
|
||||||
file: {
|
file: {
|
||||||
type: "dateFile",
|
type: "dateFile",
|
||||||
filename: logFilePath,
|
filename: logFilePath,
|
||||||
@@ -33,7 +14,7 @@ export function resetLogConfigure() {
|
|||||||
numBackups: 3,
|
numBackups: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
categories: { default: { appenders: ["std", "file"], level: "info" }, pipeline: { appenders: ["std", "file", "output"], level: "info" } },
|
categories: { default: { appenders: ["std", "file"], level: "info" }, pipeline: { appenders: ["std", "file"], level: "info" } },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
resetLogConfigure();
|
resetLogConfigure();
|
||||||
@@ -44,15 +25,98 @@ export function resetLogFilePath(filePath: string) {
|
|||||||
resetLogConfigure();
|
resetLogConfigure();
|
||||||
}
|
}
|
||||||
export function buildLogger(write: (text: string) => void) {
|
export function buildLogger(write: (text: string) => void) {
|
||||||
const logger = log4js.getLogger("pipeline");
|
return new PipelineLogger("pipeline", write);
|
||||||
const _secrets: string[] = [];
|
}
|
||||||
//@ts-ignore
|
|
||||||
logger.addSecret = (secret: string) => {
|
export type ILogger = {
|
||||||
_secrets.push(secret);
|
readonly category: string;
|
||||||
};
|
level: Level | string;
|
||||||
logger.addContext("outputHandler", {
|
log(level: Level | string, ...args: any[]): void;
|
||||||
write: (text: string) => {
|
|
||||||
for (const item of _secrets) {
|
isLevelEnabled(level?: string): boolean;
|
||||||
|
|
||||||
|
isTraceEnabled(): boolean;
|
||||||
|
isDebugEnabled(): boolean;
|
||||||
|
isInfoEnabled(): boolean;
|
||||||
|
isWarnEnabled(): boolean;
|
||||||
|
isErrorEnabled(): boolean;
|
||||||
|
isFatalEnabled(): boolean;
|
||||||
|
|
||||||
|
_log(level: Level, data: any): void;
|
||||||
|
|
||||||
|
addContext(key: string, value: any): void;
|
||||||
|
|
||||||
|
removeContext(key: string): void;
|
||||||
|
|
||||||
|
clearContext(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the basic parse function with a new custom one
|
||||||
|
* - Note that linesToSkip will be based on the origin of the Error object in addition to the callStackLinesToSkip (at least 1)
|
||||||
|
* @param parseFunction the new parseFunction. Use `undefined` to reset to the base implementation
|
||||||
|
*/
|
||||||
|
setParseCallStackFunction(parseFunction: (error: Error, linesToSkip: number) => CallStack | undefined): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust the value of linesToSkip when the parseFunction is called.
|
||||||
|
*
|
||||||
|
* Cannot be less than 0.
|
||||||
|
*/
|
||||||
|
callStackLinesToSkip: number;
|
||||||
|
|
||||||
|
trace(message: any, ...args: any[]): void;
|
||||||
|
|
||||||
|
debug(message: any, ...args: any[]): void;
|
||||||
|
|
||||||
|
info(message: any, ...args: any[]): void;
|
||||||
|
|
||||||
|
warn(message: any, ...args: any[]): void;
|
||||||
|
|
||||||
|
error(message: any, ...args: any[]): void;
|
||||||
|
|
||||||
|
fatal(message: any, ...args: any[]): void;
|
||||||
|
|
||||||
|
mark(message: any, ...args: any[]): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||||
|
const formatter = new Intl.DateTimeFormat(locale, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
function formatDateIntl(date = new Date()) {
|
||||||
|
const milliseconds = date.getMilliseconds(); // 获取毫秒
|
||||||
|
const formattedMilliseconds = milliseconds.toString().padStart(3, "0");
|
||||||
|
return formatter.format(date) + "." + formattedMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
export class PipelineLogger implements ILogger {
|
||||||
|
callStackLinesToSkip: number = 3;
|
||||||
|
readonly category: string = "pipeline";
|
||||||
|
level: Level | string = "info";
|
||||||
|
_secrets: string[] = [];
|
||||||
|
logger: ILogger;
|
||||||
|
customWriter!: (text: string) => void;
|
||||||
|
|
||||||
|
constructor(name: string, write: (text: string) => void) {
|
||||||
|
this.customWriter = write;
|
||||||
|
this.logger = log4js.getLogger(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
addSecret(secret: string) {
|
||||||
|
this._secrets.push(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
_doLog(level: string, ...args: any[]) {
|
||||||
|
let text = args.join(" ");
|
||||||
|
if (this.customWriter) {
|
||||||
|
for (const item of this._secrets) {
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -66,10 +130,88 @@ export function buildLogger(write: (text: string) => void) {
|
|||||||
text = text.replaceAll(item, "*".repeat(item.length));
|
text = text.replaceAll(item, "*".repeat(item.length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write(text);
|
text = `[${formatDateIntl()}] [${level.toUpperCase()}] - ${text} \n`;
|
||||||
},
|
this.customWriter(text);
|
||||||
});
|
}
|
||||||
return logger;
|
// @ts-ignore
|
||||||
}
|
this.logger[level](...args);
|
||||||
|
}
|
||||||
|
|
||||||
export type ILogger = Logger;
|
_log(level: Level, data: any): void {}
|
||||||
|
|
||||||
|
addContext(key: string, value: any): void {}
|
||||||
|
|
||||||
|
clearContext(): void {}
|
||||||
|
|
||||||
|
debug(message: any, ...args: any[]): void {
|
||||||
|
if (this.isDebugEnabled()) {
|
||||||
|
this._doLog("debug", message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message: any, ...args: any[]): void {
|
||||||
|
if (this.isErrorEnabled()) {
|
||||||
|
this._doLog("error", message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fatal(message: any, ...args: any[]): void {
|
||||||
|
if (this.isFatalEnabled()) {
|
||||||
|
this._doLog("fatal", message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info(message: any, ...args: any[]): void {
|
||||||
|
if (this.isInfoEnabled()) {
|
||||||
|
this._doLog("info", message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace(message: any, ...args: any[]): void {
|
||||||
|
if (this.isTraceEnabled()) {
|
||||||
|
this._doLog("trace", message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(message: any, ...args: any[]): void {
|
||||||
|
if (this.isWarnEnabled()) {
|
||||||
|
this._doLog("warn", message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDebugEnabled(): boolean {
|
||||||
|
return logger.isDebugEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
isErrorEnabled(): boolean {
|
||||||
|
return logger.isErrorEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
isFatalEnabled(): boolean {
|
||||||
|
return logger.isFatalEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
isInfoEnabled(): boolean {
|
||||||
|
return logger.isInfoEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
isLevelEnabled(level?: string): boolean {
|
||||||
|
return logger.isLevelEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
isTraceEnabled(): boolean {
|
||||||
|
return logger.isTraceEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
isWarnEnabled(): boolean {
|
||||||
|
return logger.isWarnEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
log(level: Level | string, ...args: any[]): void {}
|
||||||
|
|
||||||
|
mark(message: any, ...args: any[]): void {}
|
||||||
|
|
||||||
|
removeContext(key: string): void {}
|
||||||
|
|
||||||
|
setParseCallStackFunction(parseFunction: (error: Error, linesToSkip: number) => CallStack | undefined): void {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import axios, { AxiosHeaders, AxiosRequestConfig } from "axios";
|
import axios, { AxiosHeaders, AxiosRequestConfig } from "axios";
|
||||||
import { ILogger, logger } from "./util.log.js";
|
import { ILogger, logger } from "./util.log.js";
|
||||||
import { Logger } from "log4js";
|
|
||||||
import { HttpProxyAgent } from "http-proxy-agent";
|
import { HttpProxyAgent } from "http-proxy-agent";
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||||
import nodeHttp from "http";
|
import nodeHttp from "http";
|
||||||
@@ -84,7 +83,7 @@ export function getGlobalAgents() {
|
|||||||
/**
|
/**
|
||||||
* @description 创建请求实例
|
* @description 创建请求实例
|
||||||
*/
|
*/
|
||||||
export function createAxiosService({ logger }: { logger: Logger }) {
|
export function createAxiosService({ logger }: { logger: ILogger }) {
|
||||||
// 创建一个 axios 实例
|
// 创建一个 axios 实例
|
||||||
const service = axios.create();
|
const service = axios.create();
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,25 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 前置任务输出不存在时输出警告提示 ([b59052c](https://github.com/certd/certd/commit/b59052cc43b7b070fabd8b8e914e4c2a5e0ad61c))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 支持godaddy ([b7980aa](https://github.com/certd/certd/commit/b7980aad5ab50f58662eaddf5d84aa82876a98eb))
|
||||||
|
* 支持ssl.com证书颁发机构 ([27b6dfa](https://github.com/certd/certd/commit/27b6dfa4d2ab3bddd284c3a34511a72e1a513a4c))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.36.16",
|
"@certd/basic": "^1.36.19",
|
||||||
"@certd/plus-core": "^1.36.16",
|
"@certd/plus-core": "^1.36.19",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"reflect-metadata": "^0.1.13"
|
"reflect-metadata": "^0.1.13"
|
||||||
@@ -44,5 +44,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ export type PageRes = {
|
|||||||
export class Pager {
|
export class Pager {
|
||||||
pageNo: number;
|
pageNo: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
constructor(req: PageSearch) {
|
constructor(req?: PageSearch) {
|
||||||
this.pageNo = req.pageNo ?? 1;
|
this.pageNo = req?.pageNo ?? 1;
|
||||||
this.pageSize = req.pageSize || 50;
|
this.pageSize = req?.pageSize || 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOffset() {
|
getOffset() {
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ export class Executor {
|
|||||||
const outputKey = arr[2];
|
const outputKey = arr[2];
|
||||||
input[key] = this.currentStatusMap.get(id)?.status?.output[outputKey] ?? this.lastStatusMap.get(id)?.status?.output[outputKey];
|
input[key] = this.currentStatusMap.get(id)?.status?.output[outputKey] ?? this.lastStatusMap.get(id)?.status?.output[outputKey];
|
||||||
if (input[key] == null) {
|
if (input[key] == null) {
|
||||||
this.logger.warn(`${item.title}的配置未找到对应的输出值,请确认对应的前置任务是否存在或者是否执行正确`);
|
currentLogger.warn(`${item.title}的配置未找到对应的输出值,请确认对应的前置任务是否存在或者是否执行正确`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,9 +253,9 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
|||||||
return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS");
|
return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS");
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCertName(domain: string) {
|
buildCertName(domain: string, prefix = "") {
|
||||||
domain = domain.replaceAll("*", "_").replaceAll(".", "_");
|
domain = domain.replaceAll("*", "_").replaceAll(".", "_");
|
||||||
return `${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`;
|
return `${prefix}_${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRequest(req: PluginRequestHandleReq<any>) {
|
async onRequest(req: PluginRequestHandleReq<any>) {
|
||||||
|
|||||||
@@ -69,9 +69,15 @@ export class Registry<T = any> {
|
|||||||
return this.storage;
|
return this.storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefineList() {
|
getDefineList(prefix?: string) {
|
||||||
let list = [];
|
let list = [];
|
||||||
|
if (prefix) {
|
||||||
|
prefix = prefix + ":";
|
||||||
|
}
|
||||||
for (const key in this.storage) {
|
for (const key in this.storage) {
|
||||||
|
if (prefix && !key.startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const define = this.getDefine(key);
|
const define = this.getDefine(key);
|
||||||
if (define) {
|
if (define) {
|
||||||
if (define?.deprecated) {
|
if (define?.deprecated) {
|
||||||
@@ -90,7 +96,10 @@ export class Registry<T = any> {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefine(key: string) {
|
getDefine(key: string, prefix?: string) {
|
||||||
|
if (prefix) {
|
||||||
|
key = prefix + ":" + key;
|
||||||
|
}
|
||||||
const item = this.storage[key];
|
const item = this.storage[key];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-huawei
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-huawei",
|
"name": "@certd/lib-huawei",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
"types": "./dist/d/index.d.ts",
|
"types": "./dist/d/index.d.ts",
|
||||||
@@ -24,5 +24,5 @@
|
|||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-iframe
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-iframe",
|
"name": "@certd/lib-iframe",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -31,5 +31,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/jdcloud
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/jdcloud",
|
"name": "@certd/jdcloud",
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"description": "jdcloud openApi sdk",
|
"description": "jdcloud openApi sdk",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
@@ -61,5 +61,5 @@
|
|||||||
"fetch"
|
"fetch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,26 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复远程数据选择无法过滤的bug ([6cbb073](https://github.com/certd/certd/commit/6cbb0739f8428d51b0712f718fe4d236cc087cf9))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 创建k8s secret 时设置type为tls ([79ebabf](https://github.com/certd/certd/commit/79ebabfcfb9e5a534049c84f5f1a642b357fc856))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 部署到k8s支持自动创建secret ([c09c962](https://github.com/certd/certd/commit/c09c962cb676ca261610aa9f3e5105c9dae43f43))
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-k8s
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-k8s",
|
"name": "@certd/lib-k8s",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.36.16",
|
"@certd/basic": "^1.36.19",
|
||||||
"@kubernetes/client-node": "0.21.0"
|
"@kubernetes/client-node": "0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -32,5 +32,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CoreV1Api, KubeConfig, NetworkingV1Api, V1Ingress, V1Secret } from "@kubernetes/client-node";
|
import { CoreV1Api, KubeConfig, NetworkingV1Api, V1Ingress, V1Secret } from "@kubernetes/client-node";
|
||||||
import dns from "dns";
|
import dns from "dns";
|
||||||
import { ILogger } from "@certd/basic";
|
import { ILogger } from "@certd/basic";
|
||||||
import _ from "lodash-es";
|
import { merge } from "lodash-es";
|
||||||
|
|
||||||
export type K8sClientOpts = {
|
export type K8sClientOpts = {
|
||||||
kubeConfigStr: string;
|
kubeConfigStr: string;
|
||||||
@@ -85,12 +85,11 @@ export class K8sClient {
|
|||||||
/**
|
/**
|
||||||
* 创建Secret
|
* 创建Secret
|
||||||
* @param opts {namespace:default, body:yamlStr}
|
* @param opts {namespace:default, body:yamlStr}
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
*/
|
||||||
async createSecret(opts: { namespace: string; body: V1Secret }) {
|
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);
|
const created = await this.client.createNamespacedSecret(namespace, opts.body);
|
||||||
this.logger.info("new secrets:", opts.body);
|
this.logger.info("new secrets:", opts.body.metadata);
|
||||||
return created.body;
|
return created.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,17 +102,39 @@ export class K8sClient {
|
|||||||
// return await this.client.replaceNamespacedSecret(secretName, namespace, opts.body);
|
// return await this.client.replaceNamespacedSecret(secretName, namespace, opts.body);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async patchSecret(opts: { namespace: string; secretName: string; body: V1Secret }) {
|
async patchSecret(opts: { namespace: string; secretName: string; body: V1Secret; createOnNotFound?: boolean }) {
|
||||||
const namespace = opts.namespace || "default";
|
const namespace = opts.namespace || "default";
|
||||||
const secretName = opts.secretName;
|
const secretName = opts.secretName;
|
||||||
if (secretName == null) {
|
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);
|
let oldSecret: any = null;
|
||||||
const newSecret = _.merge(oldSecret.body, opts.body);
|
try {
|
||||||
|
oldSecret = await this.client.readNamespacedSecret(secretName, namespace);
|
||||||
|
} catch (e) {
|
||||||
|
//@ts-ignore
|
||||||
|
if (e.response?.body?.code === 404) {
|
||||||
|
this.logger.warn(`secret ${secretName} 不存在`);
|
||||||
|
if (opts.createOnNotFound) {
|
||||||
|
//没有找到,则创建
|
||||||
|
const body = merge(
|
||||||
|
{
|
||||||
|
type: "kubernetes.io/tls",
|
||||||
|
},
|
||||||
|
opts.body
|
||||||
|
);
|
||||||
|
const res = await this.createSecret({ namespace, body });
|
||||||
|
this.logger.info(`secret ${secretName} 已创建`);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSecret = merge(oldSecret.body, opts.body);
|
||||||
const res = await this.client.replaceNamespacedSecret(secretName, namespace, newSecret);
|
const res = await this.client.replaceNamespacedSecret(secretName, namespace, newSecret);
|
||||||
this.logger.info("secret updated");
|
this.logger.info(`secret ${secretName} 已更新`);
|
||||||
return res.body;
|
return res.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +166,7 @@ export class K8sClient {
|
|||||||
this.logger.info("patch ingress:", ingressName, namespace);
|
this.logger.info("patch ingress:", ingressName, namespace);
|
||||||
const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
|
const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
|
||||||
const oldIngress = await client.readNamespacedIngress(ingressName, namespace);
|
const oldIngress = await client.readNamespacedIngress(ingressName, namespace);
|
||||||
const newIngress = _.merge(oldIngress.body, opts.body);
|
const newIngress = merge(oldIngress.body, opts.body);
|
||||||
const res = await client.replaceNamespacedIngress(ingressName, namespace, newIngress);
|
const res = await client.replaceNamespacedIngress(ingressName, namespace, newIngress);
|
||||||
this.logger.info("ingress patched", opts.body);
|
this.logger.info("ingress patched", opts.body);
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -27,10 +27,10 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.36.16",
|
"@certd/acme-client": "^1.36.19",
|
||||||
"@certd/basic": "^1.36.16",
|
"@certd/basic": "^1.36.19",
|
||||||
"@certd/pipeline": "^1.36.16",
|
"@certd/pipeline": "^1.36.19",
|
||||||
"@certd/plus-core": "^1.36.16",
|
"@certd/plus-core": "^1.36.19",
|
||||||
"@midwayjs/cache": "~3.14.0",
|
"@midwayjs/cache": "~3.14.0",
|
||||||
"@midwayjs/core": "~3.20.3",
|
"@midwayjs/core": "~3.20.3",
|
||||||
"@midwayjs/i18n": "~3.20.3",
|
"@midwayjs/i18n": "~3.20.3",
|
||||||
@@ -61,5 +61,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { SysSettingsEntity } from './system/index.js';
|
import { SysSettingsEntity } from './system/index.js';
|
||||||
import { AccessEntity } from './user/access/entity/access.js';
|
import { AccessEntity } from './user/access/entity/access.js';
|
||||||
|
import { AddonEntity } from "./user/index.js";
|
||||||
export * from './basic/index.js';
|
export * from './basic/index.js';
|
||||||
export * from './system/index.js';
|
export * from './system/index.js';
|
||||||
export * from './user/index.js';
|
export * from './user/index.js';
|
||||||
export { LibServerConfiguration as Configuration } from './configuration.js';
|
export { LibServerConfiguration as Configuration } from './configuration.js';
|
||||||
|
|
||||||
export const libServerEntities = [SysSettingsEntity, AccessEntity];
|
export const libServerEntities = [SysSettingsEntity, AccessEntity,AddonEntity];
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ export class SysPublicSettings extends BaseSettings {
|
|||||||
mpsNo?: string;
|
mpsNo?: string;
|
||||||
robots?: boolean = true;
|
robots?: boolean = true;
|
||||||
aiChatEnabled = true;
|
aiChatEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
|
//验证码是否开启
|
||||||
|
captchaEnabled = false;
|
||||||
|
//验证码类型
|
||||||
|
captchaType?: string;
|
||||||
|
captchaAddonId?:number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SysPrivateSettings extends BaseSettings {
|
export class SysPrivateSettings extends BaseSettings {
|
||||||
@@ -207,4 +214,3 @@ export class SysSafeSetting extends BaseSettings {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
96
packages/libs/lib-server/src/user/addon/api/api.ts
Normal file
96
packages/libs/lib-server/src/user/addon/api/api.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||||
|
import {upperFirst} from "lodash-es";
|
||||||
|
import { FormItemProps, PluginRequestHandleReq, Registrable } from "@certd/pipeline";
|
||||||
|
|
||||||
|
|
||||||
|
export type AddonRequestHandleReqInput<T = any> = {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
addon: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddonRequestHandleReq<T = any> = {
|
||||||
|
addonType: string;
|
||||||
|
} &PluginRequestHandleReq<AddonRequestHandleReqInput<T>>;
|
||||||
|
|
||||||
|
export type AddonInputDefine = FormItemProps & {
|
||||||
|
title: string;
|
||||||
|
required?: boolean;
|
||||||
|
};
|
||||||
|
export type AddonDefine = Registrable & {
|
||||||
|
addonType: string;
|
||||||
|
needPlus?: boolean;
|
||||||
|
input?: {
|
||||||
|
[key: string]: AddonInputDefine;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddonInstanceConfig = {
|
||||||
|
id: number;
|
||||||
|
addonType: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
userId: number;
|
||||||
|
setting: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface IAddon {
|
||||||
|
ctx: AddonContext;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddonContext = {
|
||||||
|
http: HttpClient;
|
||||||
|
logger: ILogger;
|
||||||
|
utils: typeof utils;
|
||||||
|
};
|
||||||
|
|
||||||
|
export abstract class BaseAddon implements IAddon {
|
||||||
|
define!: AddonDefine;
|
||||||
|
ctx!: AddonContext;
|
||||||
|
http!: HttpClient;
|
||||||
|
logger!: ILogger;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
async onInstance() {}
|
||||||
|
setCtx(ctx: AddonContext) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.http = ctx.http;
|
||||||
|
this.logger = ctx.logger;
|
||||||
|
}
|
||||||
|
setDefine = (define:AddonDefine) => {
|
||||||
|
this.define = define;
|
||||||
|
};
|
||||||
|
|
||||||
|
async onRequest(req:AddonRequestHandleReq) {
|
||||||
|
if (!req.action) {
|
||||||
|
throw new Error("action is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
let methodName = req.action;
|
||||||
|
if (!req.action.startsWith("on")) {
|
||||||
|
methodName = `on${upperFirst(req.action)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const method = this[methodName];
|
||||||
|
if (method) {
|
||||||
|
// @ts-ignore
|
||||||
|
return await this[methodName](req.data);
|
||||||
|
}
|
||||||
|
throw new Error(`action ${req.action} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IAddonGetter {
|
||||||
|
getById<T = any>(id: any): Promise<T>;
|
||||||
|
getCommonById<T = any>(id: any): Promise<T>;
|
||||||
|
}
|
||||||
65
packages/libs/lib-server/src/user/addon/api/decorator.ts
Normal file
65
packages/libs/lib-server/src/user/addon/api/decorator.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// src/decorator/memoryCache.decorator.ts
|
||||||
|
import * as _ from "lodash-es";
|
||||||
|
import { merge } from "lodash-es";
|
||||||
|
import { addonRegistry } from "./registry.js";
|
||||||
|
import { AddonContext, AddonDefine, AddonInputDefine } from "./api.js";
|
||||||
|
import { Decorator } from "@certd/pipeline";
|
||||||
|
|
||||||
|
// 提供一个唯一 key
|
||||||
|
export const ADDON_CLASS_KEY = "pipeline:addon";
|
||||||
|
export const ADDON_INPUT_KEY = "pipeline:addon:input";
|
||||||
|
|
||||||
|
export function IsAddon(define: AddonDefine): ClassDecorator {
|
||||||
|
return (target: any) => {
|
||||||
|
target = Decorator.target(target);
|
||||||
|
|
||||||
|
const inputs: any = {};
|
||||||
|
const properties = Decorator.getClassProperties(target);
|
||||||
|
for (const property in properties) {
|
||||||
|
const input = Reflect.getMetadata(ADDON_INPUT_KEY, target, property);
|
||||||
|
if (input) {
|
||||||
|
inputs[property] = input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_.merge(define, { input: inputs });
|
||||||
|
Reflect.defineMetadata(ADDON_CLASS_KEY, define, target);
|
||||||
|
target.define = define;
|
||||||
|
const key = `${define.addonType}:${define.name}`;
|
||||||
|
addonRegistry.register(key, {
|
||||||
|
define,
|
||||||
|
target: async () => {
|
||||||
|
return target;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddonInput(input?: AddonInputDefine): PropertyDecorator {
|
||||||
|
return (target, propertyKey) => {
|
||||||
|
target = Decorator.target(target, propertyKey);
|
||||||
|
// const _type = Reflect.getMetadata("design:type", target, propertyKey);
|
||||||
|
Reflect.defineMetadata(ADDON_INPUT_KEY, input, target, propertyKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function newAddon(addonType:string,type: string, input: any, ctx: AddonContext) {
|
||||||
|
const key = `${addonType}:${type}`
|
||||||
|
const register = addonRegistry.get(key);
|
||||||
|
if (register == null) {
|
||||||
|
throw new Error(`${addonType} ${type} not found`);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const pluginCls = await register.target();
|
||||||
|
// @ts-ignore
|
||||||
|
const plugin = new pluginCls();
|
||||||
|
merge(plugin, input);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("ctx is required");
|
||||||
|
}
|
||||||
|
plugin.setDefine(register.define);
|
||||||
|
plugin.setCtx(ctx);
|
||||||
|
await plugin.onInstance();
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
3
packages/libs/lib-server/src/user/addon/api/index.ts
Normal file
3
packages/libs/lib-server/src/user/addon/api/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./api.js";
|
||||||
|
export * from "./registry.js";
|
||||||
|
export * from "./decorator.js";
|
||||||
3
packages/libs/lib-server/src/user/addon/api/registry.ts
Normal file
3
packages/libs/lib-server/src/user/addon/api/registry.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createRegistry } from "@certd/pipeline";
|
||||||
|
|
||||||
|
export const addonRegistry = createRegistry("addon");
|
||||||
44
packages/libs/lib-server/src/user/addon/entity/addon.ts
Normal file
44
packages/libs/lib-server/src/user/addon/entity/addon.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@Entity('cd_addon')
|
||||||
|
export class AddonEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
@Column({ name: 'user_id', comment: '用户id' })
|
||||||
|
userId: number;
|
||||||
|
@Column({ comment: '名称', length: 100 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
|
||||||
|
@Column({ name: 'addon_type', comment: 'addon类型', length: 100 })
|
||||||
|
addonType: string;
|
||||||
|
|
||||||
|
|
||||||
|
@Column({ comment: '类型', length: 100 })
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@Column({ name: 'setting', comment: '设置', length: 10240, nullable: true })
|
||||||
|
setting: string;
|
||||||
|
|
||||||
|
@Column({ name: 'is_system', comment: '是否系统级别', nullable: false, default: false })
|
||||||
|
isSystem: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_default', comment: '是否默认', nullable: false, default: false })
|
||||||
|
isDefault: boolean;
|
||||||
|
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'create_time',
|
||||||
|
comment: '创建时间',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
})
|
||||||
|
createTime: Date;
|
||||||
|
@Column({
|
||||||
|
name: 'update_time',
|
||||||
|
comment: '修改时间',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
})
|
||||||
|
updateTime: Date;
|
||||||
|
}
|
||||||
5
packages/libs/lib-server/src/user/addon/index.ts
Normal file
5
packages/libs/lib-server/src/user/addon/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './api/index.js'
|
||||||
|
export * from './entity/addon.js'
|
||||||
|
export * from './service/addon-service.js'
|
||||||
|
export * from './service/addon-getter.js'
|
||||||
|
export * from './service/addon-sys-getter.js'
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { IAddonGetter } from "../api/index.js";
|
||||||
|
|
||||||
|
export class AddonGetter implements IAddonGetter {
|
||||||
|
userId: number;
|
||||||
|
getter: <T>(id: any, userId?: number) => Promise<T>;
|
||||||
|
constructor(userId: number, getter: (id: any, userId: number) => Promise<any>) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.getter = getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById<T = any>(id: any) {
|
||||||
|
return await this.getter<T>(id, this.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCommonById<T = any>(id: any) {
|
||||||
|
return await this.getter<T>(id, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
231
packages/libs/lib-server/src/user/addon/service/addon-service.ts
Normal file
231
packages/libs/lib-server/src/user/addon/service/addon-service.ts
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||||
|
import { In, Repository } from "typeorm";
|
||||||
|
import { AddonDefine, BaseService, PageReq, PermissionException, ValidateException } from "../../../index.js";
|
||||||
|
import { addonRegistry, newAddon } from "../api/index.js";
|
||||||
|
import { AddonEntity } from "../entity/addon.js";
|
||||||
|
import { http, logger, utils } from "@certd/basic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addon
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||||
|
export class AddonService extends BaseService<AddonEntity> {
|
||||||
|
@InjectEntityModel(AddonEntity)
|
||||||
|
repository: Repository<AddonEntity>;
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
getRepository() {
|
||||||
|
return this.repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
async page(pageReq: PageReq<AddonEntity>) {
|
||||||
|
const res = await super.page(pageReq);
|
||||||
|
res.records = res.records.map(item => {
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(param) {
|
||||||
|
let oldEntity = null;
|
||||||
|
if (param._copyFrom){
|
||||||
|
oldEntity = await this.info(param._copyFrom);
|
||||||
|
if (oldEntity == null) {
|
||||||
|
throw new ValidateException('该Addon配置不存在,请确认是否已被删除');
|
||||||
|
}
|
||||||
|
if (oldEntity.userId !== param.userId) {
|
||||||
|
throw new ValidateException('您无权查看该Addon配置');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!param.userId){
|
||||||
|
param.isSystem = true
|
||||||
|
}else{
|
||||||
|
param.isSystem = false
|
||||||
|
}
|
||||||
|
delete param._copyFrom
|
||||||
|
return await super.add(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
* @param param 数据
|
||||||
|
*/
|
||||||
|
async update(param) {
|
||||||
|
const oldEntity = await this.info(param.id);
|
||||||
|
if (oldEntity == null) {
|
||||||
|
throw new ValidateException('该Addon配置不存在,请确认是否已被删除');
|
||||||
|
}
|
||||||
|
return await super.update(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSimpleInfo(id: number) {
|
||||||
|
const entity = await this.info(id);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new ValidateException('该Addon配置不存在,请确认是否已被删除');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name,
|
||||||
|
userId: entity.userId,
|
||||||
|
addonType: entity.addonType,
|
||||||
|
type: entity.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAddonById(id: any, checkUserId: boolean, userId?: number): Promise<any> {
|
||||||
|
const ctx = {
|
||||||
|
http: http,
|
||||||
|
logger: logger,
|
||||||
|
utils: utils,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (!id){
|
||||||
|
//使用图片验证码
|
||||||
|
return await newAddon("captcha", "image", {},ctx);
|
||||||
|
}
|
||||||
|
const entity = await this.info(id);
|
||||||
|
if (entity == null) {
|
||||||
|
//使用图片验证码
|
||||||
|
return await newAddon("captcha", "image", {},ctx);
|
||||||
|
}
|
||||||
|
if (checkUserId) {
|
||||||
|
if (userId == null) {
|
||||||
|
throw new ValidateException('userId不能为空');
|
||||||
|
}
|
||||||
|
if (userId !== entity.userId) {
|
||||||
|
throw new PermissionException('您对该Addon无访问权限');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setting = JSON.parse(entity.setting ??"{}")
|
||||||
|
const input = {
|
||||||
|
id: entity.id,
|
||||||
|
...setting,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await newAddon(entity.addonType, entity.type, input,ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: any, userId: number): Promise<any> {
|
||||||
|
return await this.getAddonById(id, true, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getDefineList(addonType: string) {
|
||||||
|
return addonRegistry.getDefineList();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefineByType(type: string,prefix?: string) {
|
||||||
|
return addonRegistry.getDefine(type,prefix) as AddonDefine;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getSimpleByIds(ids: number[], userId: any) {
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!userId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return await this.repository.find({
|
||||||
|
where: {
|
||||||
|
id: In(ids),
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
addonType: true,
|
||||||
|
type: true,
|
||||||
|
userId:true,
|
||||||
|
isSystem: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async getDefault(userId: number,addonType: string): Promise<any> {
|
||||||
|
const res = await this.repository.findOne({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
addonType
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
isDefault: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!res) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.buildAddonInstanceConfig(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildAddonInstanceConfig(res: AddonEntity) {
|
||||||
|
const setting = JSON.parse(res.setting);
|
||||||
|
return {
|
||||||
|
id: res.id,
|
||||||
|
addonType: res.addonType,
|
||||||
|
type: res.type,
|
||||||
|
name: res.name,
|
||||||
|
userId: res.userId,
|
||||||
|
setting,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDefault(id: number, userId: number,addonType:string) {
|
||||||
|
if (!id) {
|
||||||
|
throw new ValidateException('id不能为空');
|
||||||
|
}
|
||||||
|
if (!userId) {
|
||||||
|
throw new ValidateException('userId不能为空');
|
||||||
|
}
|
||||||
|
await this.repository.update(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
addonType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDefault: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await this.repository.update(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
addonType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDefault: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrCreateDefault(opts:{addonType:string,type:string, inputs: any, userId: any}) {
|
||||||
|
const {addonType,type,inputs,userId} = opts;
|
||||||
|
|
||||||
|
const addonDefine = this.getDefineByType( type,addonType)
|
||||||
|
|
||||||
|
const defaultConfig = await this.getDefault(userId,addonType);
|
||||||
|
if (defaultConfig) {
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
const setting = {
|
||||||
|
...inputs,
|
||||||
|
};
|
||||||
|
const res = await this.repository.save({
|
||||||
|
userId,
|
||||||
|
addonType,
|
||||||
|
type: type,
|
||||||
|
name: addonDefine.title,
|
||||||
|
setting: JSON.stringify(setting),
|
||||||
|
isDefault: true,
|
||||||
|
});
|
||||||
|
return this.buildAddonInstanceConfig(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { IAccessService } from '@certd/pipeline';
|
||||||
|
import { AddonService } from './addon-service.js';
|
||||||
|
|
||||||
|
export class AddonSysGetter implements IAccessService {
|
||||||
|
addonService: AddonService;
|
||||||
|
constructor(addonService: AddonService) {
|
||||||
|
this.addonService = addonService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById<T = any>(id: any) {
|
||||||
|
return await this.addonService.getById(id, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCommonById<T = any>(id: any) {
|
||||||
|
return await this.addonService.getById(id, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from './access/index.js';
|
export * from './access/index.js';
|
||||||
|
export * from './addon/index.js';
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/midway-flyway-js",
|
"name": "@certd/midway-flyway-js",
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -46,5 +46,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,26 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 支持ssl.com证书颁发机构 ([27b6dfa](https://github.com/certd/certd/commit/27b6dfa4d2ab3bddd284c3a34511a72e1a513a4c))
|
||||||
|
* 子域名托管说明 ([39a0223](https://github.com/certd/certd/commit/39a02235cf4416bb5bd1acd3831241efeaa2f602))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
|
||||||
|
* openapi返回证书时挑选匹配范围最小的那一个;增加format参数,增加返回值p7b格式,增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 证书申请任务默认不发送申请成功通知 ([0283bd2](https://github.com/certd/certd/commit/0283bd2f978dbcd13d361129135e439dd9fbc180))
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-cert",
|
"name": "@certd/plugin-cert",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.36.16",
|
"@certd/acme-client": "^1.36.19",
|
||||||
"@certd/basic": "^1.36.16",
|
"@certd/basic": "^1.36.19",
|
||||||
"@certd/pipeline": "^1.36.16",
|
"@certd/pipeline": "^1.36.19",
|
||||||
"@certd/plugin-lib": "^1.36.16",
|
"@certd/plugin-lib": "^1.36.19",
|
||||||
"@google-cloud/publicca": "^1.3.0",
|
"@google-cloud/publicca": "^1.3.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
@@ -43,5 +43,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class EabAccess extends BaseAccess {
|
|||||||
component: {
|
component: {
|
||||||
placeholder: "kid / keyId",
|
placeholder: "kid / keyId",
|
||||||
},
|
},
|
||||||
helper: "EAB KID, google的叫 keyId",
|
helper: "EAB KID, google的叫 keyId,ssl.com的叫Account/ACME Key",
|
||||||
required: true,
|
required: true,
|
||||||
encrypt: true,
|
encrypt: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -48,8 +48,9 @@ export type CertInfo = {
|
|||||||
der?: string;
|
der?: string;
|
||||||
jks?: string;
|
jks?: string;
|
||||||
one?: string;
|
one?: string;
|
||||||
|
p7b?: string;
|
||||||
};
|
};
|
||||||
export type SSLProvider = "letsencrypt" | "google" | "zerossl";
|
export type SSLProvider = "letsencrypt" | "google" | "zerossl" | "sslcom";
|
||||||
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";
|
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";
|
||||||
type AcmeServiceOptions = {
|
type AcmeServiceOptions = {
|
||||||
userContext: IContext;
|
userContext: IContext;
|
||||||
@@ -328,8 +329,9 @@ export class AcmeService {
|
|||||||
isTest?: boolean;
|
isTest?: boolean;
|
||||||
privateKeyType?: string;
|
privateKeyType?: string;
|
||||||
profile?: string;
|
profile?: string;
|
||||||
|
preferredChain?: string;
|
||||||
}): Promise<CertInfo> {
|
}): Promise<CertInfo> {
|
||||||
const { email, isTest, csrInfo, dnsProvider, domainsVerifyPlan, profile } = options;
|
const { email, isTest, csrInfo, dnsProvider, domainsVerifyPlan, profile, preferredChain } = options;
|
||||||
const client: acme.Client = await this.getAcmeClient(email, isTest);
|
const client: acme.Client = await this.getAcmeClient(email, isTest);
|
||||||
|
|
||||||
let domains = options.domains;
|
let domains = options.domains;
|
||||||
@@ -372,6 +374,7 @@ export class AcmeService {
|
|||||||
commonName,
|
commonName,
|
||||||
...csrInfo,
|
...csrInfo,
|
||||||
altNames,
|
altNames,
|
||||||
|
emailAddress: email,
|
||||||
},
|
},
|
||||||
privateKey
|
privateKey
|
||||||
);
|
);
|
||||||
@@ -402,6 +405,7 @@ export class AcmeService {
|
|||||||
},
|
},
|
||||||
signal: this.options.signal,
|
signal: this.options.signal,
|
||||||
profile,
|
profile,
|
||||||
|
preferredChain,
|
||||||
});
|
});
|
||||||
|
|
||||||
const crtString = crt.toString();
|
const crtString = crt.toString();
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
|
|||||||
"2、子域名被通配符包含的不要填写,例如:www.foo.com已经被*.foo.com包含,不要填写www.foo.com\n" +
|
"2、子域名被通配符包含的不要填写,例如:www.foo.com已经被*.foo.com包含,不要填写www.foo.com\n" +
|
||||||
"3、泛域名只能通配*号那一级(*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com)\n" +
|
"3、泛域名只能通配*号那一级(*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com)\n" +
|
||||||
"4、输入一个,空格之后,再输入下一个 \n" +
|
"4、输入一个,空格之后,再输入下一个 \n" +
|
||||||
"5、如果您配置了子域托管解析,请先[设置托管子域名](#/certd/pipeline/subDomain)",
|
"5、如果设置了子域托管解析(比如免费的二级域名托管在CF或者阿里云),请先[设置托管子域名](#/certd/pipeline/subDomain)",
|
||||||
})
|
})
|
||||||
domains!: string[];
|
domains!: string[];
|
||||||
|
|
||||||
@@ -125,6 +125,10 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
|
|||||||
cert.jks = res.jks;
|
cert.jks = res.jks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cert.p7b == null && res.p7b) {
|
||||||
|
cert.p7b = res.p7b;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.info("转换证书格式成功");
|
this.logger.info("转换证书格式成功");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error("转换证书格式失败", e);
|
this.logger.error("转换证书格式失败", e);
|
||||||
@@ -150,6 +154,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
|
|||||||
zip.file("intermediate.crt", cert.ic);
|
zip.file("intermediate.crt", cert.ic);
|
||||||
zip.file("origin.crt", cert.oc);
|
zip.file("origin.crt", cert.oc);
|
||||||
zip.file("one.pem", cert.one);
|
zip.file("one.pem", cert.one);
|
||||||
|
zip.file("cert.p7b", cert.p7b);
|
||||||
if (cert.pfx) {
|
if (cert.pfx) {
|
||||||
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
|
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
|||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "证书申请成功通知",
|
title: "证书申请成功通知",
|
||||||
value: true,
|
value: false,
|
||||||
component: {
|
component: {
|
||||||
name: "a-switch",
|
name: "a-switch",
|
||||||
vModel: "checked",
|
vModel: "checked",
|
||||||
@@ -41,7 +41,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
|||||||
order: 100,
|
order: 100,
|
||||||
helper: "证书申请成功后是否发送通知,优先使用默认通知渠道",
|
helper: "证书申请成功后是否发送通知,优先使用默认通知渠道",
|
||||||
})
|
})
|
||||||
successNotify = true;
|
successNotify = false;
|
||||||
|
|
||||||
// @TaskInput({
|
// @TaskInput({
|
||||||
// title: "CsrInfo",
|
// title: "CsrInfo",
|
||||||
|
|||||||
@@ -17,9 +17,19 @@ export type CertReaderHandleContext = {
|
|||||||
tmpIcPath?: string;
|
tmpIcPath?: string;
|
||||||
tmpJksPath?: string;
|
tmpJksPath?: string;
|
||||||
tmpOnePath?: string;
|
tmpOnePath?: string;
|
||||||
|
tmpP7bPath?: string;
|
||||||
};
|
};
|
||||||
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
|
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
|
||||||
export type HandleOpts = { logger: ILogger; handle: CertReaderHandle };
|
export type HandleOpts = { logger: ILogger; handle: CertReaderHandle };
|
||||||
|
|
||||||
|
const formats = {
|
||||||
|
pem: ["crt", "key", "ic"],
|
||||||
|
one: ["one"],
|
||||||
|
pfx: ["pfx"],
|
||||||
|
der: ["der"],
|
||||||
|
jks: ["jks"],
|
||||||
|
p7b: ["p7b", "key"],
|
||||||
|
};
|
||||||
export class CertReader {
|
export class CertReader {
|
||||||
cert: CertInfo;
|
cert: CertInfo;
|
||||||
|
|
||||||
@@ -73,10 +83,19 @@ export class CertReader {
|
|||||||
return arr[0] + endStr;
|
return arr[0] + endStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
toCertInfo(): CertInfo {
|
toCertInfo(format?: string): CertInfo {
|
||||||
|
if (!format) {
|
||||||
return this.cert;
|
return this.cert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatArr = formats[format];
|
||||||
|
const res: any = {};
|
||||||
|
formatArr.forEach((key: string) => {
|
||||||
|
res[key] = this.cert[key];
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
getCrtDetail(crt: string = this.cert.crt) {
|
getCrtDetail(crt: string = this.cert.crt) {
|
||||||
return CertReader.readCertDetail(crt);
|
return CertReader.readCertDetail(crt);
|
||||||
}
|
}
|
||||||
@@ -124,7 +143,7 @@ export class CertReader {
|
|||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToFile(type: "crt" | "key" | "pfx" | "der" | "oc" | "one" | "ic" | "jks", filepath?: string) {
|
saveToFile(type: "crt" | "key" | "pfx" | "der" | "oc" | "one" | "ic" | "jks" | "p7b", filepath?: string) {
|
||||||
if (!this.cert[type]) {
|
if (!this.cert[type]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -138,7 +157,7 @@ export class CertReader {
|
|||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
if (type === "crt" || type === "key" || type === "ic" || type === "oc" || type === "one") {
|
if (type === "crt" || type === "key" || type === "ic" || type === "oc" || type === "one" || type === "p7b") {
|
||||||
fs.writeFileSync(filepath, this.cert[type]);
|
fs.writeFileSync(filepath, this.cert[type]);
|
||||||
} else {
|
} else {
|
||||||
fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64"));
|
fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64"));
|
||||||
@@ -157,17 +176,19 @@ export class CertReader {
|
|||||||
const tmpDerPath = this.saveToFile("der");
|
const tmpDerPath = this.saveToFile("der");
|
||||||
const tmpJksPath = this.saveToFile("jks");
|
const tmpJksPath = this.saveToFile("jks");
|
||||||
const tmpOnePath = this.saveToFile("one");
|
const tmpOnePath = this.saveToFile("one");
|
||||||
|
const tmpP7bPath = this.saveToFile("p7b");
|
||||||
logger.info("本地文件写入成功");
|
logger.info("本地文件写入成功");
|
||||||
try {
|
try {
|
||||||
return await opts.handle({
|
return await opts.handle({
|
||||||
reader: this,
|
reader: this,
|
||||||
tmpCrtPath: tmpCrtPath,
|
tmpCrtPath,
|
||||||
tmpKeyPath: tmpKeyPath,
|
tmpKeyPath,
|
||||||
tmpPfxPath: tmpPfxPath,
|
tmpPfxPath,
|
||||||
tmpDerPath: tmpDerPath,
|
tmpDerPath,
|
||||||
tmpIcPath: tmpIcPath,
|
tmpIcPath,
|
||||||
tmpJksPath: tmpJksPath,
|
tmpJksPath,
|
||||||
tmpOcPath: tmpOcPath,
|
tmpOcPath,
|
||||||
|
tmpP7bPath,
|
||||||
tmpOnePath,
|
tmpOnePath,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -189,6 +210,7 @@ export class CertReader {
|
|||||||
removeFile(tmpIcPath);
|
removeFile(tmpIcPath);
|
||||||
removeFile(tmpJksPath);
|
removeFile(tmpJksPath);
|
||||||
removeFile(tmpOnePath);
|
removeFile(tmpOnePath);
|
||||||
|
removeFile(tmpP7bPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,10 +221,10 @@ export class CertReader {
|
|||||||
return `${prefix}_${domain}_${timeStr}.${suffix}`;
|
return `${prefix}_${domain}_${timeStr}.${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCertName() {
|
buildCertName(prefix: string = "") {
|
||||||
let domain = this.getMainDomain();
|
let domain = this.getMainDomain();
|
||||||
domain = domain.replaceAll(".", "_").replaceAll("*", "_");
|
domain = domain.replaceAll(".", "_").replaceAll("*", "_");
|
||||||
return `${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`;
|
return `${prefix}_${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static appendTimeSuffix(name?: string) {
|
static appendTimeSuffix(name?: string) {
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ export class CertConverter {
|
|||||||
pfx: string;
|
pfx: string;
|
||||||
der: string;
|
der: string;
|
||||||
jks: string;
|
jks: string;
|
||||||
|
p7b: string;
|
||||||
}> {
|
}> {
|
||||||
const certReader = new CertReader(opts.cert);
|
const certReader = new CertReader(opts.cert);
|
||||||
let pfx: string;
|
let pfx: string;
|
||||||
let der: string;
|
let der: string;
|
||||||
let jks: string;
|
let jks: string;
|
||||||
|
let p7b: string;
|
||||||
const handle = async (ctx: CertReaderHandleContext) => {
|
const handle = async (ctx: CertReaderHandleContext) => {
|
||||||
// 调用openssl 转pfx
|
// 调用openssl 转pfx
|
||||||
pfx = await this.convertPfx(ctx, opts.pfxPassword, opts.pfxArgs);
|
pfx = await this.convertPfx(ctx, opts.pfxPassword, opts.pfxArgs);
|
||||||
@@ -31,6 +33,8 @@ export class CertConverter {
|
|||||||
der = await this.convertDer(ctx);
|
der = await this.convertDer(ctx);
|
||||||
|
|
||||||
jks = await this.convertJks(ctx, opts.pfxPassword);
|
jks = await this.convertJks(ctx, opts.pfxPassword);
|
||||||
|
|
||||||
|
p7b = await this.convertP7b(ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
await certReader.readCertFile({ logger: this.logger, handle });
|
await certReader.readCertFile({ logger: this.logger, handle });
|
||||||
@@ -39,6 +43,7 @@ export class CertConverter {
|
|||||||
pfx,
|
pfx,
|
||||||
der,
|
der,
|
||||||
jks,
|
jks,
|
||||||
|
p7b,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +100,23 @@ export class CertConverter {
|
|||||||
return derCert;
|
return derCert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async convertP7b(opts: CertReaderHandleContext) {
|
||||||
|
const { tmpCrtPath } = opts;
|
||||||
|
const p7bPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + `_cert.p7b`);
|
||||||
|
const dir = path.dirname(p7bPath);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
//openssl crl2pkcs7 -nocrl \
|
||||||
|
// -certfile your_domain.crt \
|
||||||
|
// -certfile intermediate.crt \
|
||||||
|
// -out chain.p7b
|
||||||
|
await this.exec(`openssl crl2pkcs7 -nocrl -certfile ${tmpCrtPath} -out ${p7bPath}`);
|
||||||
|
const fileBuffer = fs.readFileSync(p7bPath);
|
||||||
|
const p7bCert = fileBuffer.toString();
|
||||||
|
fs.unlinkSync(p7bPath);
|
||||||
|
return p7bCert;
|
||||||
|
}
|
||||||
async convertJks(opts: CertReaderHandleContext, pfxPassword = "") {
|
async convertJks(opts: CertReaderHandleContext, pfxPassword = "") {
|
||||||
const jksPassword = pfxPassword || "123456";
|
const jksPassword = pfxPassword || "123456";
|
||||||
try {
|
try {
|
||||||
@@ -113,9 +135,7 @@ export class CertConverter {
|
|||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
await this.exec(
|
await this.exec(`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `);
|
||||||
`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `
|
|
||||||
);
|
|
||||||
fs.unlinkSync(p12Path);
|
fs.unlinkSync(p12Path);
|
||||||
|
|
||||||
const fileBuffer = fs.readFileSync(jksPath);
|
const fileBuffer = fs.readFileSync(jksPath);
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
{ value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" },
|
{ value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" },
|
||||||
{ value: "google", label: "Google", icon: "flat-color-icons:google" },
|
{ value: "google", label: "Google", icon: "flat-color-icons:google" },
|
||||||
{ value: "zerossl", label: "ZeroSSL", icon: "emojione:digit-zero" },
|
{ value: "zerossl", label: "ZeroSSL", icon: "emojione:digit-zero" },
|
||||||
|
{ value: "sslcom", label: "SSL.com(仅主域名和www免费)", icon: "la:expeditedssl" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
helper: "Let's Encrypt:申请最简单\nGoogle:大厂光环,兼容性好,仅首次需要翻墙获取EAB授权\nZeroSSL:需要EAB授权,无需翻墙",
|
helper: "Let's Encrypt:申请最简单\nGoogle:大厂光环,兼容性好,仅首次需要翻墙获取EAB授权\nZeroSSL:需要EAB授权,无需翻墙",
|
||||||
@@ -194,6 +195,13 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
})
|
})
|
||||||
zerosslCommonEabAccessId!: number;
|
zerosslCommonEabAccessId!: number;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "SSL.com公共EAB授权",
|
||||||
|
isSys: true,
|
||||||
|
show: false,
|
||||||
|
})
|
||||||
|
sslcomCommonEabAccessId!: number;
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "EAB授权",
|
title: "EAB授权",
|
||||||
component: {
|
component: {
|
||||||
@@ -203,11 +211,16 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
maybeNeed: true,
|
maybeNeed: true,
|
||||||
required: false,
|
required: false,
|
||||||
helper:
|
helper:
|
||||||
"需要提供EAB授权\nZeroSSL:请前往[zerossl开发者中心](https://app.zerossl.com/developer),生成 'EAB Credentials'\n Google:请查看[google获取eab帮助文档](https://certd.docmirror.cn/guide/use/google/),用过一次后会绑定邮箱,后续复用EAB要用同一个邮箱",
|
"需要提供EAB授权" +
|
||||||
|
"\nZeroSSL:请前往[zerossl开发者中心](https://app.zerossl.com/developer),生成 'EAB Credentials'" +
|
||||||
|
"\nGoogle:请查看[google获取eab帮助文档](https://certd.docmirror.cn/guide/use/google/),用过一次后会绑定邮箱,后续复用EAB要用同一个邮箱" +
|
||||||
|
"\nSSL.com:[SSL.com账号页面](https://secure.ssl.com/account),然后点击api credentials链接,然后点击编辑按钮,查看Secret key和HMAC key",
|
||||||
mergeScript: `
|
mergeScript: `
|
||||||
return {
|
return {
|
||||||
show: ctx.compute(({form})=>{
|
show: ctx.compute(({form})=>{
|
||||||
return (form.sslProvider === 'zerossl' && !form.zerosslCommonEabAccessId) || (form.sslProvider === 'google' && !form.googleCommonEabAccessId)
|
return (form.sslProvider === 'zerossl' && !form.zerosslCommonEabAccessId)
|
||||||
|
|| (form.sslProvider === 'google' && !form.googleCommonEabAccessId)
|
||||||
|
|| (form.sslProvider === 'sslcom' && !form.sslcomCommonEabAccessId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -279,6 +292,29 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
})
|
})
|
||||||
certProfile!: string;
|
certProfile!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "首选链",
|
||||||
|
value: "ISRG Root X1",
|
||||||
|
component: {
|
||||||
|
name: "a-select",
|
||||||
|
vModel: "value",
|
||||||
|
options: [
|
||||||
|
{ value: "ISRG Root X1", label: "ISRG Root X1" },
|
||||||
|
{ value: "ISRG Root X2", label: "ISRG Root X2" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
helper: "仅 Let's Encrypt 可选,默认为 ISRG Root X1",
|
||||||
|
required: false,
|
||||||
|
mergeScript: `
|
||||||
|
return {
|
||||||
|
show: ctx.compute(({form})=>{
|
||||||
|
return form.sslProvider === 'letsencrypt'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
preferredChain!: string;
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "使用代理",
|
title: "使用代理",
|
||||||
value: false,
|
value: false,
|
||||||
@@ -339,8 +375,8 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
async onInit() {
|
async onInit() {
|
||||||
let eab: EabAccess = null;
|
let eab: EabAccess = null;
|
||||||
|
|
||||||
if (this.sslProvider === "google") {
|
if (this.sslProvider !== "letsencrypt") {
|
||||||
if (this.googleAccessId) {
|
if (this.sslProvider === "google" && this.googleAccessId) {
|
||||||
this.logger.info("当前正在使用 google服务账号授权获取EAB");
|
this.logger.info("当前正在使用 google服务账号授权获取EAB");
|
||||||
const googleAccess = await this.getAccess(this.googleAccessId);
|
const googleAccess = await this.getAccess(this.googleAccessId);
|
||||||
const googleClient = new GoogleClient({
|
const googleClient = new GoogleClient({
|
||||||
@@ -348,24 +384,19 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
});
|
});
|
||||||
eab = await googleClient.getEab();
|
eab = await googleClient.getEab();
|
||||||
} else if (this.eabAccessId) {
|
|
||||||
this.logger.info("当前正在使用 google EAB授权");
|
|
||||||
eab = await this.getAccess(this.eabAccessId);
|
|
||||||
} else if (this.googleCommonEabAccessId) {
|
|
||||||
this.logger.info("当前正在使用 google 公共EAB授权");
|
|
||||||
eab = await this.getAccess(this.googleCommonEabAccessId, true);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("google需要配置EAB授权或服务账号授权");
|
const getEab = async (type: string) => {
|
||||||
}
|
|
||||||
} else if (this.sslProvider === "zerossl") {
|
|
||||||
if (this.eabAccessId) {
|
if (this.eabAccessId) {
|
||||||
this.logger.info("当前正在使用 zerossl EAB授权");
|
this.logger.info(`当前正在使用 ${type} EAB授权`);
|
||||||
eab = await this.getAccess(this.eabAccessId);
|
eab = await this.getAccess(this.eabAccessId);
|
||||||
} else if (this.zerosslCommonEabAccessId) {
|
} else if (this[`${type}CommonEabAccessId`]) {
|
||||||
this.logger.info("当前正在使用 zerossl 公共EAB授权");
|
this.logger.info(`当前正在使用 ${type} 公共EAB授权`);
|
||||||
eab = await this.getAccess(this.zerosslCommonEabAccessId, true);
|
eab = await this.getAccess(this[`${type}CommonEabAccessId`], true);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("zerossl需要配置EAB授权");
|
throw new Error(`${type}需要配置EAB授权`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await getEab(this.sslProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.eab = eab;
|
this.eab = eab;
|
||||||
@@ -397,12 +428,12 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
|
|
||||||
const csrInfo = _.merge(
|
const csrInfo = _.merge(
|
||||||
{
|
{
|
||||||
country: "CN",
|
// country: "CN",
|
||||||
state: "GuangDong",
|
// state: "GuangDong",
|
||||||
locality: "ShengZhen",
|
// locality: "ShengZhen",
|
||||||
organization: "CertD Org.",
|
// organization: "CertD Org.",
|
||||||
organizationUnit: "IT Department",
|
// organizationUnit: "IT Department",
|
||||||
emailAddress: email,
|
// emailAddress: email,
|
||||||
},
|
},
|
||||||
this.csrInfo ? JSON.parse(this.csrInfo) : {}
|
this.csrInfo ? JSON.parse(this.csrInfo) : {}
|
||||||
);
|
);
|
||||||
@@ -430,6 +461,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
isTest: false,
|
isTest: false,
|
||||||
privateKeyType: this.privateKeyType,
|
privateKeyType: this.privateKeyType,
|
||||||
profile: this.certProfile,
|
profile: this.certProfile,
|
||||||
|
preferredChain: this.preferredChain,
|
||||||
});
|
});
|
||||||
|
|
||||||
const certInfo = this.formatCerts(cert);
|
const certInfo = this.formatCerts(cert);
|
||||||
|
|||||||
@@ -3,6 +3,26 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* ssh 增加超时断开连接,默认10分钟超时 ([c24a040](https://github.com/certd/certd/commit/c24a040c19cacafc79228d7a7649af93837d94a1))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 腾讯云插件支持国际版 ([58e82d5](https://github.com/certd/certd/commit/58e82d5dbd4ebf089ef239578ef9b68454d17b30))
|
||||||
|
* 支持部署到阿里云云原生API网关、AI网关 ([2ca20be](https://github.com/certd/certd/commit/2ca20be197720201fceabcce9d927f4dbc1cc872))
|
||||||
|
* ssh 配置sudo免密提示 ([e1e7011](https://github.com/certd/certd/commit/e1e7011853ad0c5bd7b09c3690861d5aa34b2db4))
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 腾讯云关闭证书通知增加开关选项,在腾讯云授权里面 ([a77c777](https://github.com/certd/certd/commit/a77c777980dd38d97d983124eeed1596879bba95))
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/plugin-lib
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
"@alicloud/tea-util": "^1.4.10",
|
"@alicloud/tea-util": "^1.4.10",
|
||||||
"@aws-sdk/client-s3": "^3.787.0",
|
"@aws-sdk/client-s3": "^3.787.0",
|
||||||
"@certd/basic": "^1.36.16",
|
"@certd/basic": "^1.36.19",
|
||||||
"@certd/pipeline": "^1.36.16",
|
"@certd/pipeline": "^1.36.19",
|
||||||
"@kubernetes/client-node": "0.21.0",
|
"@kubernetes/client-node": "0.21.0",
|
||||||
"ali-oss": "^6.22.0",
|
"ali-oss": "^6.22.0",
|
||||||
"basic-ftp": "^5.0.5",
|
"basic-ftp": "^5.0.5",
|
||||||
@@ -53,5 +53,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "fb7341f1f7d05d05c5439a36594665e3855d6a00"
|
"gitHead": "6d8981479517b5de9634e242c1ebf22e70527ec4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ export type AliyunClientV2Req = {
|
|||||||
// 接口 HTTP 方法
|
// 接口 HTTP 方法
|
||||||
method?: "GET" | "POST";
|
method?: "GET" | "POST";
|
||||||
authType?: "AK";
|
authType?: "AK";
|
||||||
style?: "RPC";
|
style?: "RPC" | "ROA";
|
||||||
// 接口 PATH
|
// 接口 PATH
|
||||||
pathname?: `/`;
|
pathname?: string;
|
||||||
|
|
||||||
data?: any;
|
data?: any;
|
||||||
};
|
};
|
||||||
@@ -63,10 +63,10 @@ export class AliyunClientV2 {
|
|||||||
protocol: "HTTPS",
|
protocol: "HTTPS",
|
||||||
// 接口 HTTP 方法
|
// 接口 HTTP 方法
|
||||||
method: req.method ?? "POST",
|
method: req.method ?? "POST",
|
||||||
authType: "AK",
|
authType: req.authType ?? "AK",
|
||||||
style: "RPC",
|
style: req.style ?? "RPC",
|
||||||
// 接口 PATH
|
// 接口 PATH
|
||||||
pathname: `/`,
|
pathname: req.pathname ?? `/`,
|
||||||
// 接口请求体内容格式
|
// 接口请求体内容格式
|
||||||
reqBodyType: "json",
|
reqBodyType: "json",
|
||||||
// 接口响应体内容格式
|
// 接口响应体内容格式
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export type AliyunCertInfo = {
|
|||||||
export type AliyunSslClientOpts = {
|
export type AliyunSslClientOpts = {
|
||||||
access: AliyunAccess;
|
access: AliyunAccess;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
endpoint: string;
|
endpoint?: string;
|
||||||
|
region?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AliyunSslGetResourceListReq = {
|
export type AliyunSslGetResourceListReq = {
|
||||||
@@ -48,10 +49,19 @@ export class AliyunSslClient {
|
|||||||
async getClient() {
|
async getClient() {
|
||||||
const access = this.opts.access;
|
const access = this.opts.access;
|
||||||
const client = new AliyunClient({ logger: this.opts.logger });
|
const client = new AliyunClient({ logger: this.opts.logger });
|
||||||
|
|
||||||
|
let endpoint = this.opts.endpoint || "cas.aliyuncs.com";
|
||||||
|
if (this.opts.endpoint == null && this.opts.region) {
|
||||||
|
if (this.opts.region === "cn-hangzhou") {
|
||||||
|
endpoint = "cas.aliyuncs.com";
|
||||||
|
} else {
|
||||||
|
endpoint = `cas.${this.opts.region}.aliyuncs.com`;
|
||||||
|
}
|
||||||
|
}
|
||||||
await client.init({
|
await client.init({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
endpoint: `https://${this.opts.endpoint || "cas.aliyuncs.com"}`,
|
endpoint: `https://${endpoint}`,
|
||||||
apiVersion: "2020-04-07",
|
apiVersion: "2020-04-07",
|
||||||
});
|
});
|
||||||
return client;
|
return client;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
|||||||
title: "天翼云授权",
|
title: "天翼云授权",
|
||||||
desc: "",
|
desc: "",
|
||||||
icon: "ant-design:aliyun-outlined",
|
icon: "ant-design:aliyun-outlined",
|
||||||
|
order: 2,
|
||||||
})
|
})
|
||||||
export class CtyunAccess extends BaseAccess {
|
export class CtyunAccess extends BaseAccess {
|
||||||
@AccessInput({
|
@AccessInput({
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
|||||||
desc: "",
|
desc: "",
|
||||||
icon: "svg:icon-qiniuyun",
|
icon: "svg:icon-qiniuyun",
|
||||||
input: {},
|
input: {},
|
||||||
|
order: 2,
|
||||||
})
|
})
|
||||||
export class QiniuAccess extends BaseAccess {
|
export class QiniuAccess extends BaseAccess {
|
||||||
@AccessInput({
|
@AccessInput({
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
|||||||
desc: "",
|
desc: "",
|
||||||
icon: "clarity:host-line",
|
icon: "clarity:host-line",
|
||||||
input: {},
|
input: {},
|
||||||
|
order: 0,
|
||||||
})
|
})
|
||||||
export class SshAccess extends BaseAccess {
|
export class SshAccess extends BaseAccess {
|
||||||
@AccessInput({
|
@AccessInput({
|
||||||
@@ -63,6 +64,22 @@ export class SshAccess extends BaseAccess {
|
|||||||
})
|
})
|
||||||
passphrase!: string;
|
passphrase!: string;
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "脚本类型",
|
||||||
|
helper: "bash 、sh 、fish",
|
||||||
|
component: {
|
||||||
|
name: "a-select",
|
||||||
|
vModel: "value",
|
||||||
|
options: [
|
||||||
|
{ value: "default", label: "默认" },
|
||||||
|
{ value: "sh", label: "sh" },
|
||||||
|
{ value: "bash", label: "bash" },
|
||||||
|
{ value: "fish", label: "fish(不支持set -e)" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
scriptType: string;
|
||||||
|
|
||||||
@AccessInput({
|
@AccessInput({
|
||||||
title: "伪终端",
|
title: "伪终端",
|
||||||
helper: "如果登录报错:all authentication methods failed,可以尝试开启伪终端模式进行keyboard-interactive方式登录\n开启后对日志输出有一定的影响",
|
helper: "如果登录报错:all authentication methods failed,可以尝试开启伪终端模式进行keyboard-interactive方式登录\n开启后对日志输出有一定的影响",
|
||||||
@@ -85,6 +102,15 @@ export class SshAccess extends BaseAccess {
|
|||||||
})
|
})
|
||||||
socksProxy!: string;
|
socksProxy!: string;
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "超时时间",
|
||||||
|
helper: "执行命令的超时时间,单位秒,默认30分钟",
|
||||||
|
component: {
|
||||||
|
name: "a-input-number",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
timeout: number;
|
||||||
|
|
||||||
@AccessInput({
|
@AccessInput({
|
||||||
title: "是否Windows",
|
title: "是否Windows",
|
||||||
helper: "如果是Windows主机,请勾选此项\n并且需要windows[安装OpenSSH](https://certd.docmirror.cn/guide/use/host/windows.html)",
|
helper: "如果是Windows主机,请勾选此项\n并且需要windows[安装OpenSSH](https://certd.docmirror.cn/guide/use/host/windows.html)",
|
||||||
@@ -135,9 +161,10 @@ export class SshAccess extends BaseAccess {
|
|||||||
const { SshClient } = await import("./ssh.js");
|
const { SshClient } = await import("./ssh.js");
|
||||||
const client = new SshClient(this.ctx.logger);
|
const client = new SshClient(this.ctx.logger);
|
||||||
|
|
||||||
|
const script = ["echo hello", "exit"];
|
||||||
await client.exec({
|
await client.exec({
|
||||||
connectConf: this,
|
connectConf: this,
|
||||||
script: "echo hello",
|
script: script,
|
||||||
});
|
});
|
||||||
return "ok";
|
return "ok";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,6 +247,9 @@ export class AsyncSsh2Client {
|
|||||||
const err = this.convert(iconv, ret);
|
const err = this.convert(iconv, ret);
|
||||||
stdErr += err;
|
stdErr += err;
|
||||||
hasErrorLog = true;
|
hasErrorLog = true;
|
||||||
|
if (err.includes("sudo: a password is required")) {
|
||||||
|
this.logger.warn("请配置sudo免密,否则命令无法执行");
|
||||||
|
}
|
||||||
this.logger.error(`[${this.connConf.host}][error]: ` + err.trimEnd());
|
this.logger.error(`[${this.connConf.host}][error]: ` + err.trimEnd());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -466,7 +469,8 @@ export class SshClient {
|
|||||||
|
|
||||||
async isCmd(conn: AsyncSsh2Client) {
|
async isCmd(conn: AsyncSsh2Client) {
|
||||||
const spec = await conn.exec("echo %COMSPEC% ");
|
const spec = await conn.exec("echo %COMSPEC% ");
|
||||||
if (spec.toString().trim() === "%COMSPEC%") {
|
const ret = spec.toString().trim();
|
||||||
|
if (ret.includes("%COMSPEC%") && !ret.includes("echo %COMSPEC%")) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
@@ -539,9 +543,17 @@ export class SshClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLinux && options.stopOnError !== false) {
|
if (isLinux) {
|
||||||
|
if (options.connectConf.scriptType == "bash") {
|
||||||
|
script = "#!/usr/bin/env bash \n" + script;
|
||||||
|
} else if (options.connectConf.scriptType == "sh") {
|
||||||
|
script = "#!/bin/sh\n" + script;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.connectConf.scriptType != "fish" && options.stopOnError !== false) {
|
||||||
script = "set -e\n" + script;
|
script = "set -e\n" + script;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await conn.exec(script as string, { throwOnStdErr });
|
return await conn.exec(script as string, { throwOnStdErr });
|
||||||
},
|
},
|
||||||
@@ -584,10 +596,15 @@ export class SshClient {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
let timeoutId = null;
|
||||||
try {
|
try {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
this.logger.info("执行超时,断开连接");
|
||||||
|
conn.end();
|
||||||
|
}, 1000 * (connectConf.timeout || 1800));
|
||||||
return await callable(conn);
|
return await callable(conn);
|
||||||
} finally {
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
conn.end();
|
conn.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
|||||||
name: "tencent",
|
name: "tencent",
|
||||||
title: "腾讯云",
|
title: "腾讯云",
|
||||||
icon: "svg:icon-tencentcloud",
|
icon: "svg:icon-tencentcloud",
|
||||||
|
order: 0,
|
||||||
})
|
})
|
||||||
export class TencentAccess extends BaseAccess {
|
export class TencentAccess extends BaseAccess {
|
||||||
@AccessInput({
|
@AccessInput({
|
||||||
@@ -46,7 +47,21 @@ export class TencentAccess extends BaseAccess {
|
|||||||
})
|
})
|
||||||
accountType: string;
|
accountType: string;
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "关闭证书过期通知",
|
||||||
|
value: true,
|
||||||
|
component: {
|
||||||
|
name: "a-switch",
|
||||||
|
vModel: "checked",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
closeExpiresNotify: boolean = true;
|
||||||
|
|
||||||
isIntl() {
|
isIntl() {
|
||||||
return this.accountType === "intl";
|
return this.accountType === "intl";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intlDomain() {
|
||||||
|
return this.isIntl() ? "intl." : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class TencentSslClient {
|
|||||||
region: this.region,
|
region: this.region,
|
||||||
profile: {
|
profile: {
|
||||||
httpProfile: {
|
httpProfile: {
|
||||||
endpoint: "ssl.tencentcloudapi.com",
|
endpoint: this.access.isIntl() ? "ssl.intl.tencentcloudapi.com" : "ssl.tencentcloudapi.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -50,7 +50,10 @@ export class TencentSslClient {
|
|||||||
const ret = await client.UploadCertificate(params);
|
const ret = await client.UploadCertificate(params);
|
||||||
this.checkRet(ret);
|
this.checkRet(ret);
|
||||||
this.logger.info(`证书[${opts.certName}]上传成功:tencentCertId=`, ret.CertificateId);
|
this.logger.info(`证书[${opts.certName}]上传成功:tencentCertId=`, ret.CertificateId);
|
||||||
|
if (this.access.closeExpiresNotify) {
|
||||||
await this.switchCertNotify([ret.CertificateId], true);
|
await this.switchCertNotify([ret.CertificateId], true);
|
||||||
|
}
|
||||||
|
|
||||||
return ret.CertificateId;
|
return ret.CertificateId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,9 +76,26 @@ export class TencentSslClient {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async DescribeCertificates(params: any) {
|
async DescribeHostUploadUpdateRecordDetail(params: any) {
|
||||||
const client = await this.getSslClient();
|
const client = await this.getSslClient();
|
||||||
const res = await client.DescribeCertificates(params);
|
const res = await client.request("DescribeHostUploadUpdateRecordDetail", params);
|
||||||
|
this.checkRet(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async UploadUpdateCertificateInstance(params: any) {
|
||||||
|
const client = await this.getSslClient();
|
||||||
|
const res = await client.request("UploadUpdateCertificateInstance", params);
|
||||||
|
this.checkRet(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async DescribeCertificates(params: { Limit?: number; Offset?: number; SearchKey?: string }) {
|
||||||
|
const client = await this.getSslClient();
|
||||||
|
const res = await client.DescribeCertificates({
|
||||||
|
ExpirationSort: "ASC",
|
||||||
|
...params,
|
||||||
|
});
|
||||||
this.checkRet(res);
|
this.checkRet(res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,39 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.36.19](https://github.com/certd/certd/compare/v1.36.18...v1.36.19) (2025-09-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复批量流水线执行时日志显示错乱的问题 ([4372adc](https://github.com/certd/certd/commit/4372adc703b9a4c785664054ab2a533626d815a8))
|
||||||
|
* 修复远程数据选择无法过滤的bug ([6cbb073](https://github.com/certd/certd/commit/6cbb0739f8428d51b0712f718fe4d236cc087cf9))
|
||||||
|
* 修复mysql下购买套餐加量包无效的bug ([c26ad4c](https://github.com/certd/certd/commit/c26ad4c8075f0606d45b8da13915737968d6191a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 创建证书时支持选择通知时机 ([0e96bfd](https://github.com/certd/certd/commit/0e96bfdfa377824d204e72923d1176408ae6b300))
|
||||||
|
* 商业版隐藏文档相关链接 ([4443a1c](https://github.com/certd/certd/commit/4443a1c0308fa6b95a05efd73d15d24b65d641c9))
|
||||||
|
* 商业版隐藏文档相关链接 ([db89561](https://github.com/certd/certd/commit/db8956148083bc4f988226ccf719940d08158a27))
|
||||||
|
* 支持根据id更新证书(证书Id不变接口),不过该接口为白名单功能,普通腾讯云账户无法使用 ([fe9c4f3](https://github.com/certd/certd/commit/fe9c4f3391ff07c01dd9a252225f69a129c39050))
|
||||||
|
* 子域名托管说明 ([39a0223](https://github.com/certd/certd/commit/39a02235cf4416bb5bd1acd3831241efeaa2f602))
|
||||||
|
|
||||||
|
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复cron选择组件星期显示错误的bug ([eb75e52](https://github.com/certd/certd/commit/eb75e52278f94a72643f7317e6740fb42666c68a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 短信验证码支持腾讯云 ([9108459](https://github.com/certd/certd/commit/9108459ae42bcd95a59acba164a64e82e5f2cfe6))
|
||||||
|
* 商业版支持自定义插件的参数配置 ([17f23f3](https://github.com/certd/certd/commit/17f23f37516af925d5049291d67d41e4271f81f8))
|
||||||
|
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
|
||||||
|
* openapi返回证书时挑选匹配范围最小的那一个;增加format参数,增加返回值p7b格式,增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
|
||||||
|
|
||||||
|
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/ui-client
|
||||||
|
|
||||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -23,5 +23,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
<script src="https://static.geetest.com/v4/gt4.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.36.16",
|
"version": "1.36.19",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
"@aws-sdk/client-s3": "^3.535.0",
|
"@aws-sdk/client-s3": "^3.535.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.535.0",
|
"@aws-sdk/s3-request-presigner": "^3.535.0",
|
||||||
|
"@certd/vue-js-cron-light": "^4.0.14",
|
||||||
"@ctrl/tinycolor": "^4.1.0",
|
"@ctrl/tinycolor": "^4.1.0",
|
||||||
"@fast-crud/fast-crud": "^1.25.13",
|
"@fast-crud/fast-crud": "^1.25.13",
|
||||||
"@fast-crud/fast-extends": "^1.25.13",
|
"@fast-crud/fast-extends": "^1.25.13",
|
||||||
@@ -43,7 +44,6 @@
|
|||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tanstack/vue-store": "^0.7.0",
|
"@tanstack/vue-store": "^0.7.0",
|
||||||
"@vee-validate/zod": "^4.15.0",
|
"@vee-validate/zod": "^4.15.0",
|
||||||
"@vue-js-cron/light": "^4.0.5",
|
|
||||||
"@vue/shared": "^3.5.13",
|
"@vue/shared": "^3.5.13",
|
||||||
"@vueuse/core": "^10.11.0",
|
"@vueuse/core": "^10.11.0",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
@@ -103,8 +103,8 @@
|
|||||||
"zod-defaults": "^0.1.3"
|
"zod-defaults": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.36.16",
|
"@certd/lib-iframe": "^1.36.19",
|
||||||
"@certd/pipeline": "^1.36.16",
|
"@certd/pipeline": "^1.36.19",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="captchaComponent" v-if="settingStore.inited" ref="captchaRef" class="captcha_input" :captcha-get="getCaptcha" @change="onChange" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, defineAsyncComponent } from "vue";
|
||||||
|
import { useSettingStore } from "/@/store/settings";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { request } from "/@/api/service";
|
||||||
|
|
||||||
|
const captchaRef = ref(null);
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
|
const emits = defineEmits(["update:modelValue", "change"]);
|
||||||
|
const captchaImpls = import.meta.glob("./captchas/*.vue");
|
||||||
|
|
||||||
|
const captchaAddonId = computed(() => {
|
||||||
|
return settingStore.sysPublic.captchaAddonId ?? 0;
|
||||||
|
});
|
||||||
|
const captchaComponent = computed(() => {
|
||||||
|
let type = "image";
|
||||||
|
if (settingStore.sysPublic.captchaAddonId && settingStore.sysPublic.captchaType) {
|
||||||
|
type = settingStore.sysPublic.captchaType;
|
||||||
|
}
|
||||||
|
const componentName = `${type}_captcha`;
|
||||||
|
return defineAsyncComponent(captchaImpls[`./captchas/${componentName}.vue`]);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getCaptcha(): Promise<any> {
|
||||||
|
const randomStr = nanoid(10);
|
||||||
|
return await request({
|
||||||
|
url: `/basic/code/captcha/get?randomStr=${randomStr}`,
|
||||||
|
method: "post",
|
||||||
|
data: {
|
||||||
|
captchaAddonId: captchaAddonId.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(data) {
|
||||||
|
emits("update:modelValue", data);
|
||||||
|
emits("change", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCaptchaForm() {
|
||||||
|
return await captchaRef.value.getCaptchaForm();
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
getCaptchaForm,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="captchaRef" class="geetest_captcha_wrapper"></div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, defineProps, defineEmits, ref, onUnmounted } from "vue";
|
||||||
|
import { useSettingStore } from "/@/store/settings";
|
||||||
|
import { request } from "/src/api/service";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "GeetestCaptcha",
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
const props = defineProps<{
|
||||||
|
captchaGet: () => Promise<any>;
|
||||||
|
}>();
|
||||||
|
const captchaRef = ref(null);
|
||||||
|
// const addonApi = createAddonApi();
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
|
const captchaInstanceRef = ref({});
|
||||||
|
async function init() {
|
||||||
|
// if (!initGeetest4) {
|
||||||
|
// await import("https://static.geetest.com/v4/gt4.js");
|
||||||
|
// }
|
||||||
|
|
||||||
|
const { captchaId } = await props.captchaGet();
|
||||||
|
// @ts-ignore
|
||||||
|
initGeetest4(
|
||||||
|
{
|
||||||
|
captchaId: captchaId,
|
||||||
|
},
|
||||||
|
(captcha: any) => {
|
||||||
|
// captcha为验证码实例
|
||||||
|
captcha.appendTo(captchaRef.value); // 调用appendTo将验证码插入到页的某一个元素中,这个元素用户可以自定义
|
||||||
|
captchaInstanceRef.value.instance = captcha;
|
||||||
|
captchaInstanceRef.value.captchaId = captchaId;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaptchaForm() {
|
||||||
|
if (!captchaInstanceRef.value?.instance) {
|
||||||
|
// notification.error({
|
||||||
|
// message: "验证码还未初始化",
|
||||||
|
// });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const result = captchaInstanceRef.value.instance.getValidate();
|
||||||
|
if (!result) {
|
||||||
|
// notification.error({
|
||||||
|
// message: "请先完成验证码验证",
|
||||||
|
// });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result.captcha_id = captchaInstanceRef.value.captchaId;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueRef = ref(null);
|
||||||
|
const timeoutId = setInterval(() => {
|
||||||
|
const form = getCaptchaForm();
|
||||||
|
if (form && valueRef.value != form) {
|
||||||
|
console.log("form", form);
|
||||||
|
valueRef.value = form;
|
||||||
|
emitChange(form);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
});
|
||||||
|
|
||||||
|
function emitChange(value: string) {
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
emit("change", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getCaptchaForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await init();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.geetest_captcha_wrapper {
|
||||||
|
.geetest_captcha {
|
||||||
|
.geetest_holder {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex">
|
||||||
|
<a-input :value="valueRef" placeholder="请输入图片验证码" autocomplete="off" @update:value="onChange">
|
||||||
|
<template #prefix>
|
||||||
|
<fs-icon icon="ion:image-outline"></fs-icon>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
<div class="input-right pointer" title="点击刷新">
|
||||||
|
<img class="image-code" :src="imageCodeSrc" @click="resetImageCode" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineEmits, defineExpose, defineProps, ref } from "vue";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
captchaGet?: () => Promise<any>;
|
||||||
|
}>();
|
||||||
|
defineOptions({
|
||||||
|
name: "ImageCaptcha",
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
|
||||||
|
const valueRef = ref("");
|
||||||
|
const randomStrRef = ref();
|
||||||
|
const imageCodeSrc = ref();
|
||||||
|
async function resetImageCode() {
|
||||||
|
const res = await props.captchaGet();
|
||||||
|
randomStrRef.value = res.randomStr;
|
||||||
|
valueRef.value = "";
|
||||||
|
emitChange(null);
|
||||||
|
imageCodeSrc.value = "data:image/svg+xml," + encodeURIComponent(res.imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaptchaForm() {
|
||||||
|
return {
|
||||||
|
imageCode: valueRef.value,
|
||||||
|
randomStr: randomStrRef.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
resetImageCode,
|
||||||
|
getCaptchaForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
resetImageCode();
|
||||||
|
|
||||||
|
function onChange(value: string) {
|
||||||
|
valueRef.value = value;
|
||||||
|
const form = getCaptchaForm();
|
||||||
|
emitChange(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitChange(value) {
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
emit("change", value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -4,8 +4,8 @@ import vip from "./vip-button/install.js";
|
|||||||
import { CheckCircleOutlined, InfoCircleOutlined, UndoOutlined } from "@ant-design/icons-vue";
|
import { CheckCircleOutlined, InfoCircleOutlined, UndoOutlined } from "@ant-design/icons-vue";
|
||||||
import CronEditor from "./cron-editor/index.vue";
|
import CronEditor from "./cron-editor/index.vue";
|
||||||
import FoldBox from "./fold-box.vue";
|
import FoldBox from "./fold-box.vue";
|
||||||
import { CronLight } from "@vue-js-cron/light";
|
import { CronLight } from "@certd/vue-js-cron-light";
|
||||||
import "@vue-js-cron/light/dist/light.css";
|
import "@certd/vue-js-cron-light/dist/light.css";
|
||||||
import Plugins from "./plugins/index";
|
import Plugins from "./plugins/index";
|
||||||
import LoadingButton from "./loading-button.vue";
|
import LoadingButton from "./loading-button.vue";
|
||||||
import IconSelect from "./icon-select.vue";
|
import IconSelect from "./icon-select.vue";
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const getOptions = async () => {
|
|||||||
const input = (pluginType === "plugin" ? form?.input : form) || {};
|
const input = (pluginType === "plugin" ? form?.input : form) || {};
|
||||||
|
|
||||||
for (let key in define.input) {
|
for (let key in define.input) {
|
||||||
const inWatches = props.watches.includes(key);
|
const inWatches = props.watches?.includes(key);
|
||||||
const inputDefine = define.input[key];
|
const inputDefine = define.input[key];
|
||||||
if (inWatches && inputDefine.required) {
|
if (inWatches && inputDefine.required) {
|
||||||
const value = input[key];
|
const value = input[key];
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const getOptions = async () => {
|
|||||||
const input = (pluginType === "plugin" ? form?.input : form) || {};
|
const input = (pluginType === "plugin" ? form?.input : form) || {};
|
||||||
|
|
||||||
for (let key in define.input) {
|
for (let key in define.input) {
|
||||||
const inWatches = props.watches.includes(key);
|
const inWatches = props.watches?.includes(key);
|
||||||
const inputDefine = define.input[key];
|
const inputDefine = define.input[key];
|
||||||
if (inWatches && inputDefine.required) {
|
if (inWatches && inputDefine.required) {
|
||||||
const value = input[key];
|
const value = input[key];
|
||||||
@@ -169,7 +169,7 @@ const getOptions = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const filterOption = (input: string, option: any) => {
|
const filterOption = (input: string, option: any) => {
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 || String(option.value).toLowerCase().indexOf(input.toLowerCase());
|
return option.label.toLowerCase().includes(input.toLowerCase()) || String(option.value).toLowerCase().includes(input.toLowerCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<div class="remote-select">
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<a-tree-select class="remote-tree-select-input" :tree-data="optionsRef" :value="value" tree-checkable allow-clear v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)"> </a-tree-select>
|
||||||
|
<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: "RemoteTreeSelect",
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<
|
||||||
|
{
|
||||||
|
watches: string[];
|
||||||
|
search?: boolean;
|
||||||
|
pager?: boolean;
|
||||||
|
} & 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 searchKeyRef = ref("");
|
||||||
|
const optionsRef = ref([]);
|
||||||
|
const message = ref("");
|
||||||
|
const hasError = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const pagerRef: Ref = ref({
|
||||||
|
current: 1,
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
const pageNo = pagerRef.value.pageNo;
|
||||||
|
const pageSize = pagerRef.value.pageSize;
|
||||||
|
try {
|
||||||
|
const res = await doRequest(
|
||||||
|
{
|
||||||
|
type: pluginType,
|
||||||
|
typeName: form.type,
|
||||||
|
action: props.action,
|
||||||
|
input,
|
||||||
|
data: {
|
||||||
|
searchKey: props.search ? searchKeyRef.value : "",
|
||||||
|
pageNo,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError(err: any) {
|
||||||
|
hasError.value = true;
|
||||||
|
message.value = `获取选项出错:${err.message}`;
|
||||||
|
},
|
||||||
|
showErrorNotify: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const list = res?.list || res || [];
|
||||||
|
if (list.length > 0) {
|
||||||
|
message.value = "获取数据成功,请从下拉框中选择";
|
||||||
|
} else {
|
||||||
|
message.value = "获取数据成功,没有数据";
|
||||||
|
}
|
||||||
|
optionsRef.value = list;
|
||||||
|
pagerRef.value.total = list.length;
|
||||||
|
if (props.pager) {
|
||||||
|
if (res.pageNo != null) {
|
||||||
|
pagerRef.value.pageNo = res.pageNo ?? 1;
|
||||||
|
}
|
||||||
|
if (res.pageSize != null) {
|
||||||
|
pagerRef.value.pageSize = res.pageSize ?? 100;
|
||||||
|
}
|
||||||
|
if (res.total != null) {
|
||||||
|
pagerRef.value.total = res.total ?? list.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function onClick() {
|
||||||
|
if (optionsRef.value?.length === 0) {
|
||||||
|
await refreshOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshOptions() {
|
||||||
|
await getOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doSearch() {
|
||||||
|
pagerRef.value.pageNo = 1;
|
||||||
|
await refreshOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function onPageChange(current: any) {
|
||||||
|
await refreshOptions();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less"></style>
|
||||||
@@ -2,6 +2,7 @@ import SynologyIdDeviceGetter from "./synology/device-id-getter.vue";
|
|||||||
import RemoteAutoComplete from "./common/remote-auto-complete.vue";
|
import RemoteAutoComplete from "./common/remote-auto-complete.vue";
|
||||||
import RemoteSelect from "./common/remote-select.vue";
|
import RemoteSelect from "./common/remote-select.vue";
|
||||||
import RemoteInput from "./common/remote-input.vue";
|
import RemoteInput from "./common/remote-input.vue";
|
||||||
|
import RemoteTreeSelect from "./common/remote-tree-select.vue";
|
||||||
import CertDomainsGetter from "./common/cert-domains-getter.vue";
|
import CertDomainsGetter from "./common/cert-domains-getter.vue";
|
||||||
import OutputSelector from "/@/components/plugins/common/output-selector/index.vue";
|
import OutputSelector from "/@/components/plugins/common/output-selector/index.vue";
|
||||||
import DnsProviderSelector from "/@/components/plugins/cert/dns-provider-selector/index.vue";
|
import DnsProviderSelector from "/@/components/plugins/cert/dns-provider-selector/index.vue";
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
|
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
|
||||||
app.component("RemoteAutoComplete", RemoteAutoComplete);
|
app.component("RemoteAutoComplete", RemoteAutoComplete);
|
||||||
app.component("RemoteSelect", RemoteSelect);
|
app.component("RemoteSelect", RemoteSelect);
|
||||||
|
app.component("RemoteTreeSelect", RemoteTreeSelect);
|
||||||
app.component("RemoteInput", RemoteInput);
|
app.component("RemoteInput", RemoteInput);
|
||||||
app.component("CertDomainsGetter", CertDomainsGetter);
|
app.component("CertDomainsGetter", CertDomainsGetter);
|
||||||
app.component("InputPassword", InputPassword);
|
app.component("InputPassword", InputPassword);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default {
|
|||||||
pipeline: "Pipeline",
|
pipeline: "Pipeline",
|
||||||
domain: "Domain",
|
domain: "Domain",
|
||||||
deployTimes: "Deployments",
|
deployTimes: "Deployments",
|
||||||
|
monitorCount: "DomainMonitors",
|
||||||
duration: "Duration",
|
duration: "Duration",
|
||||||
price: "Price",
|
price: "Price",
|
||||||
paymentMethod: "Payment Method",
|
paymentMethod: "Payment Method",
|
||||||
@@ -218,6 +219,7 @@ export default {
|
|||||||
triggerCronHelper:
|
triggerCronHelper:
|
||||||
"Click the button above to choose a daily execution time.\nIt is recommended to trigger once per day. The task will be skipped if the certificate has not expired and will not be executed repeatedly.",
|
"Click the button above to choose a daily execution time.\nIt is recommended to trigger once per day. The task will be skipped if the certificate has not expired and will not be executed repeatedly.",
|
||||||
notificationTitle: "Failure Notification",
|
notificationTitle: "Failure Notification",
|
||||||
|
notificationWhen: "Notification Timing",
|
||||||
notificationHelper: "Get real-time alerts when the task fails",
|
notificationHelper: "Get real-time alerts when the task fails",
|
||||||
groupIdTitle: "Pipeline Group",
|
groupIdTitle: "Pipeline Group",
|
||||||
},
|
},
|
||||||
@@ -564,7 +566,7 @@ export default {
|
|||||||
ipv6Priority: "IPv6 Priority",
|
ipv6Priority: "IPv6 Priority",
|
||||||
dualStackNetworkHelper: "If IPv6 priority is selected, enable IPv6 in docker-compose.yaml",
|
dualStackNetworkHelper: "If IPv6 priority is selected, enable IPv6 in docker-compose.yaml",
|
||||||
enableCommonCnameService: "Enable Public CNAME Service",
|
enableCommonCnameService: "Enable Public CNAME Service",
|
||||||
commonCnameHelper: "Allow use of public CNAME service. If disabled and no <router-link to='/sys/cname/provider'>custom CNAME service</router-link> is set, CNAME proxy certificate application will not work.",
|
commonCnameHelper: "Allow use of public CNAME service. If disabled and no <a href='#/sys/cname/provider'>custom CNAME service</a> is set, CNAME proxy certificate application will not work.",
|
||||||
enableCommonSelfServicePasswordRetrieval: "Enable self-service password recovery",
|
enableCommonSelfServicePasswordRetrieval: "Enable self-service password recovery",
|
||||||
saveButton: "Save",
|
saveButton: "Save",
|
||||||
stopSuccess: "Stopped successfully",
|
stopSuccess: "Stopped successfully",
|
||||||
@@ -587,6 +589,7 @@ export default {
|
|||||||
commFeature: "Commercial feature",
|
commFeature: "Commercial feature",
|
||||||
smsProvider: "SMS provider",
|
smsProvider: "SMS provider",
|
||||||
aliyunSms: "Aliyun SMS",
|
aliyunSms: "Aliyun SMS",
|
||||||
|
tencentSms: "Tencent SMS",
|
||||||
yfySms: "YFY SMS",
|
yfySms: "YFY SMS",
|
||||||
smsTest: "SMS test",
|
smsTest: "SMS test",
|
||||||
testMobilePlaceholder: "Enter test mobile number",
|
testMobilePlaceholder: "Enter test mobile number",
|
||||||
@@ -708,6 +711,10 @@ export default {
|
|||||||
setting: {
|
setting: {
|
||||||
showRunStrategy: "Show RunStrategy",
|
showRunStrategy: "Show RunStrategy",
|
||||||
showRunStrategyHelper: "Allow modify the run strategy of the task",
|
showRunStrategyHelper: "Allow modify the run strategy of the task",
|
||||||
|
|
||||||
|
captchaEnabled: "Enable Login Captcha",
|
||||||
|
captchaHelper: "Whether to enable captcha verification for login",
|
||||||
|
captchaType: "Captcha Type",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
@@ -728,4 +735,8 @@ export default {
|
|||||||
challengeSetting: "Challenge Setting",
|
challengeSetting: "Challenge Setting",
|
||||||
gotoCnameTip: "Please go to CNAME Record Page",
|
gotoCnameTip: "Please go to CNAME Record Page",
|
||||||
},
|
},
|
||||||
|
addonSelector: {
|
||||||
|
select: "Select",
|
||||||
|
placeholder: "select please",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export default {
|
|||||||
pipeline: "流水线",
|
pipeline: "流水线",
|
||||||
domain: "域名",
|
domain: "域名",
|
||||||
deployTimes: "部署次数",
|
deployTimes: "部署次数",
|
||||||
|
monitorCount: "域名监控数",
|
||||||
duration: "时长",
|
duration: "时长",
|
||||||
price: "价格",
|
price: "价格",
|
||||||
paymentMethod: "支付方式",
|
paymentMethod: "支付方式",
|
||||||
@@ -223,6 +224,7 @@ export default {
|
|||||||
triggerCronTitle: "定时触发",
|
triggerCronTitle: "定时触发",
|
||||||
triggerCronHelper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
|
triggerCronHelper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
|
||||||
notificationTitle: "失败通知",
|
notificationTitle: "失败通知",
|
||||||
|
notificationWhen: "通知时机",
|
||||||
notificationHelper: "任务执行失败实时提醒",
|
notificationHelper: "任务执行失败实时提醒",
|
||||||
groupIdTitle: "流水线分组",
|
groupIdTitle: "流水线分组",
|
||||||
},
|
},
|
||||||
@@ -570,7 +572,7 @@ export default {
|
|||||||
ipv6Priority: "IPV6优先",
|
ipv6Priority: "IPV6优先",
|
||||||
dualStackNetworkHelper: "如果选择IPv6优先,需要在docker-compose.yaml中启用ipv6",
|
dualStackNetworkHelper: "如果选择IPv6优先,需要在docker-compose.yaml中启用ipv6",
|
||||||
enableCommonCnameService: "启用公共CNAME服务",
|
enableCommonCnameService: "启用公共CNAME服务",
|
||||||
commonCnameHelper: "是否可以使用公共CNAME服务,如果禁用,且没有设置<router-link to='/sys/cname/provider'>自定义CNAME服务</router-link>,则无法使用CNAME代理方式申请证书",
|
commonCnameHelper: "是否可以使用公共CNAME服务,如果禁用,且没有设置<a href='#/sys/cname/provider'>自定义CNAME服务</a>,则无法使用CNAME代理方式申请证书",
|
||||||
enableCommonSelfServicePasswordRetrieval: "启用自助找回密码",
|
enableCommonSelfServicePasswordRetrieval: "启用自助找回密码",
|
||||||
saveButton: "保存",
|
saveButton: "保存",
|
||||||
stopSuccess: "停止成功",
|
stopSuccess: "停止成功",
|
||||||
@@ -593,6 +595,7 @@ export default {
|
|||||||
commFeature: "商业版功能",
|
commFeature: "商业版功能",
|
||||||
smsProvider: "短信提供商",
|
smsProvider: "短信提供商",
|
||||||
aliyunSms: "阿里云短信",
|
aliyunSms: "阿里云短信",
|
||||||
|
tencentSms: "腾讯云短信",
|
||||||
yfySms: "易发云短信",
|
yfySms: "易发云短信",
|
||||||
smsTest: "短信测试",
|
smsTest: "短信测试",
|
||||||
testMobilePlaceholder: "输入测试手机号",
|
testMobilePlaceholder: "输入测试手机号",
|
||||||
@@ -711,6 +714,10 @@ export default {
|
|||||||
setting: {
|
setting: {
|
||||||
showRunStrategy: "显示运行策略选择",
|
showRunStrategy: "显示运行策略选择",
|
||||||
showRunStrategyHelper: "任务设置中是否允许选择运行策略",
|
showRunStrategyHelper: "任务设置中是否允许选择运行策略",
|
||||||
|
|
||||||
|
captchaEnabled: "启用登录验证码",
|
||||||
|
captchaHelper: "登录时是否启用验证码",
|
||||||
|
captchaType: "验证码类型",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
@@ -731,4 +738,8 @@ export default {
|
|||||||
challengeSetting: "校验配置",
|
challengeSetting: "校验配置",
|
||||||
gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加",
|
gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加",
|
||||||
},
|
},
|
||||||
|
addonSelector: {
|
||||||
|
select: "选择",
|
||||||
|
placeholder: "请选择",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
12
packages/ui/certd-client/src/plugin/directive/comm-show.ts
Normal file
12
packages/ui/certd-client/src/plugin/directive/comm-show.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useSettingStore } from "/@/store/settings";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mounted(el: any, binding: any, vnode: any) {
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
const isComm = settingStore.isComm;
|
||||||
|
const { value } = binding;
|
||||||
|
if ((value === false && isComm) || (value === true && !isComm)) {
|
||||||
|
el.parentNode && el.parentNode.removeChild(el);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
8
packages/ui/certd-client/src/plugin/directive/index.ts
Normal file
8
packages/ui/certd-client/src/plugin/directive/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import comm from "./comm-show.js";
|
||||||
|
const install = function (app: any) {
|
||||||
|
app.directive("comm", comm);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install,
|
||||||
|
};
|
||||||
@@ -4,9 +4,12 @@ import FastCrud from "./fast-crud";
|
|||||||
import permission from "./permission";
|
import permission from "./permission";
|
||||||
import { App } from "vue";
|
import { App } from "vue";
|
||||||
import "./validator/index.js";
|
import "./validator/index.js";
|
||||||
|
import directives from "./directive/index";
|
||||||
|
|
||||||
function install(app: App, options: any = {}) {
|
function install(app: App, options: any = {}) {
|
||||||
app.use(FastCrud, options);
|
app.use(FastCrud, options);
|
||||||
app.use(permission);
|
app.use(permission);
|
||||||
|
app.use(directives);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export const sysResources = [
|
|||||||
title: "certd.sysResources.sysPluginConfig",
|
title: "certd.sysResources.sysPluginConfig",
|
||||||
name: "SysPluginConfig",
|
name: "SysPluginConfig",
|
||||||
path: "/sys/plugin/config",
|
path: "/sys/plugin/config",
|
||||||
component: "/sys/plugin/config.vue",
|
component: "/sys/plugin/config-common.vue",
|
||||||
meta: {
|
meta: {
|
||||||
show: () => {
|
show: () => {
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import * as api from "./api.plugin";
|
import * as api from "./api.plugin";
|
||||||
import { DynamicType, FormItemProps } from "@fast-crud/fast-crud";
|
import { DynamicType, FormItemProps, useMerge } from "@fast-crud/fast-crud";
|
||||||
import { i18n } from "/src/locales/i18n";
|
import { i18n } from "/src/locales/i18n";
|
||||||
|
import { cloneDeep } from "lodash-es";
|
||||||
interface PluginState {
|
interface PluginState {
|
||||||
group?: PluginGroups;
|
group?: PluginGroups;
|
||||||
|
originGroup?: PluginGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginGroup = {
|
export type PluginGroup = {
|
||||||
@@ -32,14 +34,17 @@ export class PluginGroups {
|
|||||||
groups!: { [key: string]: PluginGroup };
|
groups!: { [key: string]: PluginGroup };
|
||||||
map!: { [key: string]: PluginDefine };
|
map!: { [key: string]: PluginDefine };
|
||||||
t: any;
|
t: any;
|
||||||
constructor(groups: { [key: string]: PluginGroup }) {
|
mergeSetting?: boolean;
|
||||||
|
constructor(groups: { [key: string]: PluginGroup }, opts?: { mergeSetting?: boolean }) {
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
this.t = i18n.global.t;
|
this.t = i18n.global.t;
|
||||||
|
this.mergeSetting = opts?.mergeSetting ?? false;
|
||||||
this.initGroup(groups);
|
this.initGroup(groups);
|
||||||
this.initMap();
|
this.initMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initGroup(groups: { [p: string]: PluginGroup }) {
|
private initGroup(groups: { [p: string]: PluginGroup }) {
|
||||||
|
const { merge } = useMerge();
|
||||||
const all: PluginGroup = {
|
const all: PluginGroup = {
|
||||||
key: "all",
|
key: "all",
|
||||||
title: this.t("certd.all"),
|
title: this.t("certd.all"),
|
||||||
@@ -48,6 +53,14 @@ export class PluginGroups {
|
|||||||
icon: "material-symbols:border-all-rounded",
|
icon: "material-symbols:border-all-rounded",
|
||||||
};
|
};
|
||||||
for (const key in groups) {
|
for (const key in groups) {
|
||||||
|
if (this.mergeSetting) {
|
||||||
|
for (const plugin of groups[key].plugins) {
|
||||||
|
if (plugin.sysSetting) {
|
||||||
|
merge(plugin.input, plugin.sysSetting.metadata?.input || {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
all.plugins.push(...groups[key].plugins);
|
all.plugins.push(...groups[key].plugins);
|
||||||
}
|
}
|
||||||
this.groups = {
|
this.groups = {
|
||||||
@@ -132,11 +145,15 @@ export const usePluginStore = defineStore({
|
|||||||
id: "app.plugin",
|
id: "app.plugin",
|
||||||
state: (): PluginState => ({
|
state: (): PluginState => ({
|
||||||
group: null,
|
group: null,
|
||||||
|
originGroup: null,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async reload() {
|
async reload() {
|
||||||
const groups = await api.GetGroups({});
|
const groups = await api.GetGroups({});
|
||||||
this.group = new PluginGroups(groups);
|
this.group = new PluginGroups(groups, { mergeSetting: true });
|
||||||
|
this.originGroup = new PluginGroups(cloneDeep(groups));
|
||||||
|
console.log("group", this.group);
|
||||||
|
console.log("originGroup", this.originGroup);
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
if (!this.group) {
|
if (!this.group) {
|
||||||
@@ -150,6 +167,7 @@ export const usePluginStore = defineStore({
|
|||||||
},
|
},
|
||||||
async clear() {
|
async clear() {
|
||||||
this.group = null;
|
this.group = null;
|
||||||
|
this.originGroup = null;
|
||||||
},
|
},
|
||||||
async getList(): Promise<PluginDefine[]> {
|
async getList(): Promise<PluginDefine[]> {
|
||||||
await this.init();
|
await this.init();
|
||||||
@@ -159,6 +177,10 @@ export const usePluginStore = defineStore({
|
|||||||
await this.init();
|
await this.init();
|
||||||
return this.group.get(name);
|
return this.group.get(name);
|
||||||
},
|
},
|
||||||
|
async getPluginDefineFromOrigin(name: string): Promise<PluginDefine> {
|
||||||
|
await this.init();
|
||||||
|
return this.originGroup.get(name);
|
||||||
|
},
|
||||||
async getPluginConfig(query: any) {
|
async getPluginConfig(query: any) {
|
||||||
return await api.GetPluginConfig(query);
|
return await api.GetPluginConfig(query);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ export type SysPublicSetting = {
|
|||||||
aiChatEnabled?: boolean;
|
aiChatEnabled?: boolean;
|
||||||
|
|
||||||
showRunStrategy?: boolean;
|
showRunStrategy?: boolean;
|
||||||
|
|
||||||
|
captchaEnabled?: boolean;
|
||||||
|
captchaType?: number;
|
||||||
|
captchaAddonId?: number;
|
||||||
};
|
};
|
||||||
export type SuiteSetting = {
|
export type SuiteSetting = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { utils } from "/@/utils";
|
|||||||
import { cloneDeep, merge } from "lodash-es";
|
import { cloneDeep, merge } from "lodash-es";
|
||||||
import { useI18n } from "/src/locales";
|
import { useI18n } from "/src/locales";
|
||||||
export interface SettingState {
|
export interface SettingState {
|
||||||
|
skipReset?: boolean; // 注销登录时,不清空此store的状态
|
||||||
sysPublic?: SysPublicSetting;
|
sysPublic?: SysPublicSetting;
|
||||||
installInfo?: {
|
installInfo?: {
|
||||||
siteId: string;
|
siteId: string;
|
||||||
@@ -64,6 +65,7 @@ const defaultSiteInfo: SiteInfo = {
|
|||||||
export const useSettingStore = defineStore({
|
export const useSettingStore = defineStore({
|
||||||
id: "app.setting",
|
id: "app.setting",
|
||||||
state: (): SettingState => ({
|
state: (): SettingState => ({
|
||||||
|
skipReset: true,
|
||||||
plusInfo: {
|
plusInfo: {
|
||||||
isPlus: false,
|
isPlus: false,
|
||||||
vipType: "free",
|
vipType: "free",
|
||||||
|
|||||||
@@ -304,3 +304,11 @@ h6 {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
color: #6e6e6e;
|
color: #6e6e6e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-modal-body{
|
||||||
|
.fs-form-body{
|
||||||
|
max-height: 66vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -38,6 +38,9 @@ export function resetAllStores() {
|
|||||||
}
|
}
|
||||||
const allStores = (pinia as any)._s;
|
const allStores = (pinia as any)._s;
|
||||||
for (const [_key, store] of allStores) {
|
for (const [_key, store] of allStores) {
|
||||||
|
if (store.skipReset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
store.$reset();
|
store.$reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
|||||||
component: {
|
component: {
|
||||||
color: "auto",
|
color: "auto",
|
||||||
},
|
},
|
||||||
|
order: -1,
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
component: {
|
component: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user