diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..d19e47465 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +github: greper +buy_me_a_coffee: greper +custom: ['https://afdian.com/a/greper'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 317406481..40d3e5b3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,56 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +### Bug Fixes + +* 修复导入插件对话框无法打开的bug,修复插件编辑页面打开多个代码编辑器消失的bug ([e5a080a](https://github.com/certd/certd/commit/e5a080aebe0d2f3e3c0f86bf863f75069c1bf7ab)) +* 修复ssl.com报EMAILADDRESS数量不对的bug ([c560cc5](https://github.com/certd/certd/commit/c560cc5adda6e15bf3a8865d874042550a6c2688)) + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Bug Fixes + +* 修复商业版退出登录后,丢失站点个性化设置的bug ([d75dd05](https://github.com/certd/certd/commit/d75dd058d65c85f80c49e1fa7a910e6c6f08e824)) +* 修复授权类型和名称字段排到最后的bug ([43b7977](https://github.com/certd/certd/commit/43b79778ea9034065f6a15af3296274315597c6b)) +* 修复证书监控某些情况下报 options.lookup不能为null的bug ([d2ecfe5](https://github.com/certd/certd/commit/d2ecfe5491b2639eb30b5cae293af6062d58bb9f)) +* 修复证书手动托管时新上传的证书无效的bug ([506385e](https://github.com/certd/certd/commit/506385e5a2600887fe30854e0713583caaa2e689)) +* 修复secret patch 类型多了type:的bug ([d04f383](https://github.com/certd/certd/commit/d04f3831611011a90ec0594724b9694490d5edd0)) + +### Performance Improvements + +* 登录支持极验验证码 ([370db62](https://github.com/certd/certd/commit/370db62bf0aece241859244927beabba32d6a257)) +* 登录注册、找回密码都支持极验验证码和图片验证码 ([7bdde68](https://github.com/certd/certd/commit/7bdde68ecea29fe2c570fd3cb082139db6c93d93)) +* 优化加量包展示效果 ([3c65f37](https://github.com/certd/certd/commit/3c65f37d84177ba107d4a6462648af12d2fc4b7a)) +* 证书到期剩余天数进度条根据实际证书有效期计算 ([#528](https://github.com/certd/certd/issues/528)) nicheng-he ([2d4586b](https://github.com/certd/certd/commit/2d4586b1c42c39f97d2a95b9453cca4bc8bfbe61)) +* add preferred chain option ([#519](https://github.com/certd/certd/issues/519)) @ZeroClover ([902359f](https://github.com/certd/certd/commit/902359f24ed12eee4f9b65178f1d6a60378351d2)) +* ssh配置增加脚本类型设置,bash还是sh ([ae41c60](https://github.com/certd/certd/commit/ae41c6038b27c9476e64a2402a8daf247c38a5b6)) +* start.sh增加sudo ([b7271d7](https://github.com/certd/certd/commit/b7271d7a464773a1bf87d7d1f24d933ba0f86915)) + +## [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 diff --git a/README.md b/README.md index fc5be4673..f0960aafe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Certd -[English](./README_en.md) | [中文](./README.md) +中文 | [English](./README_en.md) Certd® 是一个免费的全自动证书管理系统,让你的网站证书永不过期。 后缀d取自linux守护进程的命名风格,意为证书守护进程 @@ -152,7 +152,7 @@ https://certd.handfree.work/ ## 八、捐赠 ************************ -支持开源,为爱发电,我已入驻爱发电 +支持开源,为爱发电,我已入驻爱发电 https://afdian.com/a/greper 发电权益: @@ -171,6 +171,7 @@ https://afdian.com/a/greper | 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖 | | 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | +************************ ************************ diff --git a/README_en.md b/README_en.md index 57f8ea3aa..d665a9825 100644 --- a/README_en.md +++ b/README_en.md @@ -1,6 +1,6 @@ # 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. @@ -134,6 +134,8 @@ You can also add the author as a friend. | QR Code | | ## 8. Donation +************************ + [![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-red)](https://github.com/sponsors/greper) ************************ Support open-source projects and contribute with love. I've joined Afdian. https://afdian.com/a/greper diff --git a/build.trigger b/build.trigger index bacbe4702..33594b85c 100644 --- a/build.trigger +++ b/build.trigger @@ -1 +1 @@ -00:43 +21:09 diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8ac252506..1ecd238c2 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -107,7 +107,6 @@ export default defineConfig({ text: "常见问题", items: [ {text: "QA", link: "/guide/qa/use.md"}, - {text: "常见报错处理", link: "/guide/qa/"}, {text: "群晖证书部署", link: "/guide/use/synology/"}, {text: "腾讯云密钥获取", link: "/guide/use/tencent/"}, {text: "连接windows主机", link: "/guide/use/host/windows.md"}, @@ -120,6 +119,7 @@ export default defineConfig({ {text: "邮箱配置", link: "/guide/use/email/index.md"}, {text: "IPv6支持", link: "/guide/use/setting/ipv6.md"}, {text: "ESXi", link: "/guide/use/ESXi/index.md"}, + {text: "宝塔动态IP白名单", link: "/guide/use/baota/white_list.md"}, {text: "子域名托管", link: "/guide/use/cert/subdomain.md"}, ] }, diff --git a/docs/guide/changelogs/CHANGELOG.md b/docs/guide/changelogs/CHANGELOG.md index 317406481..40d3e5b3d 100644 --- a/docs/guide/changelogs/CHANGELOG.md +++ b/docs/guide/changelogs/CHANGELOG.md @@ -3,6 +3,56 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +### Bug Fixes + +* 修复导入插件对话框无法打开的bug,修复插件编辑页面打开多个代码编辑器消失的bug ([e5a080a](https://github.com/certd/certd/commit/e5a080aebe0d2f3e3c0f86bf863f75069c1bf7ab)) +* 修复ssl.com报EMAILADDRESS数量不对的bug ([c560cc5](https://github.com/certd/certd/commit/c560cc5adda6e15bf3a8865d874042550a6c2688)) + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Bug Fixes + +* 修复商业版退出登录后,丢失站点个性化设置的bug ([d75dd05](https://github.com/certd/certd/commit/d75dd058d65c85f80c49e1fa7a910e6c6f08e824)) +* 修复授权类型和名称字段排到最后的bug ([43b7977](https://github.com/certd/certd/commit/43b79778ea9034065f6a15af3296274315597c6b)) +* 修复证书监控某些情况下报 options.lookup不能为null的bug ([d2ecfe5](https://github.com/certd/certd/commit/d2ecfe5491b2639eb30b5cae293af6062d58bb9f)) +* 修复证书手动托管时新上传的证书无效的bug ([506385e](https://github.com/certd/certd/commit/506385e5a2600887fe30854e0713583caaa2e689)) +* 修复secret patch 类型多了type:的bug ([d04f383](https://github.com/certd/certd/commit/d04f3831611011a90ec0594724b9694490d5edd0)) + +### Performance Improvements + +* 登录支持极验验证码 ([370db62](https://github.com/certd/certd/commit/370db62bf0aece241859244927beabba32d6a257)) +* 登录注册、找回密码都支持极验验证码和图片验证码 ([7bdde68](https://github.com/certd/certd/commit/7bdde68ecea29fe2c570fd3cb082139db6c93d93)) +* 优化加量包展示效果 ([3c65f37](https://github.com/certd/certd/commit/3c65f37d84177ba107d4a6462648af12d2fc4b7a)) +* 证书到期剩余天数进度条根据实际证书有效期计算 ([#528](https://github.com/certd/certd/issues/528)) nicheng-he ([2d4586b](https://github.com/certd/certd/commit/2d4586b1c42c39f97d2a95b9453cca4bc8bfbe61)) +* add preferred chain option ([#519](https://github.com/certd/certd/issues/519)) @ZeroClover ([902359f](https://github.com/certd/certd/commit/902359f24ed12eee4f9b65178f1d6a60378351d2)) +* ssh配置增加脚本类型设置,bash还是sh ([ae41c60](https://github.com/certd/certd/commit/ae41c6038b27c9476e64a2402a8daf247c38a5b6)) +* start.sh增加sudo ([b7271d7](https://github.com/certd/certd/commit/b7271d7a464773a1bf87d7d1f24d933ba0f86915)) + +## [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 diff --git a/docs/guide/install/baota/index.md b/docs/guide/install/baota/index.md index ff4512451..bf173b930 100644 --- a/docs/guide/install/baota/index.md +++ b/docs/guide/install/baota/index.md @@ -21,13 +21,13 @@ #### 2.2 容器编排方式部署 -1. 打开`docker-compose.yaml`,整个内容复制下来 +1. 打开`docker-compose.yaml`,整个内容复制下来 https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml -然后到宝塔里面进到docker->容器编排->添加容器编排 -![](./images/1.png) -点击确定,等待启动完成 +然后到宝塔里面进到docker->容器编排->添加容器编排 +![](./images/1.png) +点击确定,等待启动完成 ![](./images/2.png) > certd默认使用sqlite数据库,另外支持`mysql`和`postgresql`数据库,[点我了解如何切换其他数据库](../database) @@ -35,16 +35,16 @@ ## 二、访问应用 -http://ip:7001 -https://ip:7002 -默认账号密码 -admin/123456 +http://ip:7001 +https://ip:7002 +默认账号密码 +admin/123456 登录后请及时修改密码 ## 三、如何升级 宝塔升级certd非常简单 -打开容器页面: `docker`->`容器编排`->`左侧选择Certd`->`更新镜像` +打开容器页面: `docker`->`容器编排`->`左侧选择Certd`->`更新镜像` ![img.png](./images/upgrade.png) @@ -80,5 +80,8 @@ admin/123456 ### 1. 无法访问Certd 1. 确认服务器的安全规则,是否放开了对应端口 2. 确认宝塔防火墙是否放开对应端口 -3. 尝试将Certd容器加入宝塔的`bridge`网络 -![](./images/network.png) \ No newline at end of file +3. 尝试将Certd容器加入宝塔的`bridge`网络 +![](./images/network.png) + +### 2. 动态IP无法加白名单问题 +[Nginx代理解决方案](../../use/baota/white_list.md) \ No newline at end of file diff --git a/docs/guide/install/source/index.md b/docs/guide/install/source/index.md index 8f404264b..3c25c889d 100644 --- a/docs/guide/install/source/index.md +++ b/docs/guide/install/source/index.md @@ -12,7 +12,7 @@ git clone https://github.com/certd/certd --depth=1 # git checkout v1.x.x # 当v2主干分支代码无法正常启动时,可以尝试此命令,1.x.x换成最新版本号 cd certd # 启动服务 -./start.sh +./start.sh ``` >如果是windows,请先安装`git for windows` ,然后右键,选择`open git bash here`打开终端,再执行`./start.sh`命令 @@ -21,9 +21,9 @@ cd certd ### 访问测试 -http://your_server_ip:7001 -https://your_server_ip:7002 -默认账号密码:admin/123456 +http://your_server_ip:7001 +https://your_server_ip:7002 +默认账号密码:admin/123456 记得修改密码 @@ -37,7 +37,7 @@ cp -rf ./packages/ui/certd-server/data ../certd-data-backup git pull # 如果提示pull失败,可以尝试强制更新 -# git checkout v2 -f && git pull +# git checkout v2 -f && git pull # 先停止旧的服务,7001是certd的默认端口 kill -9 $(lsof -t -i:7001) @@ -45,16 +45,31 @@ kill -9 $(lsof -t -i:7001) ./start.sh ``` -::: warning -升级certd版本前,切记切记先备份一下数据 +::: warning +升级certd版本前,切记切记先备份一下数据 ::: ## 三、数据备份 -> 数据默认保存在 `./packages/ui/certd-server/data` 目录下 +> 数据默认保存在 `./packages/ui/certd-server/data` 目录下 > 建议配置一条[数据库备份流水线](../../use/backup/) 自动备份 ## 四、备份恢复 将备份的`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 + + + + + diff --git a/docs/guide/qa/use.md b/docs/guide/qa/use.md index 36f3ce4db..c75407dd6 100644 --- a/docs/guide/qa/use.md +++ b/docs/guide/qa/use.md @@ -1,4 +1,4 @@ -# 使用问题 +# 常见问题 ## 1. 是否支持IP证书 @@ -7,8 +7,27 @@ ## 2. 建议设置多长时间运行一次流水线 -建议每天运行一次,检查证书过期时间 +建议每天运行一次,检查证书过期时间 当证书没过期时,自动跳过部署 当证书到期前35天(创建流水线时可以修改),将会自动重新申请证书,自动部署 +## 3. too many certificates 错误 +当出现如下报错时,说明相同的域名短时间内申请超过5次 +解决方案:可以加多一个子域名,重新执行就可以规避次错误 +``` +"detail": too many certificates (5) already issued for this exact set of idantifiers in the last 168hm0s +``` + +## ssl.com报错 CAA record does not include ssl.com which is required to issue the certificate +ssl.com申请证书要求必须设置CAA记录,表示允许ssl.com为该域名颁发证书 +请按如下格式添加CAA记录 + +| 示例 | 类型 | 域名前缀 | flag | tag | 值 | +|-------|-----| -- |-----------|--------|----------------------| +| 顶级域名 | CAA | @ | 0 | issue | "ssl.com" (注意有双引号) | +| 一级泛域名 | CAA | * | 0 | issue/issuewild | "ssl.com" | +| 固定子域名 | CAA | sub | 0 | issue |"ssl.com" | + + + diff --git a/docs/guide/use/baota/images/white-1.png b/docs/guide/use/baota/images/white-1.png new file mode 100644 index 000000000..f72b7bdaa Binary files /dev/null and b/docs/guide/use/baota/images/white-1.png differ diff --git a/docs/guide/use/baota/images/white-2.png b/docs/guide/use/baota/images/white-2.png new file mode 100644 index 000000000..e23da95a6 Binary files /dev/null and b/docs/guide/use/baota/images/white-2.png differ diff --git a/docs/guide/use/baota/images/white-3.png b/docs/guide/use/baota/images/white-3.png new file mode 100644 index 000000000..33ba42630 Binary files /dev/null and b/docs/guide/use/baota/images/white-3.png differ diff --git a/docs/guide/use/baota/images/white-4.png b/docs/guide/use/baota/images/white-4.png new file mode 100644 index 000000000..db7380655 Binary files /dev/null and b/docs/guide/use/baota/images/white-4.png differ diff --git a/docs/guide/use/baota/images/white-5.png b/docs/guide/use/baota/images/white-5.png new file mode 100644 index 000000000..d151d7796 Binary files /dev/null and b/docs/guide/use/baota/images/white-5.png differ diff --git a/docs/guide/use/baota/images/white-6.png b/docs/guide/use/baota/images/white-6.png new file mode 100644 index 000000000..93fb84c8b Binary files /dev/null and b/docs/guide/use/baota/images/white-6.png differ diff --git a/docs/guide/use/baota/images/white-safe-1.png b/docs/guide/use/baota/images/white-safe-1.png new file mode 100644 index 000000000..818ee68de Binary files /dev/null and b/docs/guide/use/baota/images/white-safe-1.png differ diff --git a/docs/guide/use/baota/images/white-safe-2.png b/docs/guide/use/baota/images/white-safe-2.png new file mode 100644 index 000000000..8876a335f Binary files /dev/null and b/docs/guide/use/baota/images/white-safe-2.png differ diff --git a/docs/guide/use/baota/white_list.md b/docs/guide/use/baota/white_list.md new file mode 100644 index 000000000..778e225d3 --- /dev/null +++ b/docs/guide/use/baota/white_list.md @@ -0,0 +1,98 @@ +# 宝塔IP白名单与动态IP问题 +调用宝塔接口需要添加IP白名单,但当certd部署在动态IP环境下时,IP白名单就不好添加 +本章节提供两种解决方案: +1. 小范围网段放开(简单) +2. nginx代理 + +## 一、放开小范围网段 + +家庭网络IP虽然会变动,但是只会在小范围变的。 + +你可以分析规律,将变动的部分,设置成网段即可 + +> 比如出现过: 100.25.1.5 , 100.25.1.8 +> +> 那么你可以配置 100.25.1.1-100.25.1.255 + + +> 如果出现过: 100.25.1.5 , 100.25.4.8 +> +> 可以尝试配置 100.25.*.5 + +## 二、nginx代理方案 + +通过在宝塔中配置一个nginx反向代理,代理宝塔自己的地址 + +然后在nginx中配置放开certd需要的接口,缩小影响范围 + +让nginx来充当防火墙 + +架构图如下: +``` + 只要将127.0.0.1加入白名单即可 + ↓ +certd --------> nginx -------> 宝塔 + ↑ + 拦截除更新证书之外的地址 +``` + +### 1. 添加nginx反向代理 +![](./images/white-1.png) + +### 2. 域名和代理目标 +![](./images/white-2.png) + +### 3. 设置放开哪些接口 +![](./images/white-3.png) +![img.png](images/white-4.png) +将如下脚本填入上方文本域中,保存 +```nginx configuration +set $allow_access false; + + # 检查请求的URI是否在白名单中 + if ($request_uri ~* "^/(site\?action=get_site_types)") { + # 允许测试 + set $allow_access true; + } + if ($request_uri ~* "^/(config\?action=SavePanelSSL)") { + # 允许部署到宝塔面板本身证书 + set $allow_access true; + } + + if ($request_uri ~* "^/(mod/docker/com/set_ssl|site\?action=SetSSL|ssl\?action=GetSiteDomain|mod/docker/com/get_site_list)") { + # 允许部署宝塔网站证书 + set $allow_access true; + } + + if ($request_uri ~* "^/(ssl?action=remove_cloud_cert|ssl\?action=get_cert_list)") { + # 允许删除宝塔过期证书 + set $allow_access true; + } + + if ($request_uri ~* "^/(datalist/get_data_list|site/set_site_ssl)") { + set $allow_access true; + } + + # 如果不在白名单,返回403禁止访问 + if ($allow_access = false) { + return 405; + } + +``` + + +### 4. 接口IP白名单添加127.0.0.1 + ![img.png](images/white-5.png) + +### 5. certd中宝塔授权配置改成新的这个域名地址 + +![img.png](images/white-6.png) +点击测试检查是否ok ,到这里就可以正常部署证书了 + +### 6. 安全加强(将请求地址改成https) +在宝塔中配置证书部署任务,选择刚才新建的这个网站,给他部署证书 +勾选强制https +![img.png](images/white-safe-1.png) +更换443端口【可选】 +![img.png](images/white-safe-2.png) +禁止http访问 diff --git a/lerna.json b/lerna.json index 26a7f3583..59fb7d523 100644 --- a/lerna.json +++ b/lerna.json @@ -9,5 +9,5 @@ } }, "npmClient": "pnpm", - "version": "1.36.18" + "version": "1.36.21" } diff --git a/packages/core/acme-client/CHANGELOG.md b/packages/core/acme-client/CHANGELOG.md index 329ae74ae..af65c4ac9 100644 --- a/packages/core/acme-client/CHANGELOG.md +++ b/packages/core/acme-client/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/publishlab/node-acme-client/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/acme-client + +## [1.36.20](https://github.com/publishlab/node-acme-client/compare/v1.36.19...v1.36.20) (2025-09-13) + +**Note:** Version bump only for package @certd/acme-client + +## [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 diff --git a/packages/core/acme-client/package.json b/packages/core/acme-client/package.json index d77cfec87..c5e9cf774 100644 --- a/packages/core/acme-client/package.json +++ b/packages/core/acme-client/package.json @@ -3,7 +3,7 @@ "description": "Simple and unopinionated ACME client", "private": false, "author": "nmorsman", - "version": "1.36.18", + "version": "1.36.21", "type": "module", "module": "scr/index.js", "main": "src/index.js", @@ -18,7 +18,7 @@ "types" ], "dependencies": { - "@certd/basic": "^1.36.18", + "@certd/basic": "^1.36.21", "@peculiar/x509": "^1.11.0", "asn1js": "^3.0.5", "axios": "^1.7.2", @@ -69,5 +69,5 @@ "bugs": { "url": "https://github.com/publishlab/node-acme-client/issues" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/core/acme-client/src/client.js b/packages/core/acme-client/src/client.js index f6b389fa3..b2ff14dc5 100644 --- a/packages/core/acme-client/src/client.js +++ b/packages/core/acme-client/src/client.js @@ -502,7 +502,7 @@ class AcmeClient { await verify[challenge.type](authz, challenge, keyAuthorization); }; - log('Waiting for ACME challenge verification(等待ACME挑战验证)', this.backoffOpts); + log('Waiting for ACME challenge verification(等待ACME挑战验证)'); return util.retry(verifyFn, this.backoffOpts); } diff --git a/packages/core/acme-client/src/index.js b/packages/core/acme-client/src/index.js index 99cb041fc..a40d9544d 100644 --- a/packages/core/acme-client/src/index.js +++ b/packages/core/acme-client/src/index.js @@ -25,6 +25,10 @@ export const directory = { staging: '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', + } }; /** diff --git a/packages/core/basic/CHANGELOG.md b/packages/core/basic/CHANGELOG.md index d8c3f8a14..82852c212 100644 --- a/packages/core/basic/CHANGELOG.md +++ b/packages/core/basic/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/basic + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +**Note:** Version bump only for package @certd/basic + +## [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 diff --git a/packages/core/basic/build.md b/packages/core/basic/build.md index 4316bd657..c98fede4d 100644 --- a/packages/core/basic/build.md +++ b/packages/core/basic/build.md @@ -1 +1 @@ -00:39 +20:59 diff --git a/packages/core/basic/package.json b/packages/core/basic/package.json index def44d2fe..3cc51a061 100644 --- a/packages/core/basic/package.json +++ b/packages/core/basic/package.json @@ -1,7 +1,7 @@ { "name": "@certd/basic", "private": false, - "version": "1.36.18", + "version": "1.36.21", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -45,5 +45,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/core/basic/src/utils/util.log.ts b/packages/core/basic/src/utils/util.log.ts index 6f6709aad..fbcc87e2e 100644 --- a/packages/core/basic/src/utils/util.log.ts +++ b/packages/core/basic/src/utils/util.log.ts @@ -1,22 +1,4 @@ -import log4js, { LoggingEvent, Logger } 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); - }, -}; +import log4js, { CallStack, Level } from "log4js"; let logFilePath = "./logs/app.log"; export function resetLogConfigure() { @@ -24,7 +6,6 @@ export function resetLogConfigure() { log4js.configure({ appenders: { std: { type: "stdout" }, - output: { type: OutputAppender }, file: { type: "dateFile", filename: logFilePath, @@ -33,7 +14,7 @@ export function resetLogConfigure() { 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(); @@ -44,15 +25,98 @@ export function resetLogFilePath(filePath: string) { resetLogConfigure(); } export function buildLogger(write: (text: string) => void) { - const logger = log4js.getLogger("pipeline"); - const _secrets: string[] = []; - //@ts-ignore - logger.addSecret = (secret: string) => { - _secrets.push(secret); - }; - logger.addContext("outputHandler", { - write: (text: string) => { - for (const item of _secrets) { + return new PipelineLogger("pipeline", write); +} + +export type ILogger = { + readonly category: string; + level: Level | string; + log(level: Level | string, ...args: any[]): void; + + 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) { continue; } @@ -66,10 +130,88 @@ export function buildLogger(write: (text: string) => void) { text = text.replaceAll(item, "*".repeat(item.length)); } } - write(text); - }, - }); - return logger; -} + text = `[${formatDateIntl()}] [${level.toUpperCase()}] - ${text} \n`; + this.customWriter(text); + } + // @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 {} +} diff --git a/packages/core/basic/src/utils/util.request.ts b/packages/core/basic/src/utils/util.request.ts index 39f41a2ad..ea7d83027 100644 --- a/packages/core/basic/src/utils/util.request.ts +++ b/packages/core/basic/src/utils/util.request.ts @@ -1,6 +1,5 @@ import axios, { AxiosHeaders, AxiosRequestConfig } from "axios"; import { ILogger, logger } from "./util.log.js"; -import { Logger } from "log4js"; import { HttpProxyAgent } from "http-proxy-agent"; import { HttpsProxyAgent } from "https-proxy-agent"; import nodeHttp from "http"; @@ -8,6 +7,13 @@ import * as https from "node:https"; import { merge } from "lodash-es"; import { safePromise } from "./util.promise.js"; import fs from "fs"; + +const errorMap: Record = { + "ssl3_get_record:wrong version number": "http协议错误,服务端要求http协议,请检查是否使用了https请求", + "getaddrinfo EAI_AGAIN": "无法解析域名,请检查网络连接或dns配置,更换docker-compose.yaml中dns配置", + "self-signed certificate": "目标站点为自签名证书,请勾选忽略证书校验", +}; + export class HttpError extends Error { status?: number; statusText?: string; @@ -22,11 +28,12 @@ export class HttpError extends Error { super(error.message || error.response?.statusText); const message = error?.message; - if (message && typeof message === "string") { - if (message.indexOf && message.indexOf("ssl3_get_record:wrong version number") >= 0) { - this.message = `${message}(http协议错误,服务端要求http协议,请检查是否使用了https请求)`; - } else if (message.indexOf("getaddrinfo EAI_AGAIN") >= 0) { - this.message = `${message}(无法解析域名,请检查网络连接或dns配置,更换docker-compose.yaml中dns配置)`; + if (message && typeof message === "string" && message.indexOf) { + for (const key in errorMap) { + if (message.indexOf(key) > -1) { + this.message = `${this.message}(${errorMap[key]})`; + break; + } } } @@ -84,7 +91,7 @@ export function getGlobalAgents() { /** * @description 创建请求实例 */ -export function createAxiosService({ logger }: { logger: Logger }) { +export function createAxiosService({ logger }: { logger: ILogger }) { // 创建一个 axios 实例 const service = axios.create(); diff --git a/packages/core/pipeline/CHANGELOG.md b/packages/core/pipeline/CHANGELOG.md index d954ea085..e8f742a0e 100644 --- a/packages/core/pipeline/CHANGELOG.md +++ b/packages/core/pipeline/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/pipeline + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +**Note:** Version bump only for package @certd/pipeline + +## [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 diff --git a/packages/core/pipeline/package.json b/packages/core/pipeline/package.json index 0dcf7704e..2f8914a81 100644 --- a/packages/core/pipeline/package.json +++ b/packages/core/pipeline/package.json @@ -1,7 +1,7 @@ { "name": "@certd/pipeline", "private": false, - "version": "1.36.18", + "version": "1.36.21", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -17,8 +17,8 @@ "pub": "npm publish" }, "dependencies": { - "@certd/basic": "^1.36.18", - "@certd/plus-core": "^1.36.18", + "@certd/basic": "^1.36.21", + "@certd/plus-core": "^1.36.21", "dayjs": "^1.11.7", "lodash-es": "^4.17.21", "reflect-metadata": "^0.1.13" @@ -44,5 +44,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/core/pipeline/src/context/index.ts b/packages/core/pipeline/src/context/index.ts index 0dd6b147e..2d987bc02 100644 --- a/packages/core/pipeline/src/context/index.ts +++ b/packages/core/pipeline/src/context/index.ts @@ -21,9 +21,9 @@ export type PageRes = { export class Pager { pageNo: number; pageSize: number; - constructor(req: PageSearch) { - this.pageNo = req.pageNo ?? 1; - this.pageSize = req.pageSize || 50; + constructor(req?: PageSearch) { + this.pageNo = req?.pageNo ?? 1; + this.pageSize = req?.pageSize || 50; } getOffset() { diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 81e4f78da..cb6f4d335 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -314,7 +314,7 @@ export class Executor { const outputKey = arr[2]; input[key] = this.currentStatusMap.get(id)?.status?.output[outputKey] ?? this.lastStatusMap.get(id)?.status?.output[outputKey]; if (input[key] == null) { - this.logger.warn(`${item.title}的配置未找到对应的输出值,请确认对应的前置任务是否存在或者是否执行正确`); + currentLogger.warn(`${item.title}的配置未找到对应的输出值,请确认对应的前置任务是否存在或者是否执行正确`); } } } diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index eee946f7b..8cb10d199 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -253,9 +253,9 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin { return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS"); } - buildCertName(domain: string) { + buildCertName(domain: string, prefix = "") { domain = domain.replaceAll("*", "_").replaceAll(".", "_"); - return `${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`; + return `${prefix}_${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`; } async onRequest(req: PluginRequestHandleReq) { diff --git a/packages/core/pipeline/src/registry/registry.ts b/packages/core/pipeline/src/registry/registry.ts index b466bcc9f..3322d7080 100644 --- a/packages/core/pipeline/src/registry/registry.ts +++ b/packages/core/pipeline/src/registry/registry.ts @@ -69,9 +69,15 @@ export class Registry { return this.storage; } - getDefineList() { + getDefineList(prefix?: string) { let list = []; + if (prefix) { + prefix = prefix + ":"; + } for (const key in this.storage) { + if (prefix && !key.startsWith(prefix)) { + continue; + } const define = this.getDefine(key); if (define) { if (define?.deprecated) { @@ -90,7 +96,10 @@ export class Registry { return list; } - getDefine(key: string) { + getDefine(key: string, prefix?: string) { + if (prefix) { + key = prefix + ":" + key; + } const item = this.storage[key]; if (!item) { return; diff --git a/packages/libs/lib-huawei/CHANGELOG.md b/packages/libs/lib-huawei/CHANGELOG.md index 5e582f6bd..1d653c0ab 100644 --- a/packages/libs/lib-huawei/CHANGELOG.md +++ b/packages/libs/lib-huawei/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/lib-huawei + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +**Note:** Version bump only for package @certd/lib-huawei + +## [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 diff --git a/packages/libs/lib-huawei/package.json b/packages/libs/lib-huawei/package.json index bb3a33df8..b13af8f60 100644 --- a/packages/libs/lib-huawei/package.json +++ b/packages/libs/lib-huawei/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-huawei", "private": false, - "version": "1.36.18", + "version": "1.36.21", "main": "./dist/bundle.js", "module": "./dist/bundle.js", "types": "./dist/d/index.d.ts", @@ -24,5 +24,5 @@ "prettier": "^2.8.8", "tslib": "^2.8.1" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/libs/lib-iframe/CHANGELOG.md b/packages/libs/lib-iframe/CHANGELOG.md index f2a0a6938..3f16bd93d 100644 --- a/packages/libs/lib-iframe/CHANGELOG.md +++ b/packages/libs/lib-iframe/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/lib-iframe + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +**Note:** Version bump only for package @certd/lib-iframe + +## [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 diff --git a/packages/libs/lib-iframe/package.json b/packages/libs/lib-iframe/package.json index 8466cce21..6456e9878 100644 --- a/packages/libs/lib-iframe/package.json +++ b/packages/libs/lib-iframe/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-iframe", "private": false, - "version": "1.36.18", + "version": "1.36.21", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -31,5 +31,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/libs/lib-jdcloud/CHANGELOG.md b/packages/libs/lib-jdcloud/CHANGELOG.md index ffaf02e09..d261d92d3 100644 --- a/packages/libs/lib-jdcloud/CHANGELOG.md +++ b/packages/libs/lib-jdcloud/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/jdcloud + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +**Note:** Version bump only for package @certd/jdcloud + +## [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 diff --git a/packages/libs/lib-jdcloud/package.json b/packages/libs/lib-jdcloud/package.json index df4e817ef..257c73c17 100644 --- a/packages/libs/lib-jdcloud/package.json +++ b/packages/libs/lib-jdcloud/package.json @@ -1,6 +1,6 @@ { "name": "@certd/jdcloud", - "version": "1.36.18", + "version": "1.36.21", "description": "jdcloud openApi sdk", "main": "./dist/bundle.js", "module": "./dist/bundle.js", @@ -61,5 +61,5 @@ "fetch" ] }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/libs/lib-k8s/CHANGELOG.md b/packages/libs/lib-k8s/CHANGELOG.md index f7570468a..0c9f1d6cc 100644 --- a/packages/libs/lib-k8s/CHANGELOG.md +++ b/packages/libs/lib-k8s/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/lib-k8s + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Bug Fixes + +* 修复secret patch 类型多了type:的bug ([d04f383](https://github.com/certd/certd/commit/d04f3831611011a90ec0594724b9694490d5edd0)) + +## [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 diff --git a/packages/libs/lib-k8s/package.json b/packages/libs/lib-k8s/package.json index 2ae6f74a0..fe978315a 100644 --- a/packages/libs/lib-k8s/package.json +++ b/packages/libs/lib-k8s/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-k8s", "private": false, - "version": "1.36.18", + "version": "1.36.21", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -17,7 +17,7 @@ "pub": "npm publish" }, "dependencies": { - "@certd/basic": "^1.36.18", + "@certd/basic": "^1.36.21", "@kubernetes/client-node": "0.21.0" }, "devDependencies": { @@ -32,5 +32,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/libs/lib-k8s/src/lib/k8s.client.ts b/packages/libs/lib-k8s/src/lib/k8s.client.ts index 0e2fac476..d5f8f6bd0 100644 --- a/packages/libs/lib-k8s/src/lib/k8s.client.ts +++ b/packages/libs/lib-k8s/src/lib/k8s.client.ts @@ -1,7 +1,7 @@ import { CoreV1Api, KubeConfig, NetworkingV1Api, V1Ingress, V1Secret } from "@kubernetes/client-node"; import dns from "dns"; import { ILogger } from "@certd/basic"; -import _ from "lodash-es"; +import { merge } from "lodash-es"; export type K8sClientOpts = { kubeConfigStr: string; @@ -85,7 +85,6 @@ export class K8sClient { /** * 创建Secret * @param opts {namespace:default, body:yamlStr} - * @returns {Promise<*>} */ async createSecret(opts: { namespace: string; body: V1Secret }) { const namespace = opts.namespace || "default"; @@ -119,7 +118,13 @@ export class K8sClient { this.logger.warn(`secret ${secretName} 不存在`); if (opts.createOnNotFound) { //没有找到,则创建 - const res = await this.createSecret({ namespace, body: opts.body }); + const body = merge( + { + type: "kubernetes.io/tls", + }, + opts.body + ); + const res = await this.createSecret({ namespace, body }); this.logger.info(`secret ${secretName} 已创建`); return res; } @@ -127,7 +132,7 @@ export class K8sClient { throw e; } - const newSecret = _.merge(oldSecret.body, opts.body); + const newSecret = merge(oldSecret.body, opts.body); const res = await this.client.replaceNamespacedSecret(secretName, namespace, newSecret); this.logger.info(`secret ${secretName} 已更新`); return res.body; @@ -161,7 +166,7 @@ export class K8sClient { this.logger.info("patch ingress:", ingressName, namespace); const client = this.kubeconfig.makeApiClient(NetworkingV1Api); const oldIngress = await client.readNamespacedIngress(ingressName, namespace); - const newIngress = _.merge(oldIngress.body, opts.body); + const newIngress = merge(oldIngress.body, opts.body); const res = await client.replaceNamespacedIngress(ingressName, namespace, newIngress); this.logger.info("ingress patched", opts.body); return res; diff --git a/packages/libs/lib-server/CHANGELOG.md b/packages/libs/lib-server/CHANGELOG.md index 213e6268e..09d08cfdf 100644 --- a/packages/libs/lib-server/CHANGELOG.md +++ b/packages/libs/lib-server/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/lib-server + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Performance Improvements + +* 登录支持极验验证码 ([370db62](https://github.com/certd/certd/commit/370db62bf0aece241859244927beabba32d6a257)) +* 登录注册、找回密码都支持极验验证码和图片验证码 ([7bdde68](https://github.com/certd/certd/commit/7bdde68ecea29fe2c570fd3cb082139db6c93d93)) + +## [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 diff --git a/packages/libs/lib-server/package.json b/packages/libs/lib-server/package.json index 329dfa97d..90f08b2fc 100644 --- a/packages/libs/lib-server/package.json +++ b/packages/libs/lib-server/package.json @@ -1,6 +1,6 @@ { "name": "@certd/lib-server", - "version": "1.36.18", + "version": "1.36.21", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -27,10 +27,10 @@ ], "license": "AGPL", "dependencies": { - "@certd/acme-client": "^1.36.18", - "@certd/basic": "^1.36.18", - "@certd/pipeline": "^1.36.18", - "@certd/plus-core": "^1.36.18", + "@certd/acme-client": "^1.36.21", + "@certd/basic": "^1.36.21", + "@certd/pipeline": "^1.36.21", + "@certd/plus-core": "^1.36.21", "@midwayjs/cache": "~3.14.0", "@midwayjs/core": "~3.20.3", "@midwayjs/i18n": "~3.20.3", @@ -61,5 +61,5 @@ "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/libs/lib-server/src/index.ts b/packages/libs/lib-server/src/index.ts index a6d3f603b..f4bfaf593 100644 --- a/packages/libs/lib-server/src/index.ts +++ b/packages/libs/lib-server/src/index.ts @@ -1,8 +1,9 @@ import { SysSettingsEntity } from './system/index.js'; import { AccessEntity } from './user/access/entity/access.js'; +import { AddonEntity } from "./user/index.js"; export * from './basic/index.js'; export * from './system/index.js'; export * from './user/index.js'; export { LibServerConfiguration as Configuration } from './configuration.js'; -export const libServerEntities = [SysSettingsEntity, AccessEntity]; +export const libServerEntities = [SysSettingsEntity, AccessEntity,AddonEntity]; diff --git a/packages/libs/lib-server/src/system/settings/service/models.ts b/packages/libs/lib-server/src/system/settings/service/models.ts index c31f14fbe..af7ddf052 100644 --- a/packages/libs/lib-server/src/system/settings/service/models.ts +++ b/packages/libs/lib-server/src/system/settings/service/models.ts @@ -30,6 +30,13 @@ export class SysPublicSettings extends BaseSettings { mpsNo?: string; robots?: boolean = true; aiChatEnabled = true; + + + //验证码是否开启 + captchaEnabled = false; + //验证码类型 + captchaType?: string; + captchaAddonId?:number; } export class SysPrivateSettings extends BaseSettings { @@ -207,4 +214,3 @@ export class SysSafeSetting extends BaseSettings { }; } - diff --git a/packages/libs/lib-server/src/user/addon/api/api.ts b/packages/libs/lib-server/src/user/addon/api/api.ts new file mode 100644 index 000000000..4e65ce86c --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/api.ts @@ -0,0 +1,97 @@ +import { HttpClient, ILogger, utils } from "@certd/basic"; +import {upperFirst} from "lodash-es"; +import { FormItemProps, PluginRequestHandleReq, Registrable } from "@certd/pipeline"; + + +export type AddonRequestHandleReqInput = { + id?: number; + title?: string; + addon: T; +}; + +export type AddonRequestHandleReq = { + addonType: string; +} &PluginRequestHandleReq>; + +export type AddonInputDefine = FormItemProps & { + title: string; + required?: boolean; +}; +export type AddonDefine = Registrable & { + addonType: string; + needPlus?: boolean; + input?: { + [key: string]: AddonInputDefine; + }; + showTest?: boolean; +}; + +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(id: any): Promise; + getCommonById(id: any): Promise; +} diff --git a/packages/libs/lib-server/src/user/addon/api/decorator.ts b/packages/libs/lib-server/src/user/addon/api/decorator.ts new file mode 100644 index 000000000..6a96b49ac --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/decorator.ts @@ -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; +} + + diff --git a/packages/libs/lib-server/src/user/addon/api/index.ts b/packages/libs/lib-server/src/user/addon/api/index.ts new file mode 100644 index 000000000..9b9e3a489 --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/index.ts @@ -0,0 +1,3 @@ +export * from "./api.js"; +export * from "./registry.js"; +export * from "./decorator.js"; diff --git a/packages/libs/lib-server/src/user/addon/api/registry.ts b/packages/libs/lib-server/src/user/addon/api/registry.ts new file mode 100644 index 000000000..643de99cf --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/registry.ts @@ -0,0 +1,3 @@ +import { createRegistry } from "@certd/pipeline"; + +export const addonRegistry = createRegistry("addon"); diff --git a/packages/libs/lib-server/src/user/addon/entity/addon.ts b/packages/libs/lib-server/src/user/addon/entity/addon.ts new file mode 100644 index 000000000..4d16fb43b --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/entity/addon.ts @@ -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; +} diff --git a/packages/libs/lib-server/src/user/addon/index.ts b/packages/libs/lib-server/src/user/addon/index.ts new file mode 100644 index 000000000..727232b4f --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/index.ts @@ -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' diff --git a/packages/libs/lib-server/src/user/addon/service/addon-getter.ts b/packages/libs/lib-server/src/user/addon/service/addon-getter.ts new file mode 100644 index 000000000..8e91ad7e0 --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/service/addon-getter.ts @@ -0,0 +1,18 @@ +import { IAddonGetter } from "../api/index.js"; + +export class AddonGetter implements IAddonGetter { + userId: number; + getter: (id: any, userId?: number) => Promise; + constructor(userId: number, getter: (id: any, userId: number) => Promise) { + this.userId = userId; + this.getter = getter; + } + + async getById(id: any) { + return await this.getter(id, this.userId); + } + + async getCommonById(id: any) { + return await this.getter(id, 0); + } +} diff --git a/packages/libs/lib-server/src/user/addon/service/addon-service.ts b/packages/libs/lib-server/src/user/addon/service/addon-service.ts new file mode 100644 index 000000000..d58d2453c --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/service/addon-service.ts @@ -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 { + @InjectEntityModel(AddonEntity) + repository: Repository; + + //@ts-ignore + getRepository() { + return this.repository; + } + + async page(pageReq: PageReq) { + 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 { + 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 { + 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 { + 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); + } +} diff --git a/packages/libs/lib-server/src/user/addon/service/addon-sys-getter.ts b/packages/libs/lib-server/src/user/addon/service/addon-sys-getter.ts new file mode 100644 index 000000000..773e1a7aa --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/service/addon-sys-getter.ts @@ -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(id: any) { + return await this.addonService.getById(id, 0); + } + + async getCommonById(id: any) { + return await this.addonService.getById(id, 0); + } +} diff --git a/packages/libs/lib-server/src/user/index.ts b/packages/libs/lib-server/src/user/index.ts index 17e3af2c4..f0fce929a 100644 --- a/packages/libs/lib-server/src/user/index.ts +++ b/packages/libs/lib-server/src/user/index.ts @@ -1 +1,2 @@ export * from './access/index.js'; +export * from './addon/index.js'; diff --git a/packages/libs/midway-flyway-js/CHANGELOG.md b/packages/libs/midway-flyway-js/CHANGELOG.md index 771525204..14b5cb040 100644 --- a/packages/libs/midway-flyway-js/CHANGELOG.md +++ b/packages/libs/midway-flyway-js/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/midway-flyway-js + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +**Note:** Version bump only for package @certd/midway-flyway-js + +## [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 diff --git a/packages/libs/midway-flyway-js/package.json b/packages/libs/midway-flyway-js/package.json index ab01b2a23..1618ec4e8 100644 --- a/packages/libs/midway-flyway-js/package.json +++ b/packages/libs/midway-flyway-js/package.json @@ -1,6 +1,6 @@ { "name": "@certd/midway-flyway-js", - "version": "1.36.18", + "version": "1.36.21", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -46,5 +46,5 @@ "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/plugins/plugin-cert/CHANGELOG.md b/packages/plugins/plugin-cert/CHANGELOG.md index 3e8521a58..c565d39a9 100644 --- a/packages/plugins/plugin-cert/CHANGELOG.md +++ b/packages/plugins/plugin-cert/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +### Bug Fixes + +* 修复ssl.com报EMAILADDRESS数量不对的bug ([c560cc5](https://github.com/certd/certd/commit/c560cc5adda6e15bf3a8865d874042550a6c2688)) + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Bug Fixes + +* 修复证书手动托管时新上传的证书无效的bug ([506385e](https://github.com/certd/certd/commit/506385e5a2600887fe30854e0713583caaa2e689)) + +### Performance Improvements + +* 证书到期剩余天数进度条根据实际证书有效期计算 ([#528](https://github.com/certd/certd/issues/528)) nicheng-he ([2d4586b](https://github.com/certd/certd/commit/2d4586b1c42c39f97d2a95b9453cca4bc8bfbe61)) +* add preferred chain option ([#519](https://github.com/certd/certd/issues/519)) @ZeroClover ([902359f](https://github.com/certd/certd/commit/902359f24ed12eee4f9b65178f1d6a60378351d2)) + +## [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 diff --git a/packages/plugins/plugin-cert/package.json b/packages/plugins/plugin-cert/package.json index c6d56d933..7b15ccc4f 100644 --- a/packages/plugins/plugin-cert/package.json +++ b/packages/plugins/plugin-cert/package.json @@ -1,7 +1,7 @@ { "name": "@certd/plugin-cert", "private": false, - "version": "1.36.18", + "version": "1.36.21", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -16,10 +16,10 @@ "pub": "npm publish" }, "dependencies": { - "@certd/acme-client": "^1.36.18", - "@certd/basic": "^1.36.18", - "@certd/pipeline": "^1.36.18", - "@certd/plugin-lib": "^1.36.18", + "@certd/acme-client": "^1.36.21", + "@certd/basic": "^1.36.21", + "@certd/pipeline": "^1.36.21", + "@certd/plugin-lib": "^1.36.21", "@google-cloud/publicca": "^1.3.0", "dayjs": "^1.11.7", "jszip": "^3.10.1", @@ -43,5 +43,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/plugins/plugin-cert/src/access/eab-access.ts b/packages/plugins/plugin-cert/src/access/eab-access.ts index f5c7379f3..1162c4df9 100644 --- a/packages/plugins/plugin-cert/src/access/eab-access.ts +++ b/packages/plugins/plugin-cert/src/access/eab-access.ts @@ -12,7 +12,7 @@ export class EabAccess extends BaseAccess { component: { placeholder: "kid / keyId", }, - helper: "EAB KID, google的叫 keyId", + helper: "EAB KID, google的叫 keyId,ssl.com的叫Account/ACME Key", required: true, encrypt: true, }) diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts index 3f55a27bf..5bb207519 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -50,7 +50,7 @@ export type CertInfo = { 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"; type AcmeServiceOptions = { userContext: IContext; @@ -329,8 +329,9 @@ export class AcmeService { isTest?: boolean; privateKeyType?: string; profile?: string; + preferredChain?: string; }): Promise { - 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); let domains = options.domains; @@ -373,6 +374,7 @@ export class AcmeService { commonName, ...csrInfo, altNames, + // emailAddress: email, }, privateKey ); @@ -403,6 +405,7 @@ export class AcmeService { }, signal: this.options.signal, profile, + preferredChain, }); const crtString = crt.toString(); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts index e9d593139..e8ad9d756 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts @@ -99,6 +99,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin { const cert: CertInfo = certReader.toCertInfo(); this.cert = cert; + this._result.pipelineVars.certEffectiveTime = dayjs(certReader.detail.notBefore).valueOf(); this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf(); if (!this._result.pipelinePrivateVars) { this._result.pipelinePrivateVars = {}; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts index df31264f1..8fbaa32d3 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts @@ -35,6 +35,7 @@ export class CertReader { detail: CertificateInfo; //毫秒时间戳 + effective: number; expires: number; constructor(certInfo: CertInfo) { this.cert = certInfo; @@ -52,8 +53,9 @@ export class CertReader { } try { - const { detail, expires } = this.getCrtDetail(this.cert.crt); + const { detail, effective, expires } = this.getCrtDetail(this.cert.crt); this.detail = detail; + this.effective = effective.getTime(); this.expires = expires.getTime(); } catch (e) { throw new Error("证书解析失败:" + e.message); @@ -102,8 +104,9 @@ export class CertReader { static readCertDetail(crt: string) { const detail = crypto.readCertificateInfo(crt.toString()); + const effective = detail.notBefore; const expires = detail.notAfter; - return { detail, expires }; + return { detail, effective, expires }; } getAllDomains() { @@ -221,10 +224,10 @@ export class CertReader { return `${prefix}_${domain}_${timeStr}.${suffix}`; } - buildCertName() { + buildCertName(prefix: string = "") { let domain = this.getMainDomain(); domain = domain.replaceAll(".", "_").replaceAll("*", "_"); - return `${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`; + return `${prefix}_${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`; } static appendTimeSuffix(name?: string) { diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts index d11a719cb..841b8914e 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts @@ -118,7 +118,7 @@ export class CertApplyUploadPlugin extends CertApplyBaseConvertPlugin { } async execute(): Promise { - const certReader = await this.getCertFromStore(); + let certReader = await this.getCertFromStore(); const crtMd5 = this.ctx.utils.hash.md5(certReader.cert.crt); const leftDays = dayjs(certReader.expires).diff(dayjs(), "day"); @@ -141,9 +141,13 @@ export class CertApplyUploadPlugin extends CertApplyBaseConvertPlugin { this.logger.info("输入参数有变化,重新部署"); } + certReader = new CertReader(this.uploadCert); this.clearLastStatus(); //输出证书MD5 - this.certMd5 = crtMd5; + this.certMd5 = this.ctx.utils.hash.md5(certReader.cert.crt); + const newLeftDays = dayjs(certReader.expires).diff(dayjs(), "day"); + this.logger.info(`新证书过期时间${dayjs(certReader.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${newLeftDays}天`); + await this.output(certReader, true); //必须output之后执行 diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts index 6c654cf17..e4d9ea539 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -38,6 +38,53 @@ export type DomainsVerifyPlanInput = { [key: string]: DomainVerifyPlanInput; }; +const preferredChainConfigs = { + letsencrypt: { + helper: "如无特殊需求保持默认即可", + options: [ + { value: "ISRG Root X1", label: "ISRG Root X1" }, + { value: "ISRG Root X2", label: "ISRG Root X2" }, + ], + }, + google: { + helper: "GlobalSign 提供对老旧设备更好的兼容性,但证书链会变长", + options: [ + { value: "GTS Root R1", label: "GTS Root R1" }, + { value: "GlobalSign", label: "GlobalSign" }, + ], + }, +} as const; + +const preferredChainSupportedProviders = Object.keys(preferredChainConfigs); + +const preferredChainMergeScript = (() => { + const configs = JSON.stringify(preferredChainConfigs); + const supportedProviders = JSON.stringify(preferredChainSupportedProviders); + const defaultProvider = JSON.stringify(preferredChainSupportedProviders[0]); + return ` + const chainConfigs = ${configs}; + const supportedProviders = ${supportedProviders}; + const defaultProvider = ${defaultProvider}; + const getConfig = (provider)=> chainConfigs[provider] || chainConfigs[defaultProvider]; + return { + show: ctx.compute(({form})=> supportedProviders.includes(form.sslProvider)), + component: { + options: ctx.compute(({form})=> getConfig(form.sslProvider).options) + }, + helper: ctx.compute(({form})=> getConfig(form.sslProvider).helper), + value: ctx.compute(({form})=>{ + const { options } = getConfig(form.sslProvider); + const allowed = options.map(item=>item.value); + const current = form.preferredChain; + if(allowed.includes(current)){ + return current; + } + return allowed[0]; + }) + }; + `; +})(); + @IsTaskPlugin({ name: "CertApply", title: "证书申请(JS版)", @@ -89,9 +136,10 @@ export class CertApplyPlugin extends CertApplyBasePlugin { { value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" }, { value: "google", label: "Google", icon: "flat-color-icons:google" }, { 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授权,无需翻墙\nSSL.com:仅主域名和www免费,必须设置CAA记录", required: true, }) sslProvider!: SSLProvider; @@ -104,7 +152,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin { mergeScript: ` return { show: ctx.compute(({form})=>{ - return form.challengeType === 'dns' + return form.challengeType === 'dns' }), component:{ onSelectedChange: ctx.compute(({form})=>{ @@ -137,7 +185,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin { }) }, show: ctx.compute(({form})=>{ - return form.challengeType === 'dns' + return form.challengeType === 'dns' }) } `, @@ -194,6 +242,13 @@ export class CertApplyPlugin extends CertApplyBasePlugin { }) zerosslCommonEabAccessId!: number; + @TaskInput({ + title: "SSL.com公共EAB授权", + isSys: true, + show: false, + }) + sslcomCommonEabAccessId!: number; + @TaskInput({ title: "EAB授权", component: { @@ -203,11 +258,16 @@ export class CertApplyPlugin extends CertApplyBasePlugin { maybeNeed: true, required: false, 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: ` return { 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 +339,19 @@ export class CertApplyPlugin extends CertApplyBasePlugin { }) certProfile!: string; + @TaskInput({ + title: "首选链", + component: { + name: "a-select", + vModel: "value", + options: preferredChainConfigs.letsencrypt.options, + }, + helper: preferredChainConfigs.letsencrypt.helper, + required: false, + mergeScript: preferredChainMergeScript, + }) + preferredChain!: string; + @TaskInput({ title: "使用代理", value: false, @@ -339,8 +412,8 @@ export class CertApplyPlugin extends CertApplyBasePlugin { async onInit() { let eab: EabAccess = null; - if (this.sslProvider === "google") { - if (this.googleAccessId) { + if (this.sslProvider && this.sslProvider !== "letsencrypt") { + if (this.sslProvider === "google" && this.googleAccessId) { this.logger.info("当前正在使用 google服务账号授权获取EAB"); const googleAccess = await this.getAccess(this.googleAccessId); const googleClient = new GoogleClient({ @@ -348,24 +421,19 @@ export class CertApplyPlugin extends CertApplyBasePlugin { logger: this.logger, }); 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 { - throw new Error("google需要配置EAB授权或服务账号授权"); - } - } else if (this.sslProvider === "zerossl") { - if (this.eabAccessId) { - this.logger.info("当前正在使用 zerossl EAB授权"); - eab = await this.getAccess(this.eabAccessId); - } else if (this.zerosslCommonEabAccessId) { - this.logger.info("当前正在使用 zerossl 公共EAB授权"); - eab = await this.getAccess(this.zerosslCommonEabAccessId, true); - } else { - throw new Error("zerossl需要配置EAB授权"); + const getEab = async (type: string) => { + if (this.eabAccessId) { + this.logger.info(`当前正在使用 ${type} EAB授权`); + eab = await this.getAccess(this.eabAccessId); + } else if (this[`${type}CommonEabAccessId`]) { + this.logger.info(`当前正在使用 ${type} 公共EAB授权`); + eab = await this.getAccess(this[`${type}CommonEabAccessId`], true); + } else { + throw new Error(`${type}需要配置EAB授权`); + } + }; + await getEab(this.sslProvider); } } this.eab = eab; @@ -397,12 +465,12 @@ export class CertApplyPlugin extends CertApplyBasePlugin { const csrInfo = _.merge( { - country: "CN", - state: "GuangDong", - locality: "ShengZhen", - organization: "CertD Org.", - organizationUnit: "IT Department", - emailAddress: email, + // country: "CN", + // state: "GuangDong", + // locality: "ShengZhen", + // organization: "CertD Org.", + // organizationUnit: "IT Department", + // emailAddress: email, }, this.csrInfo ? JSON.parse(this.csrInfo) : {} ); @@ -430,6 +498,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin { isTest: false, privateKeyType: this.privateKeyType, profile: this.certProfile, + preferredChain: this.preferredChain, }); const certInfo = this.formatCerts(cert); diff --git a/packages/plugins/plugin-lib/CHANGELOG.md b/packages/plugins/plugin-lib/CHANGELOG.md index d2c403c37..1d40036c7 100644 --- a/packages/plugins/plugin-lib/CHANGELOG.md +++ b/packages/plugins/plugin-lib/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/plugin-lib + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Performance Improvements + +* ssh配置增加脚本类型设置,bash还是sh ([ae41c60](https://github.com/certd/certd/commit/ae41c6038b27c9476e64a2402a8daf247c38a5b6)) + +## [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 diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index 7d96007cf..e67ddeced 100644 --- a/packages/plugins/plugin-lib/package.json +++ b/packages/plugins/plugin-lib/package.json @@ -1,7 +1,7 @@ { "name": "@certd/plugin-lib", "private": false, - "version": "1.36.18", + "version": "1.36.21", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -21,8 +21,8 @@ "@alicloud/pop-core": "^1.7.10", "@alicloud/tea-util": "^1.4.10", "@aws-sdk/client-s3": "^3.787.0", - "@certd/basic": "^1.36.18", - "@certd/pipeline": "^1.36.18", + "@certd/basic": "^1.36.21", + "@certd/pipeline": "^1.36.21", "@kubernetes/client-node": "0.21.0", "ali-oss": "^6.22.0", "basic-ftp": "^5.0.5", @@ -53,5 +53,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f" + "gitHead": "3cedef4974708d828fb972acc54af0515e3ec3a0" } diff --git a/packages/plugins/plugin-lib/src/ssh/ssh-access.ts b/packages/plugins/plugin-lib/src/ssh/ssh-access.ts index 984a435cc..4d20696d8 100644 --- a/packages/plugins/plugin-lib/src/ssh/ssh-access.ts +++ b/packages/plugins/plugin-lib/src/ssh/ssh-access.ts @@ -64,6 +64,22 @@ export class SshAccess extends BaseAccess { }) 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({ title: "伪终端", helper: "如果登录报错:all authentication methods failed,可以尝试开启伪终端模式进行keyboard-interactive方式登录\n开启后对日志输出有一定的影响", @@ -86,6 +102,15 @@ export class SshAccess extends BaseAccess { }) socksProxy!: string; + @AccessInput({ + title: "超时时间", + helper: "执行命令的超时时间,单位秒,默认30分钟", + component: { + name: "a-input-number", + }, + }) + timeout: number; + @AccessInput({ title: "是否Windows", helper: "如果是Windows主机,请勾选此项\n并且需要windows[安装OpenSSH](https://certd.docmirror.cn/guide/use/host/windows.html)", @@ -136,9 +161,10 @@ export class SshAccess extends BaseAccess { const { SshClient } = await import("./ssh.js"); const client = new SshClient(this.ctx.logger); + const script = ["echo hello", "exit"]; await client.exec({ connectConf: this, - script: "echo hello", + script: script, }); return "ok"; } diff --git a/packages/plugins/plugin-lib/src/ssh/ssh.ts b/packages/plugins/plugin-lib/src/ssh/ssh.ts index d0aa74e3c..197ebfc1d 100644 --- a/packages/plugins/plugin-lib/src/ssh/ssh.ts +++ b/packages/plugins/plugin-lib/src/ssh/ssh.ts @@ -469,7 +469,8 @@ export class SshClient { async isCmd(conn: AsyncSsh2Client) { 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; } else { return true; @@ -542,8 +543,16 @@ export class SshClient { } } - if (isLinux && options.stopOnError !== false) { - script = "set -e\n" + script; + 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; + } } return await conn.exec(script as string, { throwOnStdErr }); @@ -587,10 +596,15 @@ export class SshClient { } throw e; } - + let timeoutId = null; try { + timeoutId = setTimeout(() => { + this.logger.info("执行超时,断开连接"); + conn.end(); + }, 1000 * (connectConf.timeout || 1800)); return await callable(conn); } finally { + clearTimeout(timeoutId); conn.end(); } } diff --git a/packages/plugins/plugin-lib/src/tencent/lib/ssl-client.ts b/packages/plugins/plugin-lib/src/tencent/lib/ssl-client.ts index 4846ee1ae..2075d4705 100644 --- a/packages/plugins/plugin-lib/src/tencent/lib/ssl-client.ts +++ b/packages/plugins/plugin-lib/src/tencent/lib/ssl-client.ts @@ -76,9 +76,26 @@ export class TencentSslClient { return res; } - async DescribeCertificates(params: any) { + async DescribeHostUploadUpdateRecordDetail(params: any) { 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); return res; } diff --git a/packages/ui/certd-client/CHANGELOG.md b/packages/ui/certd-client/CHANGELOG.md index c91dfd093..abdba4681 100644 --- a/packages/ui/certd-client/CHANGELOG.md +++ b/packages/ui/certd-client/CHANGELOG.md @@ -3,6 +3,42 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +### Bug Fixes + +* 修复导入插件对话框无法打开的bug,修复插件编辑页面打开多个代码编辑器消失的bug ([e5a080a](https://github.com/certd/certd/commit/e5a080aebe0d2f3e3c0f86bf863f75069c1bf7ab)) + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Bug Fixes + +* 修复商业版退出登录后,丢失站点个性化设置的bug ([d75dd05](https://github.com/certd/certd/commit/d75dd058d65c85f80c49e1fa7a910e6c6f08e824)) +* 修复授权类型和名称字段排到最后的bug ([43b7977](https://github.com/certd/certd/commit/43b79778ea9034065f6a15af3296274315597c6b)) + +### Performance Improvements + +* 登录支持极验验证码 ([370db62](https://github.com/certd/certd/commit/370db62bf0aece241859244927beabba32d6a257)) +* 登录注册、找回密码都支持极验验证码和图片验证码 ([7bdde68](https://github.com/certd/certd/commit/7bdde68ecea29fe2c570fd3cb082139db6c93d93)) +* 优化加量包展示效果 ([3c65f37](https://github.com/certd/certd/commit/3c65f37d84177ba107d4a6462648af12d2fc4b7a)) +* 证书到期剩余天数进度条根据实际证书有效期计算 ([#528](https://github.com/certd/certd/issues/528)) nicheng-he ([2d4586b](https://github.com/certd/certd/commit/2d4586b1c42c39f97d2a95b9453cca4bc8bfbe61)) + +## [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 diff --git a/packages/ui/certd-client/index.html b/packages/ui/certd-client/index.html index 1740a304a..d760b2f58 100644 --- a/packages/ui/certd-client/index.html +++ b/packages/ui/certd-client/index.html @@ -23,5 +23,6 @@ + diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index 156fb9e60..0d25695a4 100644 --- a/packages/ui/certd-client/package.json +++ b/packages/ui/certd-client/package.json @@ -1,6 +1,6 @@ { "name": "@certd/ui-client", - "version": "1.36.18", + "version": "1.36.21", "private": true, "scripts": { "dev": "vite --open", @@ -32,10 +32,11 @@ "@aws-sdk/s3-request-presigner": "^3.535.0", "@certd/vue-js-cron-light": "^4.0.14", "@ctrl/tinycolor": "^4.1.0", - "@fast-crud/fast-crud": "^1.25.13", - "@fast-crud/fast-extends": "^1.25.13", - "@fast-crud/ui-antdv4": "^1.25.13", - "@fast-crud/ui-interface": "^1.25.13", + "@fast-crud/editor-code": "^1.26.6", + "@fast-crud/fast-crud": "^1.26.6", + "@fast-crud/fast-extends": "^1.26.6", + "@fast-crud/ui-antdv4": "^1.26.6", + "@fast-crud/ui-interface": "^1.26.6", "@iconify/tailwind": "^1.2.0", "@iconify/vue": "^4.1.1", "@manypkg/get-packages": "^2.2.2", @@ -103,8 +104,8 @@ "zod-defaults": "^0.1.3" }, "devDependencies": { - "@certd/lib-iframe": "^1.36.18", - "@certd/pipeline": "^1.36.18", + "@certd/lib-iframe": "^1.36.21", + "@certd/pipeline": "^1.36.21", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", "@types/chai": "^4.3.12", @@ -138,7 +139,7 @@ "prettier": "3.3.3", "pretty-quick": "^4.0.0", "rimraf": "^5.0.5", - "rollup": "^4.13.0", + "rollup": "4.50.0", "rollup-plugin-visualizer": "^5.12.0", "stylelint": "^15.11.0", "stylelint-order": "^6.0.4", @@ -148,7 +149,7 @@ "tslint": "^6.1.3", "typescript": "^5.4.2", "unplugin-vue-define-options": "^1.4.2", - "vite": "^5.3.1", + "vite": "5.4.19", "vite-plugin-compression": "^0.5.1", "vite-plugin-html": "^3.2.2", "vite-plugin-theme": "^0.8.6", diff --git a/packages/ui/certd-client/src/components/captcha/captcha-input.vue b/packages/ui/certd-client/src/components/captcha/captcha-input.vue new file mode 100644 index 000000000..3a348c332 --- /dev/null +++ b/packages/ui/certd-client/src/components/captcha/captcha-input.vue @@ -0,0 +1,50 @@ + + diff --git a/packages/ui/certd-client/src/components/captcha/captchas/geetest_captcha.vue b/packages/ui/certd-client/src/components/captcha/captchas/geetest_captcha.vue new file mode 100644 index 000000000..926b9da96 --- /dev/null +++ b/packages/ui/certd-client/src/components/captcha/captchas/geetest_captcha.vue @@ -0,0 +1,96 @@ + + + diff --git a/packages/ui/certd-client/src/components/captcha/captchas/image_captcha.vue b/packages/ui/certd-client/src/components/captcha/captchas/image_captcha.vue new file mode 100644 index 000000000..50be867a0 --- /dev/null +++ b/packages/ui/certd-client/src/components/captcha/captchas/image_captcha.vue @@ -0,0 +1,59 @@ + + diff --git a/packages/ui/certd-client/src/components/plugins/common/remote-auto-complete.vue b/packages/ui/certd-client/src/components/plugins/common/remote-auto-complete.vue index 2a5eb790d..a46c3d66b 100644 --- a/packages/ui/certd-client/src/components/plugins/common/remote-auto-complete.vue +++ b/packages/ui/certd-client/src/components/plugins/common/remote-auto-complete.vue @@ -66,7 +66,7 @@ const getOptions = async () => { const input = (pluginType === "plugin" ? form?.input : form) || {}; for (let key in define.input) { - const inWatches = props.watches.includes(key); + const inWatches = props.watches?.includes(key); const inputDefine = define.input[key]; if (inWatches && inputDefine.required) { const value = input[key]; diff --git a/packages/ui/certd-client/src/components/plugins/common/remote-select.vue b/packages/ui/certd-client/src/components/plugins/common/remote-select.vue index 7e1e9da05..707f118a4 100644 --- a/packages/ui/certd-client/src/components/plugins/common/remote-select.vue +++ b/packages/ui/certd-client/src/components/plugins/common/remote-select.vue @@ -105,7 +105,7 @@ const getOptions = async () => { const input = (pluginType === "plugin" ? form?.input : form) || {}; for (let key in define.input) { - const inWatches = props.watches.includes(key); + const inWatches = props.watches?.includes(key); const inputDefine = define.input[key]; if (inWatches && inputDefine.required) { const value = input[key]; @@ -169,7 +169,7 @@ const getOptions = async () => { }; 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() { diff --git a/packages/ui/certd-client/src/components/plugins/common/remote-tree-select.vue b/packages/ui/certd-client/src/components/plugins/common/remote-tree-select.vue new file mode 100644 index 000000000..ca039db49 --- /dev/null +++ b/packages/ui/certd-client/src/components/plugins/common/remote-tree-select.vue @@ -0,0 +1,191 @@ + + + + diff --git a/packages/ui/certd-client/src/components/plugins/index.ts b/packages/ui/certd-client/src/components/plugins/index.ts index 61fadc997..be4697212 100644 --- a/packages/ui/certd-client/src/components/plugins/index.ts +++ b/packages/ui/certd-client/src/components/plugins/index.ts @@ -2,6 +2,7 @@ import SynologyIdDeviceGetter from "./synology/device-id-getter.vue"; import RemoteAutoComplete from "./common/remote-auto-complete.vue"; import RemoteSelect from "./common/remote-select.vue"; import RemoteInput from "./common/remote-input.vue"; +import RemoteTreeSelect from "./common/remote-tree-select.vue"; import CertDomainsGetter from "./common/cert-domains-getter.vue"; import OutputSelector from "/@/components/plugins/common/output-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("RemoteAutoComplete", RemoteAutoComplete); app.component("RemoteSelect", RemoteSelect); + app.component("RemoteTreeSelect", RemoteTreeSelect); app.component("RemoteInput", RemoteInput); app.component("CertDomainsGetter", CertDomainsGetter); app.component("InputPassword", InputPassword); diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index af8aac992..2dbc451ad 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -21,6 +21,7 @@ export default { pipeline: "Pipeline", domain: "Domain", deployTimes: "Deployments", + monitorCount: "DomainMonitors", duration: "Duration", price: "Price", paymentMethod: "Payment Method", @@ -118,6 +119,7 @@ export default { scheduledTaskCount: "Scheduled Task Count", deployTaskCount: "Deployment Task Count", remainingValidity: "Remaining Validity", + effectiveTime: "Effective time", expiryTime: "Expiry Time", status: "Status", lastRun: "Last Run", @@ -218,6 +220,7 @@ export default { 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.", notificationTitle: "Failure Notification", + notificationWhen: "Notification Timing", notificationHelper: "Get real-time alerts when the task fails", groupIdTitle: "Pipeline Group", }, @@ -248,7 +251,9 @@ export default { ok: "Valid", expired: "Expired", }, + certEffectiveTime: "Certificate Effective", certExpiresTime: "Certificate Expiration", + remainingValidity: "Remaining Validity", expired: "expired", days: "days", lastCheckTime: "Last Check Time", @@ -463,6 +468,7 @@ export default { validDays: "Valid Days", expires: " expires", days: " days", + effectiveTime: "Effective Time", expireTime: "Expiration Time", certIssuer: "Certificate Issuer", applyTime: "Application Time", @@ -705,10 +711,23 @@ export default { pipeline: "Pipeline", }, + addonType: "Type", + addonName: "Name", + addonNameHelper: "Fill freely, helps to distinguish when multiple same type exist", + addonTypeSelect: "Select type", sys: { setting: { showRunStrategy: "Show RunStrategy", showRunStrategyHelper: "Allow modify the run strategy of the task", + + captchaEnabled: "Enable Login Captcha", + captchaHelper: "Whether to enable captcha verification for login", + captchaType: "Captcha Setting", + + baseSetting: "Base Settings", + registerSetting: "Register Settings", + safeSetting: "Safe Settings", + paymentSetting: "Payment Settings", }, }, modal: { @@ -729,4 +748,8 @@ export default { challengeSetting: "Challenge Setting", gotoCnameTip: "Please go to CNAME Record Page", }, + addonSelector: { + select: "Select", + placeholder: "select please", + }, }; diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index ed5b72070..49fb9d13b 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -25,6 +25,7 @@ export default { pipeline: "流水线", domain: "域名", deployTimes: "部署次数", + monitorCount: "域名监控数", duration: "时长", price: "价格", paymentMethod: "支付方式", @@ -124,6 +125,7 @@ export default { scheduledTaskCount: "定时任务数", deployTaskCount: "部署任务数", remainingValidity: "到期剩余", + effectiveTime: "生效时间", expiryTime: "过期时间", status: "状态", lastRun: "最后运行", @@ -223,6 +225,7 @@ export default { triggerCronTitle: "定时触发", triggerCronHelper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行", notificationTitle: "失败通知", + notificationWhen: "通知时机", notificationHelper: "任务执行失败实时提醒", groupIdTitle: "流水线分组", }, @@ -253,7 +256,9 @@ export default { ok: "正常", expired: "过期", }, + certEffectiveTime: "证书生效时间", certExpiresTime: "证书到期时间", + remainingValidity: "到期剩余", expired: "过期", days: "天", lastCheckTime: "上次检查时间", @@ -469,6 +474,7 @@ export default { validDays: "有效天数", expires: "过期", days: "天", + effectiveTime: "生效时间", expireTime: "过期时间", certIssuer: "证书颁发机构", applyTime: "申请时间", @@ -693,7 +699,10 @@ export default { setAsDefault: "设为默认", disabledLabel: "禁用", confirmToggleStatus: "确定要{action}吗?", - + addonType: "类型", + addonName: "名称", + addonNameHelper: "随意填写,相同类型助于区分即可", + addonTypeSelect: "请选择", template: { title: "流水线模版", edit: "流水线模版编辑", @@ -712,6 +721,15 @@ export default { setting: { showRunStrategy: "显示运行策略选择", showRunStrategyHelper: "任务设置中是否允许选择运行策略", + + captchaEnabled: "启用登录验证码", + captchaHelper: "登录时是否启用验证码", + captchaType: "验证码配置", + + baseSetting: "基本设置", + registerSetting: "注册设置", + safeSetting: "安全设置", + paymentSetting: "支付设置", }, }, modal: { @@ -732,4 +750,8 @@ export default { challengeSetting: "校验配置", gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加", }, + addonSelector: { + select: "选择", + placeholder: "请选择", + }, }; diff --git a/packages/ui/certd-client/src/plugin/directive/comm-show.ts b/packages/ui/certd-client/src/plugin/directive/comm-show.ts new file mode 100644 index 000000000..ce4938aa1 --- /dev/null +++ b/packages/ui/certd-client/src/plugin/directive/comm-show.ts @@ -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); + } + }, +}; diff --git a/packages/ui/certd-client/src/plugin/directive/index.ts b/packages/ui/certd-client/src/plugin/directive/index.ts new file mode 100644 index 000000000..e23116b02 --- /dev/null +++ b/packages/ui/certd-client/src/plugin/directive/index.ts @@ -0,0 +1,8 @@ +import comm from "./comm-show.js"; +const install = function (app: any) { + app.directive("comm", comm); +}; + +export default { + install, +}; diff --git a/packages/ui/certd-client/src/plugin/fast-crud/index.tsx b/packages/ui/certd-client/src/plugin/fast-crud/index.tsx index 5f0d9f6da..506c8f2a5 100644 --- a/packages/ui/certd-client/src/plugin/fast-crud/index.tsx +++ b/packages/ui/certd-client/src/plugin/fast-crud/index.tsx @@ -2,7 +2,7 @@ import { request } from "/src/api/service"; // import "/src/mock"; import { ColumnCompositionProps, CrudOptions, FastCrud, PageQuery, PageRes, setLogger, TransformResProps, useColumns, UseCrudProps, UserPageQuery, useTypes, utils } from "@fast-crud/fast-crud"; import "@fast-crud/fast-crud/dist/style.css"; -import { FsExtendsCopyable, FsExtendsEditor, FsExtendsJson, FsExtendsTime, FsExtendsUploader, FsExtendsInput, FsUploaderS3SignedUrlType, FsUploaderGetAuthContext, FsUploaderAliossSTS } from "@fast-crud/fast-extends"; +import { FsExtendsCopyable, FsExtendsEditor, FsExtendsJson, FsExtendsTime, FsExtendsUploader, FsExtendsInput } from "@fast-crud/fast-extends"; import "@fast-crud/fast-extends/dist/style.css"; import UiAntdv from "@fast-crud/ui-antdv4"; import "@fast-crud/ui-antdv4/dist/style.css"; @@ -13,6 +13,9 @@ import { notification } from "ant-design-vue"; import { usePreferences } from "/@/vben/preferences"; import { LocalStorage } from "/@/utils/util.storage"; +import { FsEditorCode } from "@fast-crud/editor-code"; +import "@fast-crud/editor-code/dist/style.css" + class ColumnSizeSaver { save: (key: string, size: number) => void; constructor() { @@ -272,6 +275,7 @@ function install(app: App, options: any = {}) { app.use(FsExtendsTime); app.use(FsExtendsCopyable); app.use(FsExtendsInput); + app.use(FsEditorCode); const { addTypes, getType } = useTypes(); //此处演示修改官方字段类型 diff --git a/packages/ui/certd-client/src/plugin/index.ts b/packages/ui/certd-client/src/plugin/index.ts index 11c6a6aa3..c08034027 100644 --- a/packages/ui/certd-client/src/plugin/index.ts +++ b/packages/ui/certd-client/src/plugin/index.ts @@ -4,9 +4,13 @@ import FastCrud from "./fast-crud"; import permission from "./permission"; import { App } from "vue"; import "./validator/index.js"; +import directives from "./directive/index"; +import { setupMonaco } from "./monaco"; function install(app: App, options: any = {}) { app.use(FastCrud, options); app.use(permission); + app.use(directives); + setupMonaco(); } export default { diff --git a/packages/ui/certd-client/src/plugin/monaco/index.ts b/packages/ui/certd-client/src/plugin/monaco/index.ts new file mode 100644 index 000000000..1b37cb6d0 --- /dev/null +++ b/packages/ui/certd-client/src/plugin/monaco/index.ts @@ -0,0 +1,15 @@ +import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; +import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"; +import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"; +import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"; +import yamlWorker from "./yaml.worker?worker"; +import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"; +import { registerWorker } from "@fast-crud/editor-code"; +export function setupMonaco() { + registerWorker("json", jsonWorker); + registerWorker(["css", "less", "scss"], cssWorker); + registerWorker(["html", "handlebars", "razor"], htmlWorker); + registerWorker(["yaml", "yml"], yamlWorker); + registerWorker(["typescript", "javascript"], tsWorker); + registerWorker("*", editorWorker); +} diff --git a/packages/ui/certd-client/src/plugin/monaco/yaml.worker.ts b/packages/ui/certd-client/src/plugin/monaco/yaml.worker.ts new file mode 100644 index 000000000..e56ff9c93 --- /dev/null +++ b/packages/ui/certd-client/src/plugin/monaco/yaml.worker.ts @@ -0,0 +1 @@ +export * from "monaco-yaml/yaml.worker.js"; diff --git a/packages/ui/certd-client/src/store/plugin/index.ts b/packages/ui/certd-client/src/store/plugin/index.ts index 6ecb356e1..7f0880ee6 100644 --- a/packages/ui/certd-client/src/store/plugin/index.ts +++ b/packages/ui/certd-client/src/store/plugin/index.ts @@ -167,7 +167,7 @@ export const usePluginStore = defineStore({ }, async clear() { this.group = null; - this.originGroup = null + this.originGroup = null; }, async getList(): Promise { await this.init(); diff --git a/packages/ui/certd-client/src/store/settings/api.basic.ts b/packages/ui/certd-client/src/store/settings/api.basic.ts index ceaa16810..ebe7b6915 100644 --- a/packages/ui/certd-client/src/store/settings/api.basic.ts +++ b/packages/ui/certd-client/src/store/settings/api.basic.ts @@ -46,6 +46,10 @@ export type SysPublicSetting = { aiChatEnabled?: boolean; showRunStrategy?: boolean; + + captchaEnabled?: boolean; + captchaType?: number; + captchaAddonId?: number; }; export type SuiteSetting = { enabled?: boolean; diff --git a/packages/ui/certd-client/src/store/settings/index.ts b/packages/ui/certd-client/src/store/settings/index.ts index 99aade5f7..a70078831 100644 --- a/packages/ui/certd-client/src/store/settings/index.ts +++ b/packages/ui/certd-client/src/store/settings/index.ts @@ -12,6 +12,7 @@ import { cloneDeep, merge } from "lodash-es"; import { useI18n } from "/src/locales"; import dayjs from "dayjs"; export interface SettingState { + skipReset?: boolean; // 注销登录时,不清空此store的状态 sysPublic?: SysPublicSetting; installInfo?: { siteId: string; @@ -64,6 +65,7 @@ const defaultSiteInfo: SiteInfo = { export const useSettingStore = defineStore({ id: "app.setting", state: (): SettingState => ({ + skipReset: true, plusInfo: { isPlus: false, vipType: "free", diff --git a/packages/ui/certd-client/src/vben/stores/setup.ts b/packages/ui/certd-client/src/vben/stores/setup.ts index ad2560a6c..09ee70fca 100644 --- a/packages/ui/certd-client/src/vben/stores/setup.ts +++ b/packages/ui/certd-client/src/vben/stores/setup.ts @@ -38,6 +38,9 @@ export function resetAllStores() { } const allStores = (pinia as any)._s; for (const [_key, store] of allStores) { + if (store.skipReset) { + continue; + } store.$reset(); } } diff --git a/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx b/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx index a0154d7da..ffb8255cc 100644 --- a/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/access/access-selector/access/crud.tsx @@ -105,9 +105,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat form: { rules: [{ required: true, message: t("certd.pleaseEnterName") }], helper: t("certd.nameHelper"), + order: -2, }, column: { width: 200, + order: -2, }, }, from: { diff --git a/packages/ui/certd-client/src/views/certd/access/common.tsx b/packages/ui/certd-client/src/views/certd/access/common.tsx index 65d30afe6..026065e51 100644 --- a/packages/ui/certd-client/src/views/certd/access/common.tsx +++ b/packages/ui/certd-client/src/views/certd/access/common.tsx @@ -87,6 +87,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { order: -1, }, form: { + order: -1, component: { disabled: false, showSearch: true, diff --git a/packages/ui/certd-client/src/views/certd/access/crud.tsx b/packages/ui/certd-client/src/views/certd/access/crud.tsx index c0490c426..09b04133f 100644 --- a/packages/ui/certd-client/src/views/certd/access/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/access/crud.tsx @@ -79,6 +79,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, form: { rules: [{ required: true, message: "必填项" }], + order: -11, }, column: { width: 300, diff --git a/packages/ui/certd-client/src/views/certd/addon/addon-selector/index.vue b/packages/ui/certd-client/src/views/certd/addon/addon-selector/index.vue new file mode 100644 index 000000000..d64884aa5 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/addon-selector/index.vue @@ -0,0 +1,178 @@ + + + + diff --git a/packages/ui/certd-client/src/views/certd/addon/api.ts b/packages/ui/certd-client/src/views/certd/addon/api.ts new file mode 100644 index 000000000..998d65cc0 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/api.ts @@ -0,0 +1,142 @@ +import { request } from "/src/api/service"; +import { RequestHandleReq } from "/@/components/plugins/lib"; + +export function createAddonApi(opts: { from: any; addonType: string }) { + let apiPrefix = "/addon"; + if (opts.from === "sys") { + apiPrefix = "/sys/addon"; + } + return { + async GetList(query: any) { + return await request({ + url: apiPrefix + "/page", + method: "post", + data: { + ...query, + query: { + addonType: opts.addonType, + ...query.query, + }, + }, + }); + }, + + async AddObj(obj: any) { + return await request({ + url: apiPrefix + "/add", + method: "post", + data: { + ...obj, + addonType: opts.addonType, + }, + }); + }, + + async UpdateObj(obj: any) { + return await request({ + url: apiPrefix + "/update", + method: "post", + data: obj, + }); + }, + + async DelObj(id: number) { + return await request({ + url: apiPrefix + "/delete", + method: "post", + params: { id }, + }); + }, + + async GetObj(id: number) { + return await request({ + url: apiPrefix + "/info", + method: "post", + params: { id }, + }); + }, + + async GetOptions(id: number) { + return await request({ + url: apiPrefix + `/options?addonType=${opts.addonType}`, + method: "post", + }); + }, + + async SetDefault(id: number) { + return await request({ + url: apiPrefix + "/setDefault", + method: "post", + params: { id }, + }); + }, + + async GetDefaultId() { + return await request({ + url: apiPrefix + "/getDefaultId", + method: "post", + }); + }, + + async GetSimpleInfo(id: number) { + return await request({ + url: apiPrefix + `/simpleInfo?addonType=${opts.addonType}`, + method: "post", + params: { id }, + }); + }, + + async GetDefineTypes() { + return await request({ + url: apiPrefix + `/getTypeDict?addonType=${opts.addonType}`, + method: "post", + }); + }, + + async GetProviderDefine(type: string) { + return await request({ + url: apiPrefix + `/define?addonType=${opts.addonType}`, + method: "post", + params: { type }, + }); + }, + + async GetProviderDefineByType(type: string) { + return await request({ + url: apiPrefix + `/defineByType?addonType=${opts.addonType}`, + method: "post", + params: { type }, + }); + }, + + async Handle(req: RequestHandleReq, opts: any = {}) { + const url = `/handle/${req.type}?addonType=${opts.addonType}`; + const { typeName, action, data, input } = req; + const res = await request({ + url, + method: "post", + data: { + typeName, + action, + data, + input, + }, + ...opts, + }); + return res; + }, + }; +} + +export const AddonTypeDefines = { + captcha: { + name: "captcha", + title: "验证码", + showDefault: false, + showTest: false, + }, +}; + +export function getAddonTypeDefine(addonType: string) { + return AddonTypeDefines[addonType]; +} diff --git a/packages/ui/certd-client/src/views/certd/addon/common.tsx b/packages/ui/certd-client/src/views/certd/addon/common.tsx new file mode 100644 index 000000000..fa31e8c05 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/common.tsx @@ -0,0 +1,280 @@ +import { ColumnCompositionProps, compute, dict } from "@fast-crud/fast-crud"; +import { computed, provide, ref, toRef } from "vue"; +import { useReference } from "/@/use/use-refrence"; +import { forEach, get, merge, set } from "lodash-es"; +import { Modal } from "ant-design-vue"; +import { mitter } from "/@/utils/util.mitt"; +import { useI18n } from "/src/locales"; +import * as pipelineApi from "/@/views/certd/pipeline/api"; +import { getAddonTypeDefine } from "/@/views/certd/addon/api"; + +export function addonProvide(api: any) { + provide("addonApi", api); + provide("get:plugin:type", () => { + return "addon"; + }); +} + +export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any, addonType: string) { + const { t } = useI18n(); + // const addonTypeTypeDictRef = dict({ + // data: [{ value: "captcha", label: "验证码" }], + // }); + const addonTypeDictRef = dict({ + url: `/addon/getTypeDict?addonType=${addonType}`, + }); + const defaultPluginConfig = { + component: { + name: "a-input", + vModel: "value", + }, + }; + + function buildDefineFields(define: any, form: any, mode: string) { + const formWrapperRef = crudExpose.getFormWrapperRef(); + const columnsRef = toRef(formWrapperRef.formOptions, "columns"); + + for (const key in columnsRef.value) { + if (key.indexOf(".") >= 0) { + delete columnsRef.value[key]; + } + } + console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value); + forEach(define.input, (value: any, mapKey: any) => { + const key = "body." + mapKey; + const field = { + ...value, + key, + }; + const column = merge({ title: key }, defaultPluginConfig, field); + //eval + useReference(column); + + if (column.required) { + if (!column.rules) { + column.rules = []; + } + column.rules.push({ required: true, message: t("certd.requiredField") }); + } + + //设置默认值 + if (column.value != null && get(form, key) == null) { + set(form, key, column.value); + } + //字段配置赋值 + columnsRef.value[key] = column; + }); + } + + const currentDefine = ref(); + + return { + id: { + title: "ID", + key: "id", + type: "number", + column: { + width: 100, + }, + form: { + show: false, + }, + }, + // addonType: { + // title: "Addon类型", + // type: "dict-select", + // dict: addonTypeTypeDictRef, + // search: { + // show: false, + // }, + // column: { + // width: 200, + // component: { + // color: "auto", + // }, + // }, + // form: { + // onChange(ctx: { value: any }) { + // addonTypeDictRef.url = `/addon/getTypeDict?addonType=${ctx.value}`; + // }, + // }, + // editForm: { + // component: { + // disabled: false, + // }, + // }, + // }, + type: { + title: t("certd.addonType"), + type: "dict-select", + dict: addonTypeDictRef, + search: { + show: false, + }, + column: { + width: 200, + component: { + color: "auto", + }, + }, + editForm: { + component: { + disabled: false, + }, + }, + form: { + order: -22, + component: { + disabled: false, + showSearch: true, + filterOption: (input: string, option: any) => { + input = input?.toLowerCase(); + return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0; + }, + renderLabel(item: any) { + return ( + + {item.label} + {item.needPlus && } + + ); + }, + }, + rules: [{ required: true, message: t("certd.addonTypeSelect") }], + valueChange: { + immediate: true, + async handle({ value, mode, form, immediate }) { + if (value == null) { + return; + } + const lastTitle = currentDefine.value?.title; + const define = await api.GetProviderDefine(value); + currentDefine.value = define; + console.log("define", define); + + if (!immediate) { + form.body = {}; + if (define.needPlus) { + mitter.emit("openVipModal"); + } + } + + if (!form.name || form.name === lastTitle) { + form.name = define.title; + } + buildDefineFields(define, form, mode); + }, + }, + helper: computed(() => { + const define = currentDefine.value; + if (define == null) { + return ""; + } + return define.desc; + }), + }, + } as ColumnCompositionProps, + name: { + title: t("certd.addonName"), + search: { + show: true, + }, + type: ["text"], + form: { + order: -2, + rules: [{ required: true, message: t("certd.enterName") }], + helper: t("certd.addonNameHelper"), + }, + column: { + width: 200, + }, + }, + isDefault: { + title: t("certd.isDefault"), + type: "dict-switch", + dict: dict({ + data: [ + { label: t("certd.yes"), value: true, color: "success" }, + { label: t("certd.no"), value: false, color: "default" }, + ], + }), + form: { + show: computed(() => { + return getAddonTypeDefine(addonType)?.showDefault ?? false; + }), + value: false, + rules: [{ required: true, message: t("certd.selectIsDefault") }], + order: 999, + }, + column: { + align: "center", + width: 100, + show: computed(() => { + return getAddonTypeDefine(addonType)?.showDefault ?? false; + }), + component: { + name: "a-switch", + vModel: "checked", + disabled: compute(({ value }) => { + return value === true; + }), + on: { + // @ts-ignore + change({ row }) { + Modal.confirm({ + title: t("certd.prompt"), + content: t("certd.confirmSetDefaultNotification"), + onOk: async () => { + await api.SetDefault(row.id); + await crudExpose.doRefresh(); + }, + onCancel: async () => { + await crudExpose.doRefresh(); + }, + }); + }, + }, + }, + }, + }, + test: { + title: t("certd.test"), + form: { + show: compute(({ form }) => { + return !!form.type && currentDefine.value?.showTest === true; + }), + component: { + name: "api-test", + action: "TestRequest", + }, + order: 990, + col: { + span: 24, + }, + }, + column: { + show: false, + }, + }, + setting: { + column: { show: false }, + form: { + show: false, + valueBuilder({ value, form }) { + form.body = {}; + if (!value) { + return; + } + const setting = JSON.parse(value); + for (const key in setting) { + form.body[key] = setting[key]; + } + }, + valueResolve({ form }) { + const setting = form.body; + form.setting = JSON.stringify(setting); + }, + }, + } as ColumnCompositionProps, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/addon/crud.tsx b/packages/ui/certd-client/src/views/certd/addon/crud.tsx new file mode 100644 index 000000000..73f00f03c --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/crud.tsx @@ -0,0 +1,55 @@ +import { ref } from "vue"; +import { getCommonColumnDefine } from "./common"; +import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; + +export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const api = context.api; + const addonType = context.addonType; + const pageRequest = async (query: UserPageQuery): Promise => { + return await api.GetList(query); + }; + const editRequest = async (req: EditReq) => { + const { form, row } = req; + form.id = row.id; + const res = await api.UpdateObj(form); + return res; + }; + const delRequest = async (req: DelReq) => { + const { row } = req; + return await api.DelObj(row.id); + }; + + const addRequest = async (req: AddReq) => { + const { form } = req; + const res = await api.AddObj(form); + return res; + }; + + const typeRef = ref(); + const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api, addonType); + return { + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest, + }, + form: { + labelCol: { + //固定label宽度 + span: null, + style: { + width: "145px", + }, + }, + }, + rowHandle: { + width: 200, + }, + columns: { + ...commonColumnsDefine, + }, + }, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/addon/index.vue b/packages/ui/certd-client/src/views/certd/addon/index.vue new file mode 100644 index 000000000..3b4607db8 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/packages/ui/certd-client/src/views/certd/cname/record/index.vue b/packages/ui/certd-client/src/views/certd/cname/record/index.vue index 4ed382d3f..833ba5478 100644 --- a/packages/ui/certd-client/src/views/certd/cname/record/index.vue +++ b/packages/ui/certd-client/src/views/certd/cname/record/index.vue @@ -4,7 +4,7 @@
{{ t("certd.cnameRecord") }} - + {{ t("certd.cname_feature_guide") }} diff --git a/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx index 97e9754df..23fae9960 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx @@ -220,22 +220,47 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat sorter: true, conditionalRender: false, cellRender({ row }) { - const value = row.expiresTime; - if (!value) { + const { + applyTime, + effectiveTime, + expiresTime, + } = row || {}; + if (!expiresTime) { return "-"; } - const expireDate = dayjs(value).format("YYYY-MM-DD"); - const leftDays = dayjs(value).diff(dayjs(), "day"); + // 申请时间 ps:此处为证书在certd创建的时间而非实际证书申请时间 + const applyDate = dayjs(effectiveTime ?? applyTime ?? Date.now()).format("YYYY-MM-DD"); + // 失效时间 + const expireDate = dayjs(expiresTime).format("YYYY-MM-DD"); + // 有效天数 ps:此处证书最小设置为90d + const effectiveDays = Math.max(90, dayjs(expiresTime).diff(applyDate, "day")); + // 距离失效时间剩余天数 + const leftDays = dayjs(expiresTime).diff(dayjs(), "day"); const color = leftDays < 20 ? "red" : "#389e0d"; - const percent = (leftDays / 90) * 100; + const percent = (leftDays / effectiveDays) * 100; const textColor = leftDays < 20 ? "red" : leftDays > 60 ? "#389e0d" : ""; const format = () => { return {`${leftDays}${t("certd.days")}`}; }; + // console.log('cellRender', 'effectiveDays', effectiveDays, 'expiresTime', expiresTime, 'applyTime', applyTime, 'percent', percent, row) return ; }, }, }, + effectiveTime: { + title: t("certd.effectiveTime"), + search: { + show: false, + }, + type: "datetime", + form: { + show: false, + }, + column: { + sorter: true, + show: false, + }, + }, expiresTime: { title: t("certd.expireTime"), search: { diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx index c53a32149..e11890836 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx @@ -345,25 +345,64 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat align: "center", }, }, + certEffectiveTime: { + title: t("certd.monitor.certEffectiveTime"), + search: { + show: false, + }, + type: "datetime", + form: { + show: false, + }, + column: { + sorter: true, + width: 155, + }, + }, certExpiresTime: { title: t("certd.monitor.certExpiresTime"), search: { show: false, }, + type: "datetime", + form: { + show: false, + }, + column: { + sorter: true, + width: 155, + }, + }, + remainingValidity: { + title: t("certd.monitor.remainingValidity"), + search: { + show: false, + }, type: "date", form: { show: false, }, column: { - sorter: true, - cellRender({ value }) { - if (!value) { + conditionalRender: false, + cellRender({ row }) { + const { + certEffectiveTime: effectiveTime, + certExpiresTime: expiresTime, + } = row || {}; + if (!expiresTime) { return "-"; } - const expireDate = dayjs(value).format("YYYY-MM-DD"); - const leftDays = dayjs(value).diff(dayjs(), "day"); + // 申请时间 ps:此处为证书在certd创建的时间而非实际证书申请时间 + const applyDate = dayjs(effectiveTime ?? Date.now()).format("YYYY-MM-DD"); + // 失效时间 + const expireDate = dayjs(expiresTime).format("YYYY-MM-DD"); + // 有效天数 ps:此处证书最小设置为90d + const effectiveDays = Math.max(90, dayjs(expiresTime).diff(applyDate, "day")); + // 距离失效时间剩余天数 + const leftDays = dayjs(expiresTime).diff(dayjs(), "day"); const color = leftDays < 20 ? "red" : "#389e0d"; - const percent = (leftDays / 90) * 100; + const percent = (leftDays / effectiveDays) * 100; + // console.log('cellRender', 'effectiveDays', effectiveDays, 'expiresTime', expiresTime, 'applyTime', applyTime, 'percent', percent, row) return `${leftDays}${t("certd.monitor.days")}`} />; }, }, diff --git a/packages/ui/certd-client/src/views/certd/notification/common.tsx b/packages/ui/certd-client/src/views/certd/notification/common.tsx index 01d007b41..da648ffe0 100644 --- a/packages/ui/certd-client/src/views/certd/notification/common.tsx +++ b/packages/ui/certd-client/src/views/certd/notification/common.tsx @@ -96,6 +96,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { }, }, form: { + order: -3, component: { disabled: false, showSearch: true, @@ -153,6 +154,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { }, type: ["text"], form: { + order: -2, rules: [{ required: true, message: t("certd.enterName") }], helper: t("certd.helperNotificationName"), }, diff --git a/packages/ui/certd-client/src/views/certd/open/openkey/index.vue b/packages/ui/certd-client/src/views/certd/open/openkey/index.vue index c21599a8a..89e8815f5 100644 --- a/packages/ui/certd-client/src/views/certd/open/openkey/index.vue +++ b/packages/ui/certd-client/src/views/certd/open/openkey/index.vue @@ -3,7 +3,7 @@ diff --git a/packages/ui/certd-client/src/views/certd/pipeline/certd-form/use.tsx b/packages/ui/certd-client/src/views/certd/pipeline/certd-form/use.tsx index dd6ff4d57..5b10b8ca5 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/certd-form/use.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/certd-form/use.tsx @@ -22,7 +22,7 @@ export function fillPipelineByDefaultForm(pipeline: any, form: any) { if (form.notification != null) { notifications.push({ type: "custom", - when: ["error", "turnToSuccess", "success"], + when: form.notificationWhen || ["error", "turnToSuccess"], notificationId: form.notification, title: form.notificationTarget?.name || "自定义通知", }); @@ -223,6 +223,25 @@ export function useCertPipelineCreator() { helper: t("certd.pipelineForm.notificationHelper"), }, }, + notificationWhen: { + title: t("certd.pipelineForm.notificationWhen"), + type: "text", + form: { + value: ["error", "turnToSuccess"], + component: { + name: "a-select", + vModel: "value", + mode: "multiple", + options: [ + { value: "start", label: t("certd.start_time") }, + { value: "success", label: t("certd.success_time") }, + { value: "turnToSuccess", label: t("certd.fail_to_success_time") }, + { value: "error", label: t("certd.fail_time") }, + ], + }, + order: 102, + }, + }, groupId: { title: t("certd.pipelineForm.groupIdTitle"), type: "dict-select", @@ -268,7 +287,7 @@ export function useCertPipelineCreator() { async function doSubmit({ form }: any) { // const certDetail = readCertDetail(form.cert.crt); // 添加certd pipeline - const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]); + const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "notificationWhen", "certApplyPlugin", "groupId"]); let pipeline: any = { title: form.domains[0] + "证书自动化", runnableType: "pipeline", diff --git a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx index 9e11102da..8ce42f4cc 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx @@ -366,23 +366,49 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys }, column: { cellRender({ row }) { - const value = row.lastVars?.certExpiresTime; - if (!value) { + const { + certEffectiveTime: effectiveTime, + certExpiresTime: expiresTime, + } = row?.lastVars || {}; + if (!expiresTime) { return "-"; } - const expireDate = dayjs(value).format("YYYY-MM-DD"); - const leftDays = dayjs(value).diff(dayjs(), "day"); + // 申请时间 ps:此处为证书在certd创建的时间而非实际证书申请时间 + const applyDate = dayjs(effectiveTime ?? Date.now()).format("YYYY-MM-DD"); + // 失效时间 + const expireDate = dayjs(expiresTime).format("YYYY-MM-DD"); + // 有效天数 ps:此处证书最小设置为90d + const effectiveDays = Math.max(90, dayjs(expiresTime).diff(applyDate, "day")); + // 距离失效时间剩余天数 + const leftDays = dayjs(expiresTime).diff(dayjs(), "day"); const color = leftDays < 20 ? "red" : "#389e0d"; - const percent = (leftDays / 90) * 100; + const percent = (leftDays / effectiveDays) * 100; const textColor = leftDays < 20 ? "red" : leftDays > 60 ? "#389e0d" : ""; const format = () => { return {`${leftDays}${t("certd.days")}`}; }; + // console.log('cellRender', 'effectiveDays', effectiveDays, 'expiresTime', expiresTime, 'applyTime', applyTime, 'percent', percent, row) return ; }, width: 150, }, }, + "lastVars.certEffectiveTime": { + title: t("certd.fields.effectiveTime"), + search: { + show: false, + }, + type: "datetime", + form: { + show: false, + }, + column: { + sorter: false, + show: false, + width: 150, + align: "center", + }, + }, "lastVars.certExpiresTime": { title: t("certd.fields.expiryTime"), search: { diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue index 97a1c8583..5c1bb9d99 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue @@ -313,6 +313,8 @@ function useStepForm() { }; const stepDelete = () => { + //检查输出依赖 + Modal.confirm({ title: "确认", content: `确定要删除此步骤吗?`, diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue index 0a0719a9f..d78bce6ee 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue @@ -279,7 +279,7 @@ - diff --git a/packages/ui/certd-client/src/views/framework/login/index.vue b/packages/ui/certd-client/src/views/framework/login/index.vue index 604aa0d30..02add9e25 100644 --- a/packages/ui/certd-client/src/views/framework/login/index.vue +++ b/packages/ui/certd-client/src/views/framework/login/index.vue @@ -20,6 +20,10 @@ + + + + @@ -32,12 +36,12 @@ - - + + - + @@ -87,14 +91,13 @@ import { defineComponent, nextTick, reactive, ref, toRaw } from "vue"; import { useUserStore } from "/src/store/user"; import { useSettingStore } from "/@/store/settings"; import { utils } from "@fast-crud/fast-crud"; -import ImageCode from "/@/views/framework/login/image-code.vue"; import SmsCode from "/@/views/framework/login/sms-code.vue"; import { useI18n } from "/@/locales"; import { LanguageToggle } from "/@/vben/layouts"; - +import CaptchaInput from "/@/components/captcha/captcha-input.vue"; export default defineComponent({ name: "LoginPage", - components: { LanguageToggle, SmsCode, ImageCode }, + components: { LanguageToggle, SmsCode, CaptchaInput }, setup() { const { t } = useI18n(); const verifyCodeInputRef = ref(); @@ -108,9 +111,9 @@ export default defineComponent({ mobile: "", password: "", loginType: "password", //password - imgCode: "", smsCode: "", - randomStr: "", + captcha: null, + smsCaptcha: null, }); const rules = { @@ -138,6 +141,12 @@ export default defineComponent({ message: "请输入短信验证码", }, ], + captcha: [ + { + required: true, + message: "请进行验证码验证", + }, + ], }; const layout = { labelCol: { @@ -160,6 +169,10 @@ export default defineComponent({ const handleFinish = async (values: any) => { loading.value = true; try { + // formState.captcha = await doCaptchaValidate(); + // if (!formState.captcha) { + // return; + // } const loginType = formState.loginType; await userStore.login(loginType, toRaw(formState)); } catch (e: any) { @@ -194,6 +207,21 @@ export default defineComponent({ return sysPublicSettings.registerEnabled && (sysPublicSettings.usernameRegisterEnabled || sysPublicSettings.emailRegisterEnabled); } + const captchaInputRef = ref(); + const captchaInputForSmsCode = ref(); + async function doCaptchaValidate() { + if (!sysPublicSettings.captchaEnabled) { + return {}; + } + const res = await captchaInputRef.value.getValidatedForm(); + if (!res) { + return false; + } + return { + ...res, + }; + } + return { t, loading, @@ -211,6 +239,8 @@ export default defineComponent({ handleTwoFactorSubmit, verifyCodeInputRef, settingStore, + captchaInputRef, + captchaInputForSmsCode, }; }, }); diff --git a/packages/ui/certd-client/src/views/framework/login/sms-code.vue b/packages/ui/certd-client/src/views/framework/login/sms-code.vue index efbf6ab36..80d85e4d1 100644 --- a/packages/ui/certd-client/src/views/framework/login/sms-code.vue +++ b/packages/ui/certd-client/src/views/framework/login/sms-code.vue @@ -5,7 +5,7 @@ -
+
{{ smsTime <= 0 ? "发送" : smsTime + " s" }} @@ -21,8 +21,7 @@ const props = defineProps<{ value?: string; mobile?: string; phoneCode?: string; - imgCode?: string; - randomStr?: string; + captcha?: any; verificationType?: string; }>(); const emit = defineEmits(["update:value", "change"]); @@ -48,8 +47,8 @@ async function sendSmsCode() { notification.error({ message: "请输入手机号" }); return; } - if (!props.imgCode) { - notification.error({ message: "请输入图片验证码" }); + if (!props.captcha) { + notification.error({ message: "请输入验证码" }); return; } loading.value = true; @@ -57,8 +56,7 @@ async function sendSmsCode() { await api.sendSmsCode({ phoneCode: props.phoneCode, mobile: props.mobile, - imgCode: props.imgCode, - randomStr: props.randomStr, + captcha: props.captcha, verificationType: props.verificationType, }); } finally { diff --git a/packages/ui/certd-client/src/views/framework/register/email-code.vue b/packages/ui/certd-client/src/views/framework/register/email-code.vue index 6ee92e1f8..5403f3270 100644 --- a/packages/ui/certd-client/src/views/framework/register/email-code.vue +++ b/packages/ui/certd-client/src/views/framework/register/email-code.vue @@ -5,7 +5,7 @@ -
+
{{ smsTime <= 0 ? "发送" : smsTime + " s" }} @@ -20,8 +20,7 @@ import * as api from "/@/store/settings/api.basic"; const props = defineProps<{ value?: string; email?: string; - imgCode?: string; - randomStr?: string; + captcha?: any; verificationType?: string; }>(); const emit = defineEmits(["update:value", "change"]); @@ -44,16 +43,15 @@ async function sendSmsCode() { notification.error({ message: "请输入邮箱" }); return; } - if (!props.imgCode) { - notification.error({ message: "请输入图片验证码" }); + if (!props.captcha) { + notification.error({ message: "请输入验证码" }); return; } loading.value = true; try { await api.sendEmailCode({ email: props.email, - imgCode: props.imgCode, - randomStr: props.randomStr, + captcha: props.captcha, verificationType: props.verificationType, }); } finally { diff --git a/packages/ui/certd-client/src/views/framework/register/index.vue b/packages/ui/certd-client/src/views/framework/register/index.vue index 38602cf67..23d85ee2e 100644 --- a/packages/ui/certd-client/src/views/framework/register/index.vue +++ b/packages/ui/certd-client/src/views/framework/register/index.vue @@ -25,8 +25,8 @@ - - + + @@ -61,12 +61,12 @@ - - + + - + @@ -86,13 +86,13 @@ import { defineComponent, reactive, ref, toRaw } from "vue"; import { useUserStore } from "/src/store/user"; import { utils } from "@fast-crud/fast-crud"; -import ImageCode from "/@/views/framework/login/image-code.vue"; import EmailCode from "./email-code.vue"; import { useSettingStore } from "/@/store/settings"; import { notification } from "ant-design-vue"; +import CaptchaInput from "/@/components/captcha/captcha-input.vue"; export default defineComponent({ name: "RegisterPage", - components: { EmailCode, ImageCode }, + components: { CaptchaInput, EmailCode }, setup() { const settingsStore = useSettingStore(); const registerType = ref("email"); @@ -114,7 +114,7 @@ export default defineComponent({ username: "", password: "", confirmPassword: "", - randomStr: "", + captcha: null, }); const rules = { @@ -159,17 +159,6 @@ export default defineComponent({ }, ], - imgCode: [ - { - required: true, - message: "请输入图片验证码", - }, - { - min: 4, - max: 4, - message: "请输入4位图片验证码", - }, - ], smsCode: [ { required: true, @@ -198,9 +187,8 @@ export default defineComponent({ type: registerType.value, password: formState.password, username: formState.username, - imgCode: formState.imgCode, - randomStr: formState.randomStr, email: formState.email, + captcha: formState.captcha, validateCode: formState.validateCode, }) as any ); @@ -214,16 +202,7 @@ export default defineComponent({ formRef.value.resetFields(); }; - const imageCodeUrl = ref(); - function resetImageCode() { - let url = "/basic/code"; - imageCodeUrl.value = url + "?t=" + new Date().getTime(); - } - resetImageCode(); - return { - resetImageCode, - imageCodeUrl, formState, formRef, rules, diff --git a/packages/ui/certd-client/src/views/sys/plugin/edit.vue b/packages/ui/certd-client/src/views/sys/plugin/edit.vue index 76da957b0..958447aab 100644 --- a/packages/ui/certd-client/src/views/sys/plugin/edit.vue +++ b/packages/ui/certd-client/src/views/sys/plugin/edit.vue @@ -25,7 +25,7 @@
@@ -33,7 +33,7 @@
- +
@@ -83,6 +83,7 @@ function initFormOptions() { } initFormOptions(); +const idRef = ref(route.query.id); async function getPlugin() { const id = route.query.id; const pluginObj = await api.GetObj(id); diff --git a/packages/ui/certd-client/src/views/sys/plugin/use-import.ts b/packages/ui/certd-client/src/views/sys/plugin/use-import.ts index b398752a3..51f230022 100644 --- a/packages/ui/certd-client/src/views/sys/plugin/use-import.ts +++ b/packages/ui/certd-client/src/views/sys/plugin/use-import.ts @@ -1,12 +1,14 @@ import * as api from "/@/views/sys/plugin/api"; -import { useFormWrapper } from "@fast-crud/fast-crud"; +import { dict, useFormWrapper } from "@fast-crud/fast-crud"; import { useI18n } from "/@/locales"; -import { Modal, notification } from "ant-design-vue"; +import { notification } from "ant-design-vue"; + export function usePluginImport() { const { openCrudFormDialog } = useFormWrapper(); const { t } = useI18n(); - async function openImportDialog({ crudExpose }) { + async function openImportDialog(opts: any) { + const { crudExpose } = opts; function createCrudOptions() { return { crudOptions: { diff --git a/packages/ui/certd-client/src/views/sys/settings/index.vue b/packages/ui/certd-client/src/views/sys/settings/index.vue index c16a76c54..2c5af5de9 100644 --- a/packages/ui/certd-client/src/views/sys/settings/index.vue +++ b/packages/ui/certd-client/src/views/sys/settings/index.vue @@ -5,17 +5,17 @@
- + - + - + - - + +
@@ -30,9 +30,11 @@ import SettingSafe from "/@/views/sys/settings/tabs/safe.vue"; import { useRoute, useRouter } from "vue-router"; import { ref } from "vue"; import { useSettingStore } from "/@/store/settings"; +import { useI18n } from "/@/locales"; defineOptions({ name: "SysSettings", }); +const { t } = useI18n(); const settingsStore = useSettingStore(); const activeKey = ref("base"); const route = useRoute(); diff --git a/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue b/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue index a3024ae4c..a7f53c56e 100644 --- a/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue +++ b/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue @@ -47,6 +47,18 @@
+ + +
+
+ + + + + + {{ t("certd.saveButton") }} @@ -63,7 +75,7 @@ import { useSettingStore } from "/@/store/settings"; import { notification } from "ant-design-vue"; import { util } from "/@/utils"; import { useI18n } from "/src/locales"; - +import AddonSelector from "../../../certd/addon/addon-selector/index.vue"; const { t } = useI18n(); defineOptions({ @@ -115,6 +127,10 @@ async function stopOtherUserTimer() { }); } +function onAddonChanged(target: any) { + formState.public.captchaType = target.type; +} + const testProxyLoading = ref(false); async function testProxy() { testProxyLoading.value = true; diff --git a/packages/ui/certd-client/vite.config.ts b/packages/ui/certd-client/vite.config.ts index 33322f01e..fabd8e9f7 100644 --- a/packages/ui/certd-client/vite.config.ts +++ b/packages/ui/certd-client/vite.config.ts @@ -68,6 +68,7 @@ export default ({ command, mode }) => { rollupOptions: { plugins: [visualizer()], }, + minify: "esbuild", }, css: { preprocessorOptions: { diff --git a/packages/ui/certd-server/.env.dev-mysql-comm.yaml b/packages/ui/certd-server/.env.dev-mysql-comm.yaml new file mode 100644 index 000000000..739a45c31 --- /dev/null +++ b/packages/ui/certd-server/.env.dev-mysql-comm.yaml @@ -0,0 +1,15 @@ +flyway: + scriptDir: './db/migration-mysql' + +typeorm: + dataSource: + default: + type: mysql # mariadb + host: localhost + port: 3309 + logging: true + username: root + password: root + database: certd_comm + + diff --git a/packages/ui/certd-server/CHANGELOG.md b/packages/ui/certd-server/CHANGELOG.md index 61b7ed361..bced970ec 100644 --- a/packages/ui/certd-server/CHANGELOG.md +++ b/packages/ui/certd-server/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15) + +**Note:** Version bump only for package @certd/ui-server + +## [1.36.20](https://github.com/certd/certd/compare/v1.36.19...v1.36.20) (2025-09-13) + +### Bug Fixes + +* 修复证书监控某些情况下报 options.lookup不能为null的bug ([d2ecfe5](https://github.com/certd/certd/commit/d2ecfe5491b2639eb30b5cae293af6062d58bb9f)) +* 修复证书手动托管时新上传的证书无效的bug ([506385e](https://github.com/certd/certd/commit/506385e5a2600887fe30854e0713583caaa2e689)) + +### Performance Improvements + +* 登录支持极验验证码 ([370db62](https://github.com/certd/certd/commit/370db62bf0aece241859244927beabba32d6a257)) +* 登录注册、找回密码都支持极验验证码和图片验证码 ([7bdde68](https://github.com/certd/certd/commit/7bdde68ecea29fe2c570fd3cb082139db6c93d93)) +* 证书到期剩余天数进度条根据实际证书有效期计算 ([#528](https://github.com/certd/certd/issues/528)) nicheng-he ([2d4586b](https://github.com/certd/certd/commit/2d4586b1c42c39f97d2a95b9453cca4bc8bfbe61)) +* add preferred chain option ([#519](https://github.com/certd/certd/issues/519)) @ZeroClover ([902359f](https://github.com/certd/certd/commit/902359f24ed12eee4f9b65178f1d6a60378351d2)) + +## [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)) +* 修复mysql下购买套餐加量包无效的bug ([c26ad4c](https://github.com/certd/certd/commit/c26ad4c8075f0606d45b8da13915737968d6191a)) + +### Performance Improvements + +* 增加健康检查探针 /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)) + ## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28) ### Bug Fixes diff --git a/packages/ui/certd-server/db/migration-mysql/v10028__fix_suite.sql b/packages/ui/certd-server/db/migration-mysql/v10028__fix_suite.sql new file mode 100644 index 000000000..27a437657 --- /dev/null +++ b/packages/ui/certd-server/db/migration-mysql/v10028__fix_suite.sql @@ -0,0 +1,3 @@ +update cd_user_suite set is_empty = false where is_empty is null; + +ALTER TABLE cd_user_suite MODIFY COLUMN `is_empty` boolean default false ; diff --git a/packages/ui/certd-server/db/migration-mysql/v10029__cert_effective_time.sql b/packages/ui/certd-server/db/migration-mysql/v10029__cert_effective_time.sql new file mode 100644 index 000000000..1f4761ee5 --- /dev/null +++ b/packages/ui/certd-server/db/migration-mysql/v10029__cert_effective_time.sql @@ -0,0 +1,2 @@ +ALTER TABLE cd_cert_info ADD COLUMN effective_time bigint; +ALTER TABLE cd_site_info ADD COLUMN cert_effective_time bigint; diff --git a/packages/ui/certd-server/db/migration-mysql/v10030__addon.sql b/packages/ui/certd-server/db/migration-mysql/v10030__addon.sql new file mode 100644 index 000000000..2dabb526c --- /dev/null +++ b/packages/ui/certd-server/db/migration-mysql/v10030__addon.sql @@ -0,0 +1,13 @@ + +CREATE TABLE `cd_addon` ( + `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL, + `user_id` bigint NOT NULL, + `name` varchar(100) NOT NULL, + `type` varchar(100) NOT NULL, + `addon_type` varchar(100) NOT NULL, + `is_default` boolean NOT NULL DEFAULT false, + `is_system` boolean NOT NULL DEFAULT false, + `setting` longtext, + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/packages/ui/certd-server/db/migration-pg/v10028__fix_suite.sql b/packages/ui/certd-server/db/migration-pg/v10028__fix_suite.sql new file mode 100644 index 000000000..6eaf3a99c --- /dev/null +++ b/packages/ui/certd-server/db/migration-pg/v10028__fix_suite.sql @@ -0,0 +1,2 @@ +update cd_user_suite set is_empty = false where is_empty is null; +ALTER TABLE "cd_user_suite" ALTER COLUMN "is_empty" SET DEFAULT false; diff --git a/packages/ui/certd-server/db/migration-pg/v10029__cert_effective_time.sql b/packages/ui/certd-server/db/migration-pg/v10029__cert_effective_time.sql new file mode 100644 index 000000000..1f4761ee5 --- /dev/null +++ b/packages/ui/certd-server/db/migration-pg/v10029__cert_effective_time.sql @@ -0,0 +1,2 @@ +ALTER TABLE cd_cert_info ADD COLUMN effective_time bigint; +ALTER TABLE cd_site_info ADD COLUMN cert_effective_time bigint; diff --git a/packages/ui/certd-server/db/migration-pg/v10030__addon.sql b/packages/ui/certd-server/db/migration-pg/v10030__addon.sql new file mode 100644 index 000000000..c53785b2a --- /dev/null +++ b/packages/ui/certd-server/db/migration-pg/v10030__addon.sql @@ -0,0 +1,13 @@ + +CREATE TABLE "cd_addon" ( + "id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + "user_id" bigint NOT NULL, + "name" varchar(100) NOT NULL, + "type" varchar(100) NOT NULL, + "addon_type" varchar(100) NOT NULL, + "is_default" boolean NOT NULL DEFAULT (false), + "is_system" boolean NOT NULL DEFAULT (false), + "setting" text, + "create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); diff --git a/packages/ui/certd-server/db/migration/v10028__fix_suite.sql b/packages/ui/certd-server/db/migration/v10028__fix_suite.sql new file mode 100644 index 000000000..e69de29bb diff --git a/packages/ui/certd-server/db/migration/v10029__cert_effective_time.sql b/packages/ui/certd-server/db/migration/v10029__cert_effective_time.sql new file mode 100644 index 000000000..b38feb3c2 --- /dev/null +++ b/packages/ui/certd-server/db/migration/v10029__cert_effective_time.sql @@ -0,0 +1,2 @@ +ALTER TABLE cd_cert_info ADD COLUMN effective_time INTEGER; +ALTER TABLE cd_site_info ADD COLUMN cert_effective_time INTEGER; diff --git a/packages/ui/certd-server/db/migration/v10030__addon.sql b/packages/ui/certd-server/db/migration/v10030__addon.sql new file mode 100644 index 000000000..be7c3a951 --- /dev/null +++ b/packages/ui/certd-server/db/migration/v10030__addon.sql @@ -0,0 +1,13 @@ + +CREATE TABLE "cd_addon" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "user_id" integer NOT NULL, + "name" varchar(100) NOT NULL, + "type" varchar(100) NOT NULL, + "addon_type" varchar(100) NOT NULL, + "is_default" boolean NOT NULL DEFAULT (false), + "is_system" boolean NOT NULL DEFAULT (false), + "setting" text, + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); diff --git a/packages/ui/certd-server/db/transform.js b/packages/ui/certd-server/db/transform.js index 9ae039605..ff414845e 100644 --- a/packages/ui/certd-server/db/transform.js +++ b/packages/ui/certd-server/db/transform.js @@ -39,6 +39,7 @@ function transformPG() { pgSql = pgSql.replaceAll(/boolean DEFAULT \(0\)/g, 'boolean DEFAULT (false)'); pgSql = pgSql.replaceAll(/boolean.*NOT NULL DEFAULT \(0\)/g, 'boolean NOT NULL DEFAULT (false)'); pgSql = pgSql.replaceAll(/integer/g, 'bigint'); + pgSql = pgSql.replaceAll(/INTEGER/g, 'bigint'); pgSql = pgSql.replaceAll(/last_insert_rowid\(\)/g, 'LASTVAL()'); fs.writeFileSync(`./migration-pg/${notFile}`, pgSql); } @@ -66,6 +67,7 @@ function transformMysql() { //DEFAULT (xxx) 替换成 DEFAULT xxx pgSql = pgSql.replaceAll(/DEFAULT \(([^)]*)\)/g, 'DEFAULT $1'); pgSql = pgSql.replaceAll(/integer/g, 'bigint'); + pgSql = pgSql.replaceAll(/INTEGER/g, 'bigint'); pgSql = pgSql.replaceAll(/last_insert_rowid\(\)/g, 'LAST_INSERT_ID()'); //text 改成longtext pgSql = pgSql.replaceAll(/text/g, 'longtext'); diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index 2f9e3cf0f..ea25e56d6 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -1,6 +1,6 @@ { "name": "@certd/ui-server", - "version": "1.36.18", + "version": "1.36.21", "description": "fast-server base midway", "private": true, "type": "module", @@ -11,6 +11,7 @@ "dev-commpro": "cross-env NODE_ENV=dev-commpro mwtsc --watch --run @midwayjs/mock/app", "dev-pg": "cross-env NODE_ENV=dev-pg mwtsc --watch --run @midwayjs/mock/app", "dev-mysql": "cross-env NODE_ENV=dev-mysql mwtsc --watch --run @midwayjs/mock/app", + "dev-mysql-comm": "cross-env NODE_ENV=dev-mysql-comm mwtsc --watch --run @midwayjs/mock/app", "dev-localplus": "cross-env NODE_ENV=dev-localplus mwtsc --watch --run @midwayjs/mock/app", "dev-pgpl": "cross-env NODE_ENV=dev-pgpl mwtsc --watch --run @midwayjs/mock/app", "dev-new": "cross-env NODE_ENV=dev-new mwtsc --watch --run @midwayjs/mock/app", @@ -42,20 +43,20 @@ "@aws-sdk/client-cloudfront": "^3.699.0", "@aws-sdk/client-iam": "^3.699.0", "@aws-sdk/client-s3": "^3.705.0", - "@certd/acme-client": "^1.36.18", - "@certd/basic": "^1.36.18", - "@certd/commercial-core": "^1.36.18", + "@certd/acme-client": "^1.36.21", + "@certd/basic": "^1.36.21", + "@certd/commercial-core": "^1.36.21", "@certd/cv4pve-api-javascript": "^8.4.2", - "@certd/jdcloud": "^1.36.18", - "@certd/lib-huawei": "^1.36.18", - "@certd/lib-k8s": "^1.36.18", - "@certd/lib-server": "^1.36.18", - "@certd/midway-flyway-js": "^1.36.18", - "@certd/pipeline": "^1.36.18", - "@certd/plugin-cert": "^1.36.18", - "@certd/plugin-lib": "^1.36.18", - "@certd/plugin-plus": "^1.36.18", - "@certd/plus-core": "^1.36.18", + "@certd/jdcloud": "^1.36.21", + "@certd/lib-huawei": "^1.36.21", + "@certd/lib-k8s": "^1.36.21", + "@certd/lib-server": "^1.36.21", + "@certd/midway-flyway-js": "^1.36.21", + "@certd/pipeline": "^1.36.21", + "@certd/plugin-cert": "^1.36.21", + "@certd/plugin-lib": "^1.36.21", + "@certd/plugin-plus": "^1.36.21", + "@certd/plus-core": "^1.36.21", "@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120", "@huaweicloud/huaweicloud-sdk-core": "^3.1.120", "@koa/cors": "^5.0.0", @@ -117,7 +118,7 @@ "socks-proxy-agent": "^8.0.4", "strip-ansi": "^7.1.0", "svg-captcha": "^1.4.0", - "tencentcloud-sdk-nodejs": "^4.0.983", + "tencentcloud-sdk-nodejs": "^4.1.112", "typeorm": "^0.3.20", "uuid": "^10.0.0" }, diff --git a/packages/ui/certd-server/src/config/config.default.ts b/packages/ui/certd-server/src/config/config.default.ts index dc616cf69..4285eda97 100644 --- a/packages/ui/certd-server/src/config/config.default.ts +++ b/packages/ui/certd-server/src/config/config.default.ts @@ -27,6 +27,7 @@ const development = { }, keys: 'certd', koa: { + hostname:"::", port: 7001, }, https: { diff --git a/packages/ui/certd-server/src/controller/basic/code-controller.ts b/packages/ui/certd-server/src/controller/basic/code-controller.ts index bb7c0f6e9..dee5a0954 100644 --- a/packages/ui/certd-server/src/controller/basic/code-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/code-controller.ts @@ -1,8 +1,9 @@ -import { Rule, RuleType } from '@midwayjs/validate'; -import { ALL, Body, Controller, Get, Inject, Post, Provide, Query } from '@midwayjs/core'; -import { BaseController, Constants } from '@certd/lib-server'; -import { CodeService } from '../../modules/basic/service/code-service.js'; -import { EmailService } from '../../modules/basic/service/email-service.js'; +import { Rule, RuleType } from "@midwayjs/validate"; +import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core"; +import { BaseController, Constants, SysSettingsService } from "@certd/lib-server"; +import { CodeService } from "../../modules/basic/service/code-service.js"; +import { EmailService } from "../../modules/basic/service/email-service.js"; +import { CaptchaService } from "../../modules/basic/service/captcha-service.js"; export class SmsCodeReq { @Rule(RuleType.string().required()) @@ -11,11 +12,8 @@ export class SmsCodeReq { @Rule(RuleType.string().required()) mobile: string; - @Rule(RuleType.string().required().max(10)) - randomStr: string; - - @Rule(RuleType.string().required().max(4)) - imgCode: string; + @Rule(RuleType.required()) + captcha: any; @Rule(RuleType.string()) verificationType: string; @@ -25,11 +23,8 @@ export class EmailCodeReq { @Rule(RuleType.string().required()) email: string; - @Rule(RuleType.string().required().max(10)) - randomStr: string; - - @Rule(RuleType.string().required().max(4)) - imgCode: string; + @Rule(RuleType.required()) + captcha: any; @Rule(RuleType.string()) verificationType: string; @@ -48,6 +43,17 @@ export class BasicController extends BaseController { @Inject() emailService: EmailService; + @Inject() + sysSettingsService: SysSettingsService; + + @Inject() + captchaService: CaptchaService; + + @Post('/captcha/get', { summary: Constants.per.guest }) + async getCaptcha(@Query("captchaAddonId") captchaAddonId:number) { + const form = await this.captchaService.getCaptcha(captchaAddonId) + return this.ok(form); + } @Post('/sendSmsCode', { summary: Constants.per.guest }) public async sendSmsCode( @@ -64,8 +70,8 @@ export class BasicController extends BaseController { // opts.verificationCodeLength = 6; //部分厂商这里会设置参数长度这里就不改了 } - await this.codeService.checkCaptcha(body.randomStr, body.imgCode); - await this.codeService.sendSmsCode(body.phoneCode, body.mobile, body.randomStr, opts); + await this.codeService.checkCaptcha(body.captcha); + await this.codeService.sendSmsCode(body.phoneCode, body.mobile, opts); return this.ok(null); } @@ -88,16 +94,10 @@ export class BasicController extends BaseController { opts.verificationCodeLength = 6; } - await this.codeService.checkCaptcha(body.randomStr, body.imgCode); - await this.codeService.sendEmailCode(body.email, body.randomStr, opts); + await this.codeService.checkCaptcha(body.captcha); + await this.codeService.sendEmailCode(body.email, opts); // 设置缓存内容 return this.ok(null); } - @Get('/captcha', { summary: Constants.per.guest }) - public async getCaptcha(@Query('randomStr') randomStr: any) { - const captcha = await this.codeService.generateCaptcha(randomStr); - this.ctx.res.setHeader('Content-Type', 'image/svg+xml'); - return captcha.data; - } } diff --git a/packages/ui/certd-server/src/controller/basic/unhidden-controller.ts b/packages/ui/certd-server/src/controller/basic/unhidden-controller.ts index 1e450e1d7..f46e6989d 100644 --- a/packages/ui/certd-server/src/controller/basic/unhidden-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/unhidden-controller.ts @@ -23,7 +23,7 @@ const unhiddenHtml = ` @Provide() @Controller('/api/unhidden') -export class HnhiddenController { +export class UnhiddenController { @Inject() ctx: IMidwayKoaContext; @Inject() diff --git a/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts b/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts new file mode 100644 index 000000000..bd5864237 --- /dev/null +++ b/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts @@ -0,0 +1,83 @@ +import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core"; +import { AddonRequestHandleReq, AddonService, Constants } from "@certd/lib-server"; +import { AddonController } from "../../user/addon/addon-controller.js"; + +@Provide() +@Controller('/api/sys/addon') +export class SysAddonController extends AddonController { + @Inject() + service2: AddonService; + + getService(): AddonService { + return this.service2; + } + + getUserId() { + // checkComm(); + return 0; + } + + @Post('/page', { summary: 'sys:settings:view' }) + async page(@Body(ALL) body: any) { + return await super.page(body); + } + + @Post('/list', { summary: 'sys:settings:view' }) + async list(@Body(ALL) body: any) { + return await super.list(body); + } + + @Post('/add', { summary: 'sys:settings:edit' }) + async add(@Body(ALL) bean: any) { + return await super.add(bean); + } + + @Post('/update', { summary: 'sys:settings:edit' }) + async update(@Body(ALL) bean: any) { + return await super.update(bean); + } + @Post('/info', { summary: 'sys:settings:view' }) + async info(@Query('id') id: number) { + return await super.info(id); + } + + @Post('/delete', { summary: 'sys:settings:edit' }) + async delete(@Query('id') id: number) { + return await super.delete(id); + } + @Post('/define', { summary: Constants.per.authOnly }) + async define(@Query('type') type: string,@Query('addonType') addonType: string) { + return await super.define(type,addonType); + } + + @Post('/getTypeDict', { summary: Constants.per.authOnly }) + async getTypeDict(@Query('addonType') addonType: string) { + return await super.getTypeDict(addonType); + } + + @Post('/simpleInfo', { summary: Constants.per.authOnly }) + async simpleInfo(@Query('addonType') addonType: string,@Query('id') id: number) { + return await super.simpleInfo(addonType,id); + } + + @Post('/getDefaultId', { summary: Constants.per.authOnly }) + async getDefaultId(@Query('addonType') addonType: string) { + return await super.getDefaultId(addonType); + } + + @Post('/setDefault', { summary: Constants.per.authOnly }) + async setDefault(@Query('addonType') addonType: string,@Query('id') id: number) { + return await super.setDefault(addonType,id); + } + + + @Post('/options', { summary: Constants.per.authOnly }) + async options(@Query('addonType') addonType: string) { + return await super.options(addonType); + } + + @Post('/handle', { summary: Constants.per.authOnly }) + async handle(@Body(ALL) body: AddonRequestHandleReq) { + return await super.handle(body); + } +} diff --git a/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts b/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts index ac9bb2521..587a4743e 100644 --- a/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts +++ b/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts @@ -1,4 +1,4 @@ -import {ALL, Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core'; +import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core"; import { CrudController, SysPrivateSettings, @@ -6,14 +6,14 @@ import { SysSafeSetting, SysSettingsEntity, SysSettingsService -} from '@certd/lib-server'; -import {cloneDeep, merge} from 'lodash-es'; -import {PipelineService} from '../../../modules/pipeline/service/pipeline-service.js'; -import {UserSettingsService} from '../../../modules/mine/service/user-settings-service.js'; -import {getEmailSettings} from '../../../modules/sys/settings/fix.js'; -import {http, logger, simpleNanoId, utils} from '@certd/basic'; -import {CodeService} from '../../../modules/basic/service/code-service.js'; -import {SmsServiceFactory} from '../../../modules/basic/sms/factory.js'; +} from "@certd/lib-server"; +import { cloneDeep, merge } from "lodash-es"; +import { PipelineService } from "../../../modules/pipeline/service/pipeline-service.js"; +import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js"; +import { getEmailSettings } from "../../../modules/sys/settings/fix.js"; +import { http, logger, utils } from "@certd/basic"; +import { CodeService } from "../../../modules/basic/service/code-service.js"; +import { SmsServiceFactory } from "../../../modules/basic/sms/factory.js"; /** @@ -158,7 +158,7 @@ export class SysSettingsController extends CrudController { @Post('/testSms', { summary: 'sys:settings:edit' }) async testSms(@Body(ALL) body) { - await this.codeService.sendSmsCode(body.phoneCode, body.mobile, simpleNanoId()); + await this.codeService.sendSmsCode(body.phoneCode, body.mobile ); return this.ok({}); } diff --git a/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts b/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts new file mode 100644 index 000000000..299f7f5f0 --- /dev/null +++ b/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts @@ -0,0 +1,199 @@ +import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core"; +import { + AddonDefine, + AddonRequestHandleReq, + AddonService, + Constants, + CrudController, + newAddon, + ValidateException +} from "@certd/lib-server"; +import { AuthService } from "../../../modules/sys/authority/service/auth-service.js"; +import { checkPlus } from "@certd/plus-core"; +import { http, logger, utils } from "@certd/basic"; + +/** + * Addon + */ +@Provide() +@Controller('/api/addon') +export class AddonController extends CrudController { + @Inject() + service: AddonService; + @Inject() + authService: AuthService; + + getService(): AddonService { + return this.service; + } + + @Post('/page', { summary: Constants.per.authOnly }) + async page(@Body(ALL) body) { + body.query = body.query ?? {}; + delete body.query.userId; + const buildQuery = qb => { + qb.andWhere('user_id = :userId', { userId: this.getUserId() }); + }; + const res = await this.service.page({ + query: body.query, + page: body.page, + sort: body.sort, + buildQuery, + }); + return this.ok(res); + } + + @Post('/list', { summary: Constants.per.authOnly }) + async list(@Body(ALL) body) { + body.query = body.query ?? {}; + body.query.userId = this.getUserId(); + return super.list(body); + } + + @Post('/add', { summary: Constants.per.authOnly }) + async add(@Body(ALL) bean) { + bean.userId = this.getUserId(); + const type = bean.type; + const addonType = bean.addonType; + if (! type || !addonType){ + throw new ValidateException('请选择Addon类型'); + } + const define: AddonDefine = this.service.getDefineByType(type,addonType); + if (!define) { + throw new ValidateException('Addon类型不存在'); + } + if (define.needPlus) { + checkPlus(); + } + return super.add(bean); + } + + @Post('/update', { summary: Constants.per.authOnly }) + async update(@Body(ALL) bean) { + await this.service.checkUserId(bean.id, this.getUserId()); + const old = await this.service.info(bean.id); + if (!old) { + throw new ValidateException('Addon配置不存在'); + } + if (old.type !== bean.type ) { + const addonType = old.type; + const type = bean.type; + const define: AddonDefine = this.service.getDefineByType(type,addonType); + if (!define) { + throw new ValidateException('Addon类型不存在'); + } + if (define.needPlus) { + checkPlus(); + } + } + delete bean.userId; + return super.update(bean); + } + @Post('/info', { summary: Constants.per.authOnly }) + async info(@Query('id') id: number) { + await this.service.checkUserId(id, this.getUserId()); + return super.info(id); + } + + @Post('/delete', { summary: Constants.per.authOnly }) + async delete(@Query('id') id: number) { + await this.service.checkUserId(id, this.getUserId()); + return super.delete(id); + } + + @Post('/define', { summary: Constants.per.authOnly }) + async define(@Query('type') type: string,@Query('addonType') addonType: string) { + const notification = this.service.getDefineByType(type,addonType); + return this.ok(notification); + } + + @Post('/getTypeDict', { summary: Constants.per.authOnly }) + async getTypeDict(@Query('addonType') addonType: string) { + const list: any = this.service.getDefineList(addonType); + let dict = []; + for (const item of list) { + dict.push({ + value: item.name, + label: item.title, + needPlus: item.needPlus ?? false, + icon: item.icon, + }); + } + dict = dict.sort(a => { + return a.needPlus ? 0 : -1; + }); + return this.ok(dict); + } + + @Post('/simpleInfo', { summary: Constants.per.authOnly }) + async simpleInfo(@Query('addonType') addonType: string,@Query('id') id: number) { + if (id === 0) { + //获取默认 + const res = await this.service.getDefault(this.getUserId(),addonType); + if (!res) { + throw new ValidateException('默认Addon配置不存在'); + } + const simple = await this.service.getSimpleInfo(res.id); + return this.ok(simple); + } + await this.authService.checkEntityUserId(this.ctx, this.service, id); + const res = await this.service.getSimpleInfo(id); + return this.ok(res); + } + + @Post('/getDefaultId', { summary: Constants.per.authOnly }) + async getDefaultId(@Query('addonType') addonType: string) { + const res = await this.service.getDefault(this.getUserId(),addonType); + return this.ok(res?.id); + } + + @Post('/setDefault', { summary: Constants.per.authOnly }) + async setDefault(@Query('addonType') addonType: string,@Query('id') id: number) { + await this.service.checkUserId(id, this.getUserId()); + const res = await this.service.setDefault(id, this.getUserId(),addonType); + return this.ok(res); + } + + + @Post('/options', { summary: Constants.per.authOnly }) + async options(@Query('addonType') addonType: string) { + const res = await this.service.list({ + query: { + userId: this.getUserId(), + addonType + }, + }); + for (const item of res) { + delete item.setting; + } + return this.ok(res); + } + + + @Post('/handle', { summary: Constants.per.authOnly }) + async handle(@Body(ALL) body: AddonRequestHandleReq) { + const userId = this.getUserId(); + let inputAddon = body.input.addon; + if (body.input.id > 0) { + const oldEntity = await this.service.info(body.input.id); + if (oldEntity) { + if (oldEntity.userId !== userId) { + throw new Error('addon not found'); + } + // const param: any = { + // type: body.typeName, + // setting: JSON.stringify(body.input.access), + // }; + inputAddon = JSON.parse( oldEntity.setting) + } + } + const ctx = { + http: http, + logger:logger, + utils:utils, + } + const addon = await newAddon(body.addonType,body.typeName, inputAddon,ctx); + const res = await addon.onRequest(body); + return this.ok(res); + } +} diff --git a/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts b/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts index 4c60d394c..2e496cf9e 100644 --- a/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts +++ b/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts @@ -29,25 +29,23 @@ export class LoginController extends BaseController { throw new CommonException('暂未开启自助找回'); } // 找回密码的验证码允许错误次数 - const errorNum = 5; + const maxErrorCount = 5; if(body.type === 'email') { this.codeService.checkEmailCode({ verificationType: 'forgotPassword', email: body.input, - randomStr: body.randomStr, validateCode: body.validateCode, - errorNum, + maxErrorCount: maxErrorCount, throwError: true, }); } else if(body.type === 'mobile') { await this.codeService.checkSmsCode({ verificationType: 'forgotPassword', mobile: body.input, - randomStr: body.randomStr, phoneCode: body.phoneCode, smsCode: body.validateCode, - errorNum, + maxErrorCount: maxErrorCount, throwError: true, }); } else { diff --git a/packages/ui/certd-server/src/controller/user/login/login-controller.ts b/packages/ui/certd-server/src/controller/user/login/login-controller.ts index 2877c257c..7a863769b 100644 --- a/packages/ui/certd-server/src/controller/user/login/login-controller.ts +++ b/packages/ui/certd-server/src/controller/user/login/login-controller.ts @@ -1,8 +1,9 @@ -import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core'; -import { LoginService } from '../../../modules/login/service/login-service.js'; -import { BaseController, Constants, SysPublicSettings, SysSettingsService } from '@certd/lib-server'; -import { CodeService } from '../../../modules/basic/service/code-service.js'; -import { checkComm } from '@certd/plus-core'; +import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core"; +import { LoginService } from "../../../modules/login/service/login-service.js"; +import { AddonService, BaseController, Constants, SysPublicSettings, SysSettingsService } from "@certd/lib-server"; +import { CodeService } from "../../../modules/basic/service/code-service.js"; +import { checkComm } from "@certd/plus-core"; +import { CaptchaService } from "../../../modules/basic/service/captcha-service.js"; /** */ @@ -16,13 +17,22 @@ export class LoginController extends BaseController { @Inject() sysSettingsService: SysSettingsService; + @Inject() + addonService: AddonService; + + @Inject() + captchaService: CaptchaService; @Post('/login', { summary: Constants.per.guest }) public async login( @Body(ALL) - user: any + body: any ) { - const token = await this.loginService.loginByPassword(user); + const settings = await this.sysSettingsService.getPublicSettings() + if (settings.captchaEnabled === true) { + await this.captchaService.doValidate({form:body.captcha,must:false,captchaAddonId:settings.captchaAddonId}) + } + const token = await this.loginService.loginByPassword(body); this.writeTokenCookie(token); return this.ok(token); } diff --git a/packages/ui/certd-server/src/controller/user/login/register-controller.ts b/packages/ui/certd-server/src/controller/user/login/register-controller.ts index 579bcef42..f31f0ef7f 100644 --- a/packages/ui/certd-server/src/controller/user/login/register-controller.ts +++ b/packages/ui/certd-server/src/controller/user/login/register-controller.ts @@ -13,8 +13,7 @@ export type RegisterReq = { phoneCode?: string; validateCode: string; - imgCode: string; - randomStr: string; + captcha:any; }; /** @@ -52,7 +51,7 @@ export class RegisterController extends BaseController { throw new Error('用户名不能为空'); } - await this.codeService.checkCaptcha(body.randomStr, body.imgCode); + await this.codeService.checkCaptcha(body.captcha); const newUser = await this.userService.register(body.type, { username: body.username, password: body.password, @@ -68,7 +67,6 @@ export class RegisterController extends BaseController { mobile: body.mobile, phoneCode: body.phoneCode, smsCode: body.validateCode, - randomStr: body.randomStr, throwError: true, }); const newUser = await this.userService.register(body.type, { @@ -85,7 +83,6 @@ export class RegisterController extends BaseController { checkPlus(); this.codeService.checkEmailCode({ email: body.email, - randomStr: body.randomStr, validateCode: body.validateCode, throwError: true, }); diff --git a/packages/ui/certd-server/src/modules/basic/service/captcha-service.ts b/packages/ui/certd-server/src/modules/basic/service/captcha-service.ts new file mode 100644 index 000000000..e5b6b951e --- /dev/null +++ b/packages/ui/certd-server/src/modules/basic/service/captcha-service.ts @@ -0,0 +1,54 @@ +import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { AddonService, SysSettingsService } from "@certd/lib-server"; +import { logger } from "@certd/basic"; +import { ICaptchaAddon } from "../../../plugins/plugin-captcha/api.js"; + +@Provide() +@Scope(ScopeEnum.Request, { allowDowngrade: true }) +export class CaptchaService { + @Inject() + sysSettingsService: SysSettingsService; + @Inject() + addonService: AddonService; + + + async getCaptcha(captchaAddonId?:number){ + if (!captchaAddonId) { + const settings = await this.sysSettingsService.getPublicSettings() + captchaAddonId = settings.captchaAddonId ?? 0 + } + const addon:ICaptchaAddon = await this.addonService.getAddonById(captchaAddonId,true,0) + if (!addon) { + throw new Error('验证码插件还未配置') + } + return await addon.getCaptcha() + } + + + async doValidate(opts:{form:any,must?:boolean,captchaAddonId?:number}){ + if (!opts.captchaAddonId) { + const settings = await this.sysSettingsService.getPublicSettings() + opts.captchaAddonId = settings.captchaAddonId ?? 0 + } + const addon = await this.addonService.getById(opts.captchaAddonId,0) + if (!addon) { + if (opts.must) { + throw new Error('请先配置验证码插件'); + } + logger.warn('验证码插件还未配置,忽略验证码校验') + return true + } + + if (!opts.form) { + throw new Error('请输入验证码'); + } + const res = await addon.onValidate(opts.form) + if (!res) { + throw new Error('验证码错误'); + } + + return true + + } + +} diff --git a/packages/ui/certd-server/src/modules/basic/service/code-service.ts b/packages/ui/certd-server/src/modules/basic/service/code-service.ts index 1a252efc7..2cfd1843c 100644 --- a/packages/ui/certd-server/src/modules/basic/service/code-service.ts +++ b/packages/ui/certd-server/src/modules/basic/service/code-service.ts @@ -8,6 +8,7 @@ import { EmailService } from './email-service.js'; import { AccessService } from '@certd/lib-server'; import { AccessSysGetter } from '@certd/lib-server'; import { isComm } from '@certd/plus-core'; +import { CaptchaService } from "./captcha-service.js"; // {data: '', text: 'abcd'} /** @@ -23,44 +24,19 @@ export class CodeService { @Inject() accessService: AccessService; - /** - */ - async generateCaptcha(randomStr) { - const svgCaptcha = await import('svg-captcha'); - const c = svgCaptcha.create(); - //{data: '', text: 'abcd'} - const imgCode = c.text; // = RandomUtil.randomStr(4, true); - cache.set('imgCode:' + randomStr, imgCode, { - ttl: 2 * 60 * 1000, //过期时间 2分钟 - }); - return c; - } + @Inject() + captchaService: CaptchaService; - async getCaptchaText(randomStr) { - return cache.get('imgCode:' + randomStr); - } - async removeCaptcha(randomStr) { - cache.delete('imgCode:' + randomStr); - } - async checkCaptcha(randomStr: string, userCaptcha: string) { - const code = await this.getCaptchaText(randomStr); - if (code == null) { - throw new Error('验证码已过期'); - } - if (code.toLowerCase() !== userCaptcha.toLowerCase()) { - throw new Error('验证码不正确'); - } - await this.removeCaptcha(randomStr); - return true; + async checkCaptcha(body:any) { + return await this.captchaService.doValidate({form:body}) } /** */ async sendSmsCode( phoneCode = '86', mobile: string, - randomStr: string, opts?: { duration?: number, verificationType?: string, @@ -70,9 +46,6 @@ export class CodeService { if (!mobile) { throw new Error('手机号不能为空'); } - if (!randomStr) { - throw new Error('randomStr不能为空'); - } const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4)); const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1)); @@ -96,7 +69,7 @@ export class CodeService { phoneCode, }); - const key = this.buildSmsCodeKey(phoneCode, mobile, randomStr, opts?.verificationType); + const key = this.buildSmsCodeKey(phoneCode, mobile, opts?.verificationType); cache.set(key, smsCode, { ttl: duration * 60 * 1000, //5分钟 }); @@ -106,12 +79,10 @@ export class CodeService { /** * * @param email 收件邮箱 - * @param randomStr * @param opts title标题 content内容模版 duration有效时间单位分钟 verificationType验证类型 */ async sendEmailCode( email: string, - randomStr: string, opts?: { title?: string, content?: string, @@ -123,9 +94,7 @@ export class CodeService { if (!email) { throw new Error('Email不能为空'); } - if (!randomStr) { - throw new Error('randomStr不能为空'); - } + let siteTitle = 'Certd'; if (isComm()) { @@ -149,7 +118,7 @@ export class CodeService { receivers: [email], }); - const key = this.buildEmailCodeKey(email, randomStr, opts?.verificationType); + const key = this.buildEmailCodeKey(email,opts?.verificationType); cache.set(key, code, { ttl: duration * 60 * 1000, //5分钟 }); @@ -159,31 +128,32 @@ export class CodeService { /** * checkSms */ - async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; verificationType?: string; throwError: boolean; errorNum?: number }) { - const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr, opts.verificationType); - if (isDev()) { + async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; verificationType?: string; throwError: boolean; maxErrorCount?: number }) { + const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.verificationType); + return this.checkValidateCode("sms",key, opts.smsCode, opts.throwError, opts.maxErrorCount); + + } + + buildSmsCodeKey(phoneCode: string, mobile: string, verificationType?: string) { + return ['sms', verificationType, phoneCode, mobile].filter(item => !!item).join(':'); + } + + buildEmailCodeKey(email: string, verificationType?: string) { + return ['email', verificationType, email].filter(item => !!item).join(':'); + } + checkValidateCode(type:string,key: string, userCode: string, throwError = true, maxErrorCount = 3) { + // 记录异常次数key + if (isDev() && userCode==="1234567") { return true; } - return this.checkValidateCode(key, opts.smsCode, opts.throwError, opts.errorNum); - } - - buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string, verificationType?: string) { - return ['sms', verificationType, phoneCode, mobile, randomStr].filter(item => !!item).join(':'); - } - - buildEmailCodeKey(email: string, randomStr: string, verificationType?: string) { - return ['email', verificationType, email, randomStr].filter(item => !!item).join(':'); - } - checkValidateCode(key: string, userCode: string, throwError = true, errorNum = 3) { - // 记录异常次数key const err_num_key = key + ':err_num'; - //验证图片验证码 + //验证邮件验证码 const code = cache.get(key); if (code == null || code !== userCode) { let maxRetryCount = false; - if (!!code && errorNum > 0) { + if (!!code && maxErrorCount > 0) { const err_num = cache.get(err_num_key) || 0 - if(err_num >= errorNum - 1) { + if(err_num >= maxErrorCount - 1) { maxRetryCount = true; cache.delete(key); cache.delete(err_num_key); @@ -194,7 +164,8 @@ export class CodeService { } } if (throwError) { - throw new CodeErrorException(!maxRetryCount ? '验证码错误': '验证码错误请获取新的验证码'); + const label = type ==='sms' ? '手机' : '邮箱'; + throw new CodeErrorException(!maxRetryCount ? `${label}验证码错误`: `${label}验证码错误请获取新的验证码`); } return false; } @@ -203,9 +174,9 @@ export class CodeService { return true; } - checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; verificationType?: string; throwError: boolean; errorNum?: number }) { - const key = this.buildEmailCodeKey(opts.email, opts.randomStr, opts.verificationType); - return this.checkValidateCode(key, opts.validateCode, opts.throwError, opts.errorNum); + checkEmailCode(opts: { validateCode: string; email: string; verificationType?: string; throwError: boolean; maxErrorCount?: number }) { + const key = this.buildEmailCodeKey(opts.email, opts.verificationType); + return this.checkValidateCode('email',key, opts.validateCode, opts.throwError, opts.maxErrorCount); } compile(templateString: string) { diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts index ed7cc39b9..681d3b566 100644 --- a/packages/ui/certd-server/src/modules/login/service/login-service.ts +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -1,17 +1,22 @@ -import {Config, Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core'; -import {UserService} from '../../sys/authority/service/user-service.js'; -import jwt from 'jsonwebtoken'; -import {AuthException, CommonException, Need2FAException} from "@certd/lib-server"; -import {RoleService} from '../../sys/authority/service/role-service.js'; -import {UserEntity} from '../../sys/authority/entity/user.js'; -import {SysSettingsService} from '@certd/lib-server'; -import {SysPrivateSettings} from '@certd/lib-server'; -import {cache, utils} from '@certd/basic'; -import {LoginErrorException} from '@certd/lib-server/dist/basic/exception/login-error-exception.js'; -import {CodeService} from '../../basic/service/code-service.js'; -import {TwoFactorService} from "../../mine/service/two-factor-service.js"; -import {UserSettingsService} from '../../mine/service/user-settings-service.js'; -import {isPlus} from "@certd/plus-core"; +import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { UserService } from "../../sys/authority/service/user-service.js"; +import jwt from "jsonwebtoken"; +import { + AuthException, + CommonException, + Need2FAException, + SysPrivateSettings, + SysSettingsService +} from "@certd/lib-server"; +import { RoleService } from "../../sys/authority/service/role-service.js"; +import { UserEntity } from "../../sys/authority/entity/user.js"; +import { cache, utils } from "@certd/basic"; +import { LoginErrorException } from "@certd/lib-server/dist/basic/exception/login-error-exception.js"; +import { CodeService } from "../../basic/service/code-service.js"; +import { TwoFactorService } from "../../mine/service/two-factor-service.js"; +import { UserSettingsService } from "../../mine/service/user-settings-service.js"; +import { isPlus } from "@certd/plus-core"; +import { AddonService } from "@certd/lib-server/dist/user/addon/service/addon-service.js"; /** * 系统用户 @@ -35,6 +40,8 @@ export class LoginService { userSettingsService: UserSettingsService; @Inject() twoFactorService: TwoFactorService; + @Inject() + addonService: AddonService; checkIsBlocked(username: string) { const blockDurationKey = `login_block_duration:${username}`; @@ -106,13 +113,12 @@ export class LoginService { mobile: req.mobile, phoneCode: req.phoneCode, smsCode: req.smsCode, - randomStr: req.randomStr, throwError: false, }); const {mobile, phoneCode} = req; if (!smsChecked) { - this.addErrorTimes(mobile, '验证码错误'); + this.addErrorTimes(mobile, '手机验证码错误'); } let info = await this.userService.findOne({phoneCode, mobile: mobile}); if (info == null) { diff --git a/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts b/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts index f16ca87dc..807489c91 100644 --- a/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts +++ b/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts @@ -30,6 +30,9 @@ export class CertInfoEntity { @Column({ name: 'cert_provider', comment: '证书颁发机构' }) certProvider: string; + @Column({ name: 'effective_time', comment: '生效时间' }) + effectiveTime: number; + @Column({ name: 'expires_time', comment: '过期时间' }) expiresTime: number; diff --git a/packages/ui/certd-server/src/modules/monitor/entity/site-info.ts b/packages/ui/certd-server/src/modules/monitor/entity/site-info.ts index 1c1e4eda7..f4dc95016 100644 --- a/packages/ui/certd-server/src/modules/monitor/entity/site-info.ts +++ b/packages/ui/certd-server/src/modules/monitor/entity/site-info.ts @@ -26,6 +26,8 @@ export class SiteInfoEntity { @Column({ name: 'cert_provider', comment: '证书颁发机构', length: 100 }) certProvider: string; + @Column({ name: 'cert_effective_time', comment: '证书生效时间' }) + certEffectiveTime: number; @Column({ name: 'cert_expires_time', comment: '证书到期时间' }) certExpiresTime: number; @Column({ name: 'last_check_time', comment: '上次检查时间' }) diff --git a/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts index 8bb2e8ac6..4819bda92 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts @@ -164,6 +164,7 @@ export class CertInfoService extends BaseService { bean.domains = domains.join(','); bean.domain = domains[0]; bean.domainCount = domains.length; + bean.effectiveTime = certReader.effective; bean.expiresTime = certReader.expires; bean.certProvider = certReader.detail.issuer.commonName; bean.userId = userId diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts index 4f9f7f9f3..8c21d9d45 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts @@ -134,6 +134,7 @@ export class SiteInfoService extends BaseService { if (!certi) { throw new Error("没有发现证书"); } + const effective = certi.valid_from; const expires = certi.valid_to; const allDomains = certi.subjectaltname?.replaceAll("DNS:", "").split(",") || []; const mainDomain = certi.subject?.CN; @@ -149,12 +150,13 @@ export class SiteInfoService extends BaseService { certDomains: domains.join(","), certStatus: status, certProvider: issuer, + certEffectiveTime: dayjs(effective).valueOf(), certExpiresTime: dayjs(expires).valueOf(), lastCheckTime: dayjs().valueOf(), error: null, checkStatus: "ok" }; - logger.info(`测试站点成功:id=${updateData.id},site=${site.name},expiresTime=${updateData.certExpiresTime}`) + logger.info(`测试站点成功:id=${updateData.id},site=${site.name},certEffectiveTime=${updateData.certEffectiveTime},expiresTime=${updateData.certExpiresTime}`) if (site.ipCheck) { delete updateData.checkStatus } diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts b/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts index e2b997310..f7e57b1b8 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts @@ -80,7 +80,11 @@ export class SiteTester { } } - options.agent = new https.Agent({ keepAlive: false, lookup: customLookup }); + const agentOptions:any = { keepAlive: false }; + if (customLookup) { + agentOptions.lookup = customLookup + } + options.agent = new https.Agent(agentOptions); // 创建 HTTPS 请求 const requestPromise = safePromise((resolve, reject) => { diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index a4937cd04..3d5b9420c 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -934,6 +934,7 @@ export class PipelineService extends BaseService { "sslProvider": "letsencrypt", "privateKeyType": "rsa_2048", "certProfile": "classic", + "preferredChain": "ISRG Root X1", "useProxy": false, "skipLocalVerify": false, "maxCheckRetryCount": 20, diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts index c0d4358ed..de913194f 100644 --- a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts +++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts @@ -238,9 +238,12 @@ export class UserService extends BaseService { async forgotPassword( data: { - type: ForgotPasswordType; input?: string, phoneCode?: string, - randomStr: string, imgCode:string, validateCode: string, - password: string, confirmPassword: string, + type: ForgotPasswordType; + input?: string, + phoneCode?: string, + validateCode: string, + password: string, + confirmPassword: string, } ) { if(!data.type) { @@ -249,7 +252,13 @@ export class UserService extends BaseService { if(data.password !== data.confirmPassword) { throw new CommonException('两次输入的密码不一致'); } - const user = await this.findOne([{ [data.type]: data.input }]); + const where :any= { + [data.type]: data.input, + }; + if (data.type === 'mobile' ) { + where.phoneCode = data.phoneCode ?? '86'; + } + const user = await this.findOne({ [data.type]: data.input }); console.log('user', user) if(!user) { throw new CommonException('用户不存在'); diff --git a/packages/ui/certd-server/src/plugins/index.ts b/packages/ui/certd-server/src/plugins/index.ts index 8157b61d9..85e4d93a3 100644 --- a/packages/ui/certd-server/src/plugins/index.ts +++ b/packages/ui/certd-server/src/plugins/index.ts @@ -34,3 +34,5 @@ export * from './plugin-admin/index.js' export * from './plugin-ksyun/index.js' export * from './plugin-apisix/index.js' export * from './plugin-dokploy/index.js' +export * from './plugin-godaddy/index.js' +export * from './plugin-captcha/index.js' diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts index 14286d2aa..1d4ba68a7 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts @@ -116,6 +116,12 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin { }) protocol!: string; + @TaskInput({ + title: '证书名称', + helper: '上传后将以此名称作为前缀备注', + }) + certName!: string; + async onInstance() {} async exec(cmd: string) { @@ -179,7 +185,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin { bodyType: 'json', }); // body params - const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)) + const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt),this.certName??"") const body: { [key: string]: any } = { certConfig: { diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/api.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/api.ts new file mode 100644 index 000000000..cee513a66 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/api.ts @@ -0,0 +1,4 @@ +export interface ICaptchaAddon{ + onValidate(data?:any):Promise; + getCaptcha():Promise; +} diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts new file mode 100644 index 000000000..a5272988a --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts @@ -0,0 +1,122 @@ +import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server"; +import crypto from "crypto"; +import { ICaptchaAddon } from "../api.js"; + +@IsAddon({ + addonType:"captcha", + name: 'geetest', + title: '极验验证码v4', + desc: '', + showTest:false, +}) +export class GeeTestCaptcha extends BaseAddon implements ICaptchaAddon{ + + @AddonInput({ + title: '验证ID', + component: { + placeholder: 'captchaId', + }, + helper:"[极验验证码v4](https://console.geetest.com/sensbot/management) -> 创建业务模块 -> 新增业务场景", + required: true, + }) + captchaId = ''; + + @AddonInput({ + title: '验证Key', + component: { + placeholder: 'captchaKey', + }, + required: true, + }) + captchaKey = ''; + + + async onValidate(data?:any) { + if (!data) { + return false + } + // geetest 服务地址 +// geetest server url + const API_SERVER = "http://gcaptcha4.geetest.com"; + +// geetest 验证接口 +// geetest server interface + const API_URL = API_SERVER + "/validate" + "?captcha_id=" + this.captchaId; + + + // 前端参数 + // web parameter + var lot_number = data['lot_number']; + var captcha_output = data['captcha_output']; + var pass_token = data['pass_token']; + var gen_time = data['gen_time']; + if (!lot_number || !captcha_output || !pass_token || !gen_time) { + return false; + } + + // 生成签名, 使用标准的hmac算法,使用用户当前完成验证的流水号lot_number作为原始消息message,使用客户验证私钥作为key + // 采用sha256散列算法将message和key进行单向散列生成最终的 “sign_token” 签名 + // use lot_number + CAPTCHA_KEY, generate the signature + var sign_token = this.hmac_sha256_encode(lot_number, this.captchaKey); + + // 向极验转发前端数据 + “sign_token” 签名 + // send web parameter and “sign_token” to geetest server + var datas = { + 'lot_number': lot_number, + 'captcha_output': captcha_output, + 'pass_token': pass_token, + 'gen_time': gen_time, + 'sign_token': sign_token + }; + + // post request + // 根据极验返回的用户验证状态, 网站主进行自己的业务逻辑 + // According to the user authentication status returned by the geetest, the website owner carries out his own business logic + try{ + const res = await this.doRequest(datas, API_URL) + if (res.result == "success") { + // 验证成功 + // verification successful + return true; + } else { + // 验证失败 + // verification failed + this.logger.error("极验验证不通过 ",res.reason) + return false; + } + }catch (e) { + this.ctx.logger.error("极验验证服务异常",e) + return true + } + } + + // 生成签名 +// Generate signature + hmac_sha256_encode(value, key){ + var hash = crypto.createHmac("sha256", key) + .update(value, 'utf8') + .digest('hex'); + return hash; + } + + +// 发送post请求, 响应json数据如:{"result": "success", "reason": "", "captcha_args": {}} +// Send a post request and respond to JSON data, such as: {result ":" success "," reason ":" "," captcha_args ": {}} + async doRequest(datas, url){ + var options = { + url: url, + method: "POST", + params: datas, + timeout: 5000 + }; + const result = await this.ctx.http.request(options); + return result; + } + + async getCaptcha(): Promise { + return { + captchaId: this.captchaId, + } + } + +} diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/image/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/image/index.ts new file mode 100644 index 000000000..ff3b79bd9 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/image/index.ts @@ -0,0 +1,56 @@ +import { BaseAddon, IsAddon } from "@certd/lib-server"; +import { ICaptchaAddon } from "../api.js"; +import { cache } from "@certd/basic"; +import { nanoid } from "nanoid"; + +@IsAddon({ + addonType:"captcha", + name: 'image', + title: '图片验证码', + desc: '', + showTest:false, +}) +export class ImageCaptcha extends BaseAddon implements ICaptchaAddon{ + + async onValidate(data?:any) { + if (!data) { + return false; + } + return await this.checkCaptcha(data.randomStr, data.imageCode) + } + + async getCaptchaText(randomStr:string) { + return cache.get('imgCode:' + randomStr); + } + + async removeCaptcha(randomStr:string) { + cache.delete('imgCode:' + randomStr); + } + + async checkCaptcha(randomStr: string, userCaptcha: string) { + const code = await this.getCaptchaText(randomStr); + if (code == null) { + throw new Error('验证码已过期'); + } + if (code.toLowerCase() !== userCaptcha?.toLowerCase()) { + throw new Error('验证码不正确'); + } + await this.removeCaptcha(randomStr); + return true; + } + + async getCaptcha(): Promise { + const svgCaptcha = await import('svg-captcha'); + const c = svgCaptcha.create(); + //{data: '', text: 'abcd'} + const imgCode = c.text; // = RandomUtil.randomStr(4, true); + const randomStr = nanoid(10) + cache.set('imgCode:' + randomStr, imgCode, { + ttl: 2 * 60 * 1000, //过期时间 2分钟 + }) + return { + randomStr: randomStr, + imageData: c.data, + } + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/index.ts new file mode 100644 index 000000000..8b32cdb84 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/index.ts @@ -0,0 +1,2 @@ +export * from './geetest/index.js'; +export * from './image/index.js'; diff --git a/packages/ui/certd-server/src/plugins/plugin-gcore/plugins/plugin-flush.ts b/packages/ui/certd-server/src/plugins/plugin-gcore/plugins/plugin-flush.ts index 9b3458646..ec759fb82 100644 --- a/packages/ui/certd-server/src/plugins/plugin-gcore/plugins/plugin-flush.ts +++ b/packages/ui/certd-server/src/plugins/plugin-gcore/plugins/plugin-flush.ts @@ -22,6 +22,7 @@ export class GcoreflushPlugin extends AbstractTaskPlugin { certName!: string; @TaskInput({ title: '证书ID', + required:true, }) ssl_id!: string; @@ -66,6 +67,10 @@ export class GcoreflushPlugin extends AbstractTaskPlugin { async execute(): Promise { const { cert, accessId } = this; + + if(!this.ssl_id){ + throw new Error('请填写要刷新的证书ID'); + } const access = (await this.getAccess(accessId)) as GcoreAccess; let otp = null; if (access.otpkey) { diff --git a/packages/ui/certd-server/src/plugins/plugin-godaddy/access.ts b/packages/ui/certd-server/src/plugins/plugin-godaddy/access.ts new file mode 100644 index 000000000..d8d00af06 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-godaddy/access.ts @@ -0,0 +1,96 @@ +import {AccessInput, BaseAccess, IsAccess, Pager, PageSearch} from '@certd/pipeline'; +import {HttpRequestConfig} from "@certd/basic"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: 'godaddy', + title: 'godaddy授权', + icon: 'simple-icons:godaddy', + desc: '', +}) +export class GodaddyAccess extends BaseAccess { + /** + * 授权属性配置 + */ + @AccessInput({ + title: 'Key', + component: { + placeholder: '授权key', + }, + helper:"[https://developer.godaddy.com/keys](https://developer.godaddy.com/keys),创建key(选择product,不要选择ote)", + required: true, + encrypt: false, + }) + key = ''; + + @AccessInput({ + title: 'Secret', + component: { + name:"a-input-password", + vModel:"value", + }, + required: true, + encrypt: true, + }) + secret = ''; + + @AccessInput({ + title: 'HTTP代理', + component: { + placeholder: 'http://xxxxx:xx', + }, + helper: '使用https_proxy', + required: false, + }) + httpProxy = ''; + + @AccessInput({ + title: "测试", + component: { + name: "api-test", + action: "TestRequest" + }, + helper: "点击测试接口是否正常(注意账号中必须要有50个域名才能使用API接口)" + }) + testRequest = true; + + async onTestRequest() { + const res = await this.getDomainList({pageSize:1}); + this.ctx.logger.info(res) + return "ok" + } + + + async getDomainList(opts?: PageSearch){ + + const pager = new Pager(opts); + const req = { + url :`/v1/domains?limit=${pager.pageSize}`, + method: "get", + } + return await this.doRequest(req); + } + + + async doRequest(req: HttpRequestConfig){ + const headers = { + "Authorization": `sso-key ${this.key}:${this.secret}`, + ...req.headers + }; + return await this.ctx.http.request({ + headers, + baseURL: "https://api.godaddy.com", + ...req, + logRes: true, + httpProxy: this.httpProxy, + }); + } + + + +} + +new GodaddyAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-godaddy/dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-godaddy/dns-provider.ts new file mode 100644 index 000000000..4c3c9ebca --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-godaddy/dns-provider.ts @@ -0,0 +1,90 @@ +import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert"; + +import { GodaddyAccess } from "./access.js"; + +export type GodaddyRecord = { + domain: string, + type: string, + name: string, + data: string, +}; + +// 这里通过IsDnsProvider注册一个dnsProvider +@IsDnsProvider({ + name: 'godaddy', + title: 'godaddy', + desc: 'GoDaddy', + icon: 'simple-icons:godaddy', + // 这里是对应的 cloudflare的access类型名称 + accessType: 'godaddy', + order:10, +}) +export class GodaddyDnsProvider extends AbstractDnsProvider { + access!: GodaddyAccess; + async onInstance() { + //一些初始化的操作 + // 也可以通过ctx成员变量传递context + this.access = this.ctx.access as GodaddyAccess; + } + + /** + * 创建dns解析记录,用于验证域名所有权 + */ + async createRecord(options: CreateRecordOptions): Promise { + /** + * fullRecord: '_acme-challenge.test.example.com', + * value: 一串uuid + * type: 'TXT', + * domain: 'example.com' + * hostRecord: _acme-challenge.test + */ + const { fullRecord,hostRecord, value, type, domain } = options; + this.logger.info('添加域名解析:', fullRecord, value, type, domain); + + + const res = await this.access.doRequest({ + method: 'PATCH', + url: '/v1/domains/'+domain+'/records', + data: [ + { + type: 'TXT', + name: hostRecord, + data: value, + ttl: 600, + } + ] + }) + this.logger.info('添加域名解析成功:', res); + return { + domain: domain, + type: 'TXT', + name: hostRecord, + data: value, + }; + } + + + /** + * 删除dns解析记录,清理申请痕迹 + * @param options + */ + async removeRecord(options: RemoveRecordOptions): Promise { + const { fullRecord, value } = options.recordReq; + const record = options.recordRes; + this.logger.info('删除域名解析:', fullRecord, value); + if (!record) { + this.logger.info('record为空,不执行删除'); + return; + } + //这里调用删除txt dns解析记录接口 + const {name,type,domain} = record + const res = await this.access.doRequest({ + method: 'DELETE', + url: '/v1/domains/'+domain+`/records/${type}/${name}`, + }) + this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},id=${res}`); + } +} + +//实例化这个provider,将其自动注册到系统中 +new GodaddyDnsProvider(); diff --git a/packages/ui/certd-server/src/plugins/plugin-godaddy/index.ts b/packages/ui/certd-server/src/plugins/plugin-godaddy/index.ts new file mode 100644 index 000000000..db899c717 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-godaddy/index.ts @@ -0,0 +1,2 @@ +export * from './dns-provider.js'; +export * from './access.js'; diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts index a35f981bc..189dcc56a 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts @@ -103,6 +103,10 @@ export class DeployCertToTencentEO extends AbstractTaskPlugin { logger: this.logger, }); + if (this.cert == null){ + throw new Error('请选择域名证书'); + } + let tencentCertId = this.cert as string; if (typeof this.cert !== 'string') { const certReader = new CertReader(this.cert); diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts index 60db3827b..b8f9220da 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts @@ -9,3 +9,4 @@ export * from './delete-expiring-cert/index.js'; export * from './deploy-to-tke-ingress/index.js'; export * from './deploy-to-live/index.js'; export * from './start-instances/index.js'; +export * from './refresh-cert/index.js'; diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts new file mode 100644 index 000000000..445fd4488 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts @@ -0,0 +1,340 @@ +import { + AbstractTaskPlugin, + IsTaskPlugin, + Pager, + PageSearch, + pluginGroups, + RunStrategy, + TaskInput +} from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { TencentAccess, TencentSslClient } from "@certd/plugin-lib"; +import { omit } from "lodash-es"; +@IsTaskPlugin({ + //命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名 + name: "TencentRefreshCert", + title: "腾讯云-更新证书(Id不变)", + desc: "根据证书id一键更新腾讯云证书并自动部署(Id不变),注意该接口为腾讯云白名单功能,非白名单用户无法使用该功能", + icon: "svg:icon-tencentcloud", + //插件分组 + group: pluginGroups.tencent.key, + needPlus: false, + default: { + //默认值配置照抄即可 + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed + } + } +}) +//类名规范,跟上面插件名称(name)一致 +export class TencentRefreshCert extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames] + } + // required: true, // 必填 + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "腾讯云授权", + component: { + name: "access-selector", + type: "tencent" //固定授权类型 + }, + required: true //必填 + }) + accessId!: string; + // + + @TaskInput( + createRemoteSelectInputDefine({ + title: "证书Id", + helper: "要更新的证书id,如果这里没有,请先给手动绑定一次证书", + action: TencentRefreshCert.prototype.onGetCertList.name, + pager: false, + search: false + }) + ) + certList!: string[]; + + // @TaskInput({ + // title: '资源类型', + // component: { + // name: 'a-select', + // vModel: 'value', + // allowClear: true, + // mode: "tags", + // options: [ + // { value: 'clb',label: '负载均衡'}, + // { value: 'cdn',label: 'CDN'}, + // { value: 'ddos',label: 'DDoS'}, + // { value: 'live',label: '直播'}, + // { value: 'vod',label: '点播'}, + // { value: 'waf',label: 'Web应用防火墙'}, + // { value: 'apigateway',label: 'API网关'}, + // { value: 'teo',label: 'TEO'}, + // { value: 'tke',label: '容器服务'}, + // { value: 'cos',label: '对象存储'}, + // { value: 'lighthouse',label: '轻应用服务器'}, + // { value: 'tse',label: '云原生微服务'}, + // { value: 'tcb',label: '云开发'}, + // ] + // }, + // helper: '', + // required: true, + // }) + // resourceTypes!: string[]; + + @TaskInput({ + title: '资源区域', + helper:"如果云资源类型区分区域,请选择区域,如果区域在选项中不存在,请手动输入", + component: { + name: 'remote-tree-select', + vModel: 'value', + action: TencentRefreshCert.prototype.onGetRegionsTree.name, + pager: false, + search: false, + watches: ['certList'], + }, + required: false, + }) + resourceTypesRegions!: string[]; + //插件实例化时执行的方法 + async onInstance() { + } + + //插件执行方法 + async execute(): Promise { + const access = await this.getAccess(this.accessId); + const sslClient = new TencentSslClient({ + access:access, + logger: this.logger, + }); + // await access.createCert({cert:this.cert}) + + let resourceTypes = [] + const resourceTypesRegions = [] + for (const item of this.resourceTypesRegions) { + const [type,region] = item.split("_") + if (!resourceTypes.includes( type)){ + resourceTypes.push(type) + } + if (!region){ + continue; + } + const resourceType = resourceTypesRegions.find(item => item.ResourceType == type) + if (!resourceType){ + resourceTypesRegions.push({ + ResourceType: type, + Regions: [region] + }) + }else{ + resourceType.Regions.push(region) + } + } + // resourceTypes = ["clb"] //固定clb + const maxRetry = 10 + for (const certId of this.certList) { + this.logger.info(`----------- 开始更新证书:${certId}`); + + let deployRes = null + + let retryCount = 0 + while(true){ + if (retryCount>maxRetry){ + this.logger.error(`任务创建失败`); + break; + } + retryCount++ + deployRes = await sslClient.UploadUpdateCertificateInstance({ + OldCertificateId: certId, + "ResourceTypes": resourceTypes, + "CertificatePublicKey": this.cert.crt, + "CertificatePrivateKey": this.cert.key, + "ResourceTypesRegions":resourceTypesRegions + }); + if (deployRes && deployRes.DeployRecordId>0){ + this.logger.info(`任务创建成功,开始检查结果:${JSON.stringify(deployRes)}`); + break; + }else{ + this.logger.info(`任务创建中,稍后查询:${JSON.stringify(deployRes)}`); + } + await this.ctx.utils.sleep(3000); + } + this.logger.info(`开始查询部署结果`); + + retryCount=0 + while(true){ + if (retryCount>maxRetry){ + this.logger.error(`任务结果检查失败`); + break; + } + retryCount++ + //查询部署状态 + const deployStatus = await sslClient.DescribeHostUploadUpdateRecordDetail({ + "DeployRecordId":deployRes.DeployRecordId + }) + const details = deployStatus.DeployRecordDetail + let allSuccess = true + for (const item of details) { + this.logger.info(`查询结果:${JSON.stringify(omit(item,"RecordDetailList"))}`); + if (item.Status === 2) { + throw new Error(`任务失败:${JSON.stringify(item.RecordDetailList)}`) + }else if (item.Status !== 1) { + //如果不是成功状态 + allSuccess = false + } + } + if (allSuccess) { + break; + } + await this.ctx.utils.sleep(10000); + } + this.logger.info(`----------- 更新证书${certId}成功`); + } + + } + + async onGetRegionsTree(data: PageSearch = {}){ + + const commonRegions = [ + /** + * 华南地区(广州) waf.ap-guangzhou.tencentcloudapi.com + * 华东地区(上海) waf.ap-shanghai.tencentcloudapi.com + * 华东地区(南京) waf.ap-nanjing.tencentcloudapi.com + * 华北地区(北京) waf.ap-beijing.tencentcloudapi.com + * 西南地区(成都) waf.ap-chengdu.tencentcloudapi.com + * 西南地区(重庆) waf.ap-chongqing.tencentcloudapi.com + * 港澳台地区(中国香港) waf.ap-hongkong.tencentcloudapi.com + * 亚太东南(新加坡) waf.ap-singapore.tencentcloudapi.com + * 亚太东南(雅加达) waf.ap-jakarta.tencentcloudapi.com + * 亚太东南(曼谷) waf.ap-bangkok.tencentcloudapi.com + * 亚太东北(首尔) waf.ap-seoul.tencentcloudapi.com + * 亚太东北(东京) waf.ap-tokyo.tencentcloudapi.com + * 美国东部(弗吉尼亚) waf.na-ashburn.tencentcloudapi.com + * 美国西部(硅谷) waf.na-siliconvalley.tencentcloudapi.com + * 南美地区(圣保罗) waf.sa-saopaulo.tencentcloudapi.com + * 欧洲地区(法兰克福) waf.eu-frankfurt.tencentcloudapi.com + */ + {value:"ap-guangzhou", label:"广州"}, + {value:"ap-shanghai", label:"上海"}, + {value:"ap-nanjing", label:"南京"}, + {value:"ap-beijing", label:"北京"}, + {value:"ap-chengdu", label:"成都"}, + {value:"ap-chongqing", label:"重庆"}, + {value:"ap-hongkong", label:"香港"}, + {value:"ap-singapore", label:"新加坡"}, + {value:"ap-jakarta", label:"雅加达"}, + {value:"ap-bangkok", label:"曼谷"}, + {value:"ap-tokyo", label:"东京"}, + {value:"ap-seoul", label:"首尔"}, + {value:"na-ashburn", label:"弗吉尼亚"}, + {value:"na-siliconvalley", label:"硅谷"}, + {value:"sa-saopaulo", label:"圣保罗"}, + {value:"eu-frankfurt", label:"法兰克福"}, + ] + + function buildTypeRegions(type: string) { + const options :any[]= [] + for (const region of commonRegions) { + options.push({ + label: type + "_" + region.label, + value: type + "_" + region.value, + }); + } + return options + } + + return [ + { value: 'cdn',label: 'CDN'}, + { value: 'ddos',label: 'DDoS'}, + { value: 'live',label: '直播'}, + { value: 'vod',label: '点播'}, + { value: 'teo',label: 'TEO'}, + { value: 'lighthouse',label: '轻应用服务器'}, + { + label: "负载均衡(clb)", + value: "clb", + children: buildTypeRegions("clb"), + }, + { + label: "Web应用防火墙(waf)", + value: "waf", + children: buildTypeRegions("waf"), + }, + { + label: "API网关(apigateway)", + value: "apigateway", + children: buildTypeRegions("apigateway"), + }, + { + label: "对象存储(COS)", + value: "cos", + children: buildTypeRegions("cos"), + }, + { + label: "容器服务(tke)", + value: "tke", + children: buildTypeRegions("tke"), + }, + { + label: "云原生微服务(tse)", + value: "tse", + children: buildTypeRegions("tse"), + }, + { + label: "云开发(tcb)", + value: "tcb", + children: buildTypeRegions("tcb"), + }, + ] + } + + async onGetCertList(data: PageSearch = {}) { + + const access = await this.getAccess(this.accessId) + const sslClient = new TencentSslClient({ + access:access, + logger: this.logger, + }); + + const pager = new Pager(data); + const offset = pager.getOffset(); + const limit = pager.pageSize + const res = await sslClient.DescribeCertificates({Limit:limit,Offset:offset,SearchKey:data.searchKey}) + const list = res.Certificates + if (!list || list.length === 0) { + throw new Error("没有找到证书,你可以直接手动输入id"); + } + + + /** + * certificate-id + * name + * dns-names + */ + const options = list.map((item: any) => { + return { + label: `${item.Alias}<${item.Domain}_${item.CertificateId}>`, + value: item.CertificateId, + domain: item.SubjectAltName, + }; + }); + return { + list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains), + }; + } +} + +//实例化一下,注册插件 +new TencentRefreshCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts b/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts index 05c9c4e53..262d247d1 100644 --- a/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts @@ -65,7 +65,7 @@ export class UpyunClient { Cookie: req.cookie } }); - if (res.msg.errors.length > 0) { + if (res.msg?.errors?.length > 0) { throw new Error(JSON.stringify(res.msg)); } if(res.data?.error_code){ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2901b81b..dd3f6e094 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: packages/core/acme-client: dependencies: '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../basic '@peculiar/x509': specifier: ^1.11.0 @@ -210,11 +210,11 @@ importers: packages/core/pipeline: dependencies: '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../basic '@certd/plus-core': - specifier: ^1.36.17 - version: link:../../pro/plus-core + specifier: ^1.36.20 + version: 1.36.20 dayjs: specifier: ^1.11.7 version: 1.11.13 @@ -418,7 +418,7 @@ importers: packages/libs/lib-k8s: dependencies: '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/basic '@kubernetes/client-node': specifier: 0.21.0 @@ -458,17 +458,17 @@ importers: packages/libs/lib-server: dependencies: '@certd/acme-client': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/pipeline '@certd/plus-core': - specifier: ^1.36.17 - version: link:../../pro/plus-core + specifier: ^1.36.20 + version: 1.36.20 '@midwayjs/cache': specifier: ~3.14.0 version: 3.14.0 @@ -610,16 +610,16 @@ importers: packages/plugins/plugin-cert: dependencies: '@certd/acme-client': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../plugin-lib '@google-cloud/publicca': specifier: ^1.3.0 @@ -701,10 +701,10 @@ importers: specifier: ^3.787.0 version: 3.810.0(aws-crt@1.26.2) '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/pipeline '@kubernetes/client-node': specifier: 0.21.0 @@ -792,19 +792,19 @@ importers: packages/pro/commercial-core: dependencies: '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../core/basic '@certd/lib-server': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../libs/lib-server '@certd/pipeline': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../core/pipeline '@certd/plugin-plus': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../plugin-plus '@certd/plus-core': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../plus-core '@midwayjs/core': specifier: ~3.20.3 @@ -889,22 +889,22 @@ importers: specifier: ^1.0.2 version: 1.0.3 '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../core/basic '@certd/lib-k8s': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../libs/lib-k8s '@certd/pipeline': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../plus-core ali-oss: specifier: ^6.21.0 @@ -1007,7 +1007,7 @@ importers: packages/pro/plus-core: dependencies: '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.19 version: link:../../core/basic dayjs: specifier: ^1.11.7 @@ -1088,18 +1088,21 @@ importers: '@ctrl/tinycolor': specifier: ^4.1.0 version: 4.1.0 + '@fast-crud/editor-code': + specifier: ^1.26.6 + version: 1.26.6 '@fast-crud/fast-crud': - specifier: ^1.25.13 - version: 1.25.13(vue@3.5.14(typescript@5.8.3)) + specifier: ^1.26.6 + version: 1.26.6(vue@3.5.14(typescript@5.8.3)) '@fast-crud/fast-extends': - specifier: ^1.25.13 - version: 1.25.13(aws-crt@1.26.2)(vue@3.5.14(typescript@5.8.3)) + specifier: ^1.26.6 + version: 1.26.6(aws-crt@1.26.2)(vue@3.5.14(typescript@5.8.3)) '@fast-crud/ui-antdv4': - specifier: ^1.25.13 - version: 1.25.13 + specifier: ^1.26.6 + version: 1.26.6 '@fast-crud/ui-interface': - specifier: ^1.25.13 - version: 1.25.13 + specifier: ^1.26.6 + version: 1.26.6 '@iconify/tailwind': specifier: ^1.2.0 version: 1.2.0 @@ -1297,17 +1300,17 @@ importers: version: 0.1.3(zod@3.24.4) devDependencies: '@certd/lib-iframe': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../libs/lib-iframe '@certd/pipeline': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/pipeline '@rollup/plugin-commonjs': specifier: ^25.0.7 - version: 25.0.8(rollup@4.40.2) + version: 25.0.8(rollup@4.50.0) '@rollup/plugin-node-resolve': specifier: ^15.2.3 - version: 15.3.1(rollup@4.40.2) + version: 15.3.1(rollup@4.50.0) '@types/chai': specifier: ^4.3.12 version: 4.3.20 @@ -1402,11 +1405,11 @@ importers: specifier: ^5.0.5 version: 5.0.10 rollup: - specifier: ^4.13.0 - version: 4.40.2 + specifier: 4.50.0 + version: 4.50.0 rollup-plugin-visualizer: specifier: ^5.12.0 - version: 5.14.0(rollup@4.40.2) + version: 5.14.0(rollup@4.50.0) stylelint: specifier: ^15.11.0 version: 15.11.0(typescript@5.8.3) @@ -1432,7 +1435,7 @@ importers: specifier: ^1.4.2 version: 1.5.5(vue@3.5.14(typescript@5.8.3)) vite: - specifier: ^5.3.1 + specifier: 5.4.19 version: 5.4.19(@types/node@18.19.100)(less@4.3.0)(terser@5.39.1) vite-plugin-compression: specifier: ^0.5.1 @@ -1483,47 +1486,47 @@ importers: specifier: ^3.705.0 version: 3.810.0(aws-crt@1.26.2) '@certd/acme-client': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/basic '@certd/commercial-core': - specifier: ^1.36.17 - version: link:../../pro/commercial-core + specifier: ^1.36.20 + version: 1.36.20(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3)) '@certd/cv4pve-api-javascript': specifier: ^8.4.2 version: 8.4.2 '@certd/jdcloud': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../libs/lib-jdcloud '@certd/lib-huawei': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../libs/lib-huawei '@certd/lib-k8s': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../libs/lib-k8s '@certd/lib-server': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../libs/lib-server '@certd/midway-flyway-js': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../libs/midway-flyway-js '@certd/pipeline': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.36.17 + specifier: ^1.36.20 version: link:../../plugins/plugin-lib '@certd/plugin-plus': - specifier: ^1.36.17 - version: link:../../pro/plugin-plus + specifier: ^1.36.20 + version: 1.36.20(encoding@0.1.13) '@certd/plus-core': - specifier: ^1.36.17 - version: link:../../pro/plus-core + specifier: ^1.36.20 + version: 1.36.20 '@huaweicloud/huaweicloud-sdk-cdn': specifier: ^3.1.120 version: 3.1.149 @@ -1708,8 +1711,8 @@ importers: specifier: ^1.4.0 version: 1.4.0 tencentcloud-sdk-nodejs: - specifier: ^4.0.983 - version: 4.1.37(encoding@0.1.13) + specifier: ^4.1.112 + version: 4.1.112(encoding@0.1.13) typeorm: specifier: ^0.3.20 version: 0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3)) @@ -2766,9 +2769,18 @@ packages: '@better-scroll/zoom@2.5.1': resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==} + '@certd/commercial-core@1.36.20': + resolution: {integrity: sha512-5l+Kev+y8t1KVPzbTzHVEfXHJtVdBtwIIM8i7VEfui0Mnrq896d7g/kUVFsOVZ1QnPlJnT17qYX8jaP9eSzr8Q==} + '@certd/cv4pve-api-javascript@8.4.2': resolution: {integrity: sha512-udGce7ewrVl4DmZvX+17PjsnqsdDIHEDatr8QP0AVrY2p+8JkaSPW4mXCKiLGf82C9K2+GXgT+qNIqgW7tfF9Q==} + '@certd/plugin-plus@1.36.20': + resolution: {integrity: sha512-aaslvdORKbEnxWXoGHUKOlP5KlD0+OLhK9fVyPBdgu5s1cZ3tOx0lB+Bu2XXZex1fZvEQ6/qn0A9QI55UmIicA==} + + '@certd/plus-core@1.36.20': + resolution: {integrity: sha512-Ji8nsVuNNfDSsSUnL+Rm3kwXt+3WymoMq2UGVNNzJC1ywnCaLkbaBEBawHqnZJLriwt3sidtZy2PYRP6VLo5bA==} + '@certd/vue-js-cron-core@6.0.3': resolution: {integrity: sha512-kqzoAMhYz9j6FGNWEODRYtt4NpUEUwjpkU89z5WVg2tCtOcI5VhwyUGOd8AxiBCRfd6PtXvzuqw85PaOps9wrQ==} @@ -3408,17 +3420,20 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@fast-crud/fast-crud@1.25.13': - resolution: {integrity: sha512-2UDp0Wzsf9CXA9qJnyYjHYOR2usBRPij/8/15ksyFhBPLC8JJgXHFUae0lXqhib6QCV0CYrF8t7REEtxKmkUfw==} + '@fast-crud/editor-code@1.26.6': + resolution: {integrity: sha512-Dq51jO3ACkJJB3eIfyyQ/cU14Sn5jBG5qui2ZTaibs16mpE1cSMLJNl1kk5VXucHeMAlaa5QEJWuuYO/dEKZcA==} - '@fast-crud/fast-extends@1.25.13': - resolution: {integrity: sha512-Hn4p/sdO96KB/+umfrZhgPM3ZbaU78uMamjl+UKhyEB5U42jN45XSEsP4ax1IHFCpvxLvTMnmTzRHqkbgTS18g==} + '@fast-crud/fast-crud@1.26.6': + resolution: {integrity: sha512-m44F0qPUaswEBAn+bUqpjNm/+zn/icsTEpr4rGnDd692eZKzx7uPP1szVFwCc0nZeVteI5hNNAioIKB/6Xns1w==} - '@fast-crud/ui-antdv4@1.25.13': - resolution: {integrity: sha512-3BDfZRGH9rBEYKw6hUyXD1kI9XtUFl9U/dX4PDH98i1tMg3GhUJ51gDZjZ8as9HsMrCE88m8ZUPjjQU/HLVY1w==} + '@fast-crud/fast-extends@1.26.6': + resolution: {integrity: sha512-OHWxN7ZWmE0I0rGyqLjgx9RfZxHakWO68cAX5b6ADwFfFlGR2zfQuj6VjfYayPe6VSt5p3ZoUSsxQgmLbHYnKA==} - '@fast-crud/ui-interface@1.25.13': - resolution: {integrity: sha512-hWjN6j6H2e9YxtqKL+fpls4/TTLZX6TuazjuxE+VoH0EdJF9QRk/D8GLxORGvnRgPABClbQvVPyofb6rPHjbAw==} + '@fast-crud/ui-antdv4@1.26.6': + resolution: {integrity: sha512-VeTt75ejjhIzii5wcQFWY5ugEizLGZhU2cowLNTGskCBmMEAP3GGUwn4TP33dtmQWvTu7FMjwaKtyWnFaiYmuA==} + + '@fast-crud/ui-interface@1.26.6': + resolution: {integrity: sha512-C6lWTWs8Vl76qQjORGbPmOy53clo3rO5KnVX2FhTcu5TTecR9JOc85IcQPSWRnWEn+dCdted2wXR83TgQK41eA==} '@fidm/asn1@1.0.4': resolution: {integrity: sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ==} @@ -4104,103 +4119,108 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.40.2': - resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} + '@rollup/rollup-android-arm-eabi@4.50.0': + resolution: {integrity: sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.40.2': - resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} + '@rollup/rollup-android-arm64@4.50.0': + resolution: {integrity: sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.40.2': - resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} + '@rollup/rollup-darwin-arm64@4.50.0': + resolution: {integrity: sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.40.2': - resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} + '@rollup/rollup-darwin-x64@4.50.0': + resolution: {integrity: sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.40.2': - resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} + '@rollup/rollup-freebsd-arm64@4.50.0': + resolution: {integrity: sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.40.2': - resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} + '@rollup/rollup-freebsd-x64@4.50.0': + resolution: {integrity: sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': - resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} + '@rollup/rollup-linux-arm-gnueabihf@4.50.0': + resolution: {integrity: sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.40.2': - resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} + '@rollup/rollup-linux-arm-musleabihf@4.50.0': + resolution: {integrity: sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.40.2': - resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} + '@rollup/rollup-linux-arm64-gnu@4.50.0': + resolution: {integrity: sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.40.2': - resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} + '@rollup/rollup-linux-arm64-musl@4.50.0': + resolution: {integrity: sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': - resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} + '@rollup/rollup-linux-loongarch64-gnu@4.50.0': + resolution: {integrity: sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': - resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} + '@rollup/rollup-linux-ppc64-gnu@4.50.0': + resolution: {integrity: sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.40.2': - resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} + '@rollup/rollup-linux-riscv64-gnu@4.50.0': + resolution: {integrity: sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.40.2': - resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} + '@rollup/rollup-linux-riscv64-musl@4.50.0': + resolution: {integrity: sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.40.2': - resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} + '@rollup/rollup-linux-s390x-gnu@4.50.0': + resolution: {integrity: sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.40.2': - resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} + '@rollup/rollup-linux-x64-gnu@4.50.0': + resolution: {integrity: sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.40.2': - resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} + '@rollup/rollup-linux-x64-musl@4.50.0': + resolution: {integrity: sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.40.2': - resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} + '@rollup/rollup-openharmony-arm64@4.50.0': + resolution: {integrity: sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.50.0': + resolution: {integrity: sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.40.2': - resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} + '@rollup/rollup-win32-ia32-msvc@4.50.0': + resolution: {integrity: sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.40.2': - resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} + '@rollup/rollup-win32-x64-msvc@4.50.0': + resolution: {integrity: sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==} cpu: [x64] os: [win32] @@ -4738,6 +4758,9 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/event-emitter@0.3.5': resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==} @@ -11527,8 +11550,8 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true - rollup@4.40.2: - resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} + rollup@4.50.0: + resolution: {integrity: sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -12242,6 +12265,10 @@ packages: temp-path@1.0.0: resolution: {integrity: sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==} + tencentcloud-sdk-nodejs@4.1.112: + resolution: {integrity: sha512-30Ju53bTd3OjMRwfieDvEYvjHhHVg2Eqc0EM7H8gKEWq0y3xMEdrxgYRrjhIkRo5Doc5YEOl6uUJUCfeT7dmFA==} + engines: {node: '>=10'} + tencentcloud-sdk-nodejs@4.1.37: resolution: {integrity: sha512-rQV/jaUHGsB71JarqFdDJTl5tC2kIavgSUqlh8JoOUNpfJoAD4qHm1GLdDTUTEPKhv3qF9Is3qo6lj4cG9kKuw==} engines: {node: '>=10'} @@ -15448,12 +15475,84 @@ snapshots: dependencies: '@better-scroll/core': 2.5.1 + '@certd/commercial-core@1.36.20(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))': + dependencies: + '@certd/basic': link:packages/core/basic + '@certd/lib-server': link:packages/libs/lib-server + '@certd/pipeline': link:packages/core/pipeline + '@certd/plugin-plus': 1.36.20(encoding@0.1.13) + '@certd/plus-core': 1.36.20 + '@midwayjs/core': 3.20.4 + '@midwayjs/koa': 3.20.5 + '@midwayjs/logger': 3.4.2 + '@midwayjs/typeorm': 3.20.4 + alipay-sdk: 4.14.0 + dayjs: 1.11.13 + typeorm: 0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3)) + wechatpay-node-v3: 2.2.1 + transitivePeerDependencies: + - '@google-cloud/spanner' + - '@sap/hana-client' + - babel-plugin-macros + - better-sqlite3 + - encoding + - hdb-pool + - ioredis + - mongodb + - mssql + - mysql2 + - oracledb + - pg + - pg-native + - pg-query-stream + - proxy-agent + - redis + - reflect-metadata + - sql.js + - sqlite3 + - supports-color + - ts-node + - typeorm-aurora-data-api-driver + '@certd/cv4pve-api-javascript@8.4.2': dependencies: debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color + '@certd/plugin-plus@1.36.20(encoding@0.1.13)': + dependencies: + '@alicloud/pop-core': 1.8.0 + '@baiducloud/sdk': 1.0.3 + '@certd/basic': link:packages/core/basic + '@certd/lib-k8s': link:packages/libs/lib-k8s + '@certd/pipeline': link:packages/core/pipeline + '@certd/plugin-cert': link:packages/plugins/plugin-cert + '@certd/plugin-lib': link:packages/plugins/plugin-lib + '@certd/plus-core': 1.36.20 + ali-oss: 6.23.0 + baidu-aip-sdk: 4.16.16 + basic-ftp: 5.0.5 + cos-nodejs-sdk-v5: 2.14.7 + crypto-js: 4.2.0 + dayjs: 1.11.13 + form-data: 4.0.2 + https-proxy-agent: 7.0.6 + js-yaml: 4.1.0 + jsencrypt: 3.3.2 + jsrsasign: 11.1.0 + qiniu: 7.14.0 + tencentcloud-sdk-nodejs: 4.1.112(encoding@0.1.13) + transitivePeerDependencies: + - encoding + - proxy-agent + - supports-color + + '@certd/plus-core@1.36.20': + dependencies: + '@certd/basic': link:packages/core/basic + dayjs: 1.11.13 + '@certd/vue-js-cron-core@6.0.3': dependencies: mustache: 4.2.0 @@ -15965,7 +16064,14 @@ snapshots: '@eslint/js@8.57.0': {} - '@fast-crud/fast-crud@1.25.13(vue@3.5.14(typescript@5.8.3))': + '@fast-crud/editor-code@1.26.6': + dependencies: + js-yaml: 4.1.0 + lodash-es: 4.17.21 + monaco-editor: 0.52.2 + monaco-yaml: 5.4.0(monaco-editor@0.52.2) + + '@fast-crud/fast-crud@1.26.6(vue@3.5.14(typescript@5.8.3))': dependencies: '@iconify/types': 2.0.0 file-saver: 2.0.5 @@ -15975,7 +16081,7 @@ snapshots: transitivePeerDependencies: - vue - '@fast-crud/fast-extends@1.25.13(aws-crt@1.26.2)(vue@3.5.14(typescript@5.8.3))': + '@fast-crud/fast-extends@1.26.6(aws-crt@1.26.2)(vue@3.5.14(typescript@5.8.3))': dependencies: '@aws-sdk/client-s3': 3.810.0(aws-crt@1.26.2) '@aws-sdk/s3-request-presigner': 3.810.0 @@ -15992,8 +16098,6 @@ snapshots: js-yaml: 4.1.0 jsoneditor: 9.10.5 lodash-es: 4.17.21 - monaco-editor: 0.52.2 - monaco-yaml: 5.4.0(monaco-editor@0.52.2) object-assign: 4.1.1 qiniu-js: 3.4.1 quill: 1.3.7 @@ -16007,9 +16111,9 @@ snapshots: - utf-8-validate - vue - '@fast-crud/ui-antdv4@1.25.13': {} + '@fast-crud/ui-antdv4@1.26.6': {} - '@fast-crud/ui-interface@1.25.13': + '@fast-crud/ui-interface@1.26.6': dependencies: lodash-es: 4.17.21 @@ -17056,16 +17160,16 @@ snapshots: optionalDependencies: rollup: 3.29.5 - '@rollup/plugin-commonjs@25.0.8(rollup@4.40.2)': + '@rollup/plugin-commonjs@25.0.8(rollup@4.50.0)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.40.2) + '@rollup/pluginutils': 5.1.4(rollup@4.50.0) commondir: 1.0.1 estree-walker: 2.0.2 glob: 8.1.0 is-reference: 1.2.1 magic-string: 0.30.17 optionalDependencies: - rollup: 4.40.2 + rollup: 4.50.0 '@rollup/plugin-json@6.1.0(rollup@3.29.5)': dependencies: @@ -17083,15 +17187,15 @@ snapshots: optionalDependencies: rollup: 3.29.5 - '@rollup/plugin-node-resolve@15.3.1(rollup@4.40.2)': + '@rollup/plugin-node-resolve@15.3.1(rollup@4.50.0)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.40.2) + '@rollup/pluginutils': 5.1.4(rollup@4.50.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.10 optionalDependencies: - rollup: 4.40.2 + rollup: 4.50.0 '@rollup/plugin-terser@0.4.4(rollup@3.29.5)': dependencies: @@ -17123,72 +17227,75 @@ snapshots: optionalDependencies: rollup: 3.29.5 - '@rollup/pluginutils@5.1.4(rollup@4.40.2)': + '@rollup/pluginutils@5.1.4(rollup@4.50.0)': dependencies: '@types/estree': 1.0.7 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.40.2 + rollup: 4.50.0 - '@rollup/rollup-android-arm-eabi@4.40.2': + '@rollup/rollup-android-arm-eabi@4.50.0': optional: true - '@rollup/rollup-android-arm64@4.40.2': + '@rollup/rollup-android-arm64@4.50.0': optional: true - '@rollup/rollup-darwin-arm64@4.40.2': + '@rollup/rollup-darwin-arm64@4.50.0': optional: true - '@rollup/rollup-darwin-x64@4.40.2': + '@rollup/rollup-darwin-x64@4.50.0': optional: true - '@rollup/rollup-freebsd-arm64@4.40.2': + '@rollup/rollup-freebsd-arm64@4.50.0': optional: true - '@rollup/rollup-freebsd-x64@4.40.2': + '@rollup/rollup-freebsd-x64@4.50.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + '@rollup/rollup-linux-arm-gnueabihf@4.50.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.40.2': + '@rollup/rollup-linux-arm-musleabihf@4.50.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.40.2': + '@rollup/rollup-linux-arm64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.40.2': + '@rollup/rollup-linux-arm64-musl@4.50.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + '@rollup/rollup-linux-loongarch64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + '@rollup/rollup-linux-ppc64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.40.2': + '@rollup/rollup-linux-riscv64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.40.2': + '@rollup/rollup-linux-riscv64-musl@4.50.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.40.2': + '@rollup/rollup-linux-s390x-gnu@4.50.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.40.2': + '@rollup/rollup-linux-x64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-x64-musl@4.40.2': + '@rollup/rollup-linux-x64-musl@4.50.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.40.2': + '@rollup/rollup-openharmony-arm64@4.50.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.40.2': + '@rollup/rollup-win32-arm64-msvc@4.50.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.40.2': + '@rollup/rollup-win32-ia32-msvc@4.50.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.50.0': optional: true '@rtsao/scc@1.1.0': {} @@ -17947,6 +18054,8 @@ snapshots: '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} + '@types/event-emitter@0.3.5': {} '@types/express-serve-static-core@5.0.6': @@ -26110,43 +26219,44 @@ snapshots: optionalDependencies: rollup: 3.29.5 - rollup-plugin-visualizer@5.14.0(rollup@4.40.2): + rollup-plugin-visualizer@5.14.0(rollup@4.50.0): dependencies: open: 8.4.2 picomatch: 4.0.2 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.40.2 + rollup: 4.50.0 rollup@3.29.5: optionalDependencies: fsevents: 2.3.3 - rollup@4.40.2: + rollup@4.50.0: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.40.2 - '@rollup/rollup-android-arm64': 4.40.2 - '@rollup/rollup-darwin-arm64': 4.40.2 - '@rollup/rollup-darwin-x64': 4.40.2 - '@rollup/rollup-freebsd-arm64': 4.40.2 - '@rollup/rollup-freebsd-x64': 4.40.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 - '@rollup/rollup-linux-arm-musleabihf': 4.40.2 - '@rollup/rollup-linux-arm64-gnu': 4.40.2 - '@rollup/rollup-linux-arm64-musl': 4.40.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-musl': 4.40.2 - '@rollup/rollup-linux-s390x-gnu': 4.40.2 - '@rollup/rollup-linux-x64-gnu': 4.40.2 - '@rollup/rollup-linux-x64-musl': 4.40.2 - '@rollup/rollup-win32-arm64-msvc': 4.40.2 - '@rollup/rollup-win32-ia32-msvc': 4.40.2 - '@rollup/rollup-win32-x64-msvc': 4.40.2 + '@rollup/rollup-android-arm-eabi': 4.50.0 + '@rollup/rollup-android-arm64': 4.50.0 + '@rollup/rollup-darwin-arm64': 4.50.0 + '@rollup/rollup-darwin-x64': 4.50.0 + '@rollup/rollup-freebsd-arm64': 4.50.0 + '@rollup/rollup-freebsd-x64': 4.50.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.50.0 + '@rollup/rollup-linux-arm-musleabihf': 4.50.0 + '@rollup/rollup-linux-arm64-gnu': 4.50.0 + '@rollup/rollup-linux-arm64-musl': 4.50.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.50.0 + '@rollup/rollup-linux-ppc64-gnu': 4.50.0 + '@rollup/rollup-linux-riscv64-gnu': 4.50.0 + '@rollup/rollup-linux-riscv64-musl': 4.50.0 + '@rollup/rollup-linux-s390x-gnu': 4.50.0 + '@rollup/rollup-linux-x64-gnu': 4.50.0 + '@rollup/rollup-linux-x64-musl': 4.50.0 + '@rollup/rollup-openharmony-arm64': 4.50.0 + '@rollup/rollup-win32-arm64-msvc': 4.50.0 + '@rollup/rollup-win32-ia32-msvc': 4.50.0 + '@rollup/rollup-win32-x64-msvc': 4.50.0 fsevents: 2.3.3 run-async@2.4.1: {} @@ -27035,6 +27145,20 @@ snapshots: temp-path@1.0.0: {} + tencentcloud-sdk-nodejs@4.1.112(encoding@0.1.13): + dependencies: + form-data: 3.0.3 + get-stream: 6.0.1 + https-proxy-agent: 5.0.1 + is-stream: 2.0.1 + json-bigint: 1.0.0 + node-fetch: 2.7.0(encoding@0.1.13) + tslib: 1.13.0 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + tencentcloud-sdk-nodejs@4.1.37(encoding@0.1.13): dependencies: form-data: 3.0.3 @@ -27788,7 +27912,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.5.6 - rollup: 4.40.2 + rollup: 4.50.0 optionalDependencies: '@types/node': 18.19.100 fsevents: 2.3.3 @@ -27801,7 +27925,7 @@ snapshots: fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 postcss: 8.5.6 - rollup: 4.40.2 + rollup: 4.50.0 tinyglobby: 0.2.13 optionalDependencies: '@types/node': 22.15.18 diff --git a/start.sh b/start.sh index 78bb46ce0..1f3bb7ca9 100755 --- a/start.sh +++ b/start.sh @@ -25,30 +25,30 @@ done echo "安装pnpm, 前提是已经安装了nodejs" -npm install -g pnpm --registry https://registry.npmmirror.com +sudo npm install -g pnpm --registry https://registry.npmmirror.com echo "安装依赖" -pnpm install --registry https://registry.npmmirror.com +sudo pnpm install --registry https://registry.npmmirror.com echo "开始构建" echo "构建certd-client" export NODE_OPTIONS=--max-old-space-size=32768 cd packages/ui/certd-client -pnpm run build +sudo -E pnpm run build cp -r dist/* ../certd-server/public echo "构建certd-server" cd ../certd-server -pnpm run build +sudo -E pnpm run build echo "构建完成" echo "启动服务" # 前台运行 if [ $confirmNohup != "y" ]; then echo "当前运行模式为前台运行,ctrl+c或者关闭ssh将会停止运行" - pnpm run start + sudo pnpm run start else echo "当前运行模式为后台运行,可以通过tail -f ./certd.log 命令查看日志" - nohup pnpm run start > certd.log & + nohup sudo pnpm run start > certd.log & fi