mirror of
https://github.com/certd/certd.git
synced 2026-04-16 05:50:50 +08:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8abe62886a | ||
|
|
78cc9cffe4 | ||
|
|
59a5dd713f | ||
|
|
a39024ff03 | ||
|
|
72bfbd93a8 | ||
|
|
c9a3e3d9d2 | ||
|
|
8387708901 | ||
|
|
b565b4b3b9 | ||
|
|
893dcd4f24 | ||
|
|
d613aa8f3e | ||
|
|
5750bb7067 | ||
|
|
0e07ae6ce8 | ||
|
|
02b6351e13 | ||
|
|
78367af830 | ||
|
|
dc05cd481f | ||
|
|
7daad5477a | ||
|
|
45cdfbfae8 | ||
|
|
3fb5c38571 | ||
|
|
59f80ebc47 | ||
|
|
198a97b00c | ||
|
|
3ea4e917e8 | ||
|
|
60ad077172 | ||
|
|
356ad28e41 | ||
|
|
e241141220 | ||
|
|
14bb1b467a | ||
|
|
2bbea6fd3f | ||
|
|
48aef25b3f | ||
|
|
8e50e5dee3 | ||
|
|
d5d54d4d3b | ||
|
|
412e8a32dd | ||
|
|
0f82cf409b | ||
|
|
79df39acab | ||
|
|
8786bae7dc | ||
|
|
4b3f8ca361 | ||
|
|
03183218f7 | ||
|
|
95b6db57e1 | ||
|
|
bbe0c2457b | ||
|
|
c894c53e69 |
24
CHANGELOG.md
24
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.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 上传商用证书,直接粘贴文本报错的问题;修复无法上传ec加密证书的bug ([5750bb7](https://github.com/certd/certd/commit/5750bb706779da274d8e7a87e71416cb64d2df79))
|
||||
* 修复下载证书时提示token已过期的问题 ([0e07ae6](https://github.com/certd/certd/commit/0e07ae6ce84dcb9279d3c44060d621566afa593c))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 更新license时同时绑定url ([78367af](https://github.com/certd/certd/commit/78367af8307f801e778c76d49f0918c21ffe032f))
|
||||
* 切换到不同的分组后再打开创建对话框,会自动选择分组 ([893dcd4](https://github.com/certd/certd/commit/893dcd4f2487891199ed3e5a3d47a79a75efc942))
|
||||
* 新增部署到火山引擎ALB/CLB、上传到证书中心 ([c9a3e3d](https://github.com/certd/certd/commit/c9a3e3d9d26f964c7af7b56667936f1414fbf42a))
|
||||
* 优化/api缓存为0 ([dc05cd4](https://github.com/certd/certd/commit/dc05cd481f186b13375192be965000e6b4b429a5))
|
||||
* 优化华为cdn插件引用ccm证书 ([b565b4b](https://github.com/certd/certd/commit/b565b4b3b919b71b98ea2517670bc1ef00e00dc9))
|
||||
* 优化证书流水线创建,支持选择分组 ([d613aa8](https://github.com/certd/certd/commit/d613aa8f3e85d8dc475ef1b62d49394ce7fd7d24))
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 登录支持双重认证 ([48aef25](https://github.com/certd/certd/commit/48aef25b3f6499d674ca4e4ef16f4c62399fb735))
|
||||
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
|
||||
* 优化部署到华为云CDN,支持先上传到ccm,再使用证书id部署,修复offline状态下导致部署报错的bug ([79df39a](https://github.com/certd/certd/commit/79df39acabab10ae7e1864dadcdc186bb007a3c5))
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
114
README.md
114
README.md
@@ -9,7 +9,7 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
|
||||
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
|
||||
|
||||
* 全自动申请证书(支持所有注册商注册的域名)
|
||||
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等,目前已支持60+部署插件)
|
||||
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等70+部署插件)
|
||||
* 支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
|
||||
* 支持通配符域名/泛域名,支持多个域名打到一个证书上,支持pem、pfx、der、jks等多种证书格式
|
||||
* 邮件通知、webhook通知
|
||||
@@ -26,7 +26,7 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
|
||||
> 关于证书续期:
|
||||
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
||||
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
||||
|
||||
>* 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
||||
|
||||
## 二、在线体验
|
||||
|
||||
@@ -65,7 +65,7 @@ https://certd.handfree.work/
|
||||
-------> [点我查看详细使用步骤演示](./step.md) <--------
|
||||
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
|
||||
更多教程请访问文档网站 [certd.docmirror.cn](https://certd.docmirror.cn/)
|
||||
更多教程请访问官方文档 [certd.docmirror.cn](https://certd.docmirror.cn/guide/)
|
||||
|
||||
|
||||
|
||||
@@ -75,10 +75,10 @@ https://certd.handfree.work/
|
||||
|
||||
您可以根据实际情况从如下方式中选择一种方式进行私有化部署:
|
||||
|
||||
1. [宝塔面板方式部署](https://certd.docmirror.cn/guide/install/docker/)
|
||||
2. [1Panel面板方式部署](https://certd.docmirror.cn/guide/install/1panel/)
|
||||
3. [Docker方式部署](https://certd.docmirror.cn/guide/install/docker/)
|
||||
4. [源码方式部署](https://certd.docmirror.cn/guide/install/source/)
|
||||
1. [宝塔面板方式部署 推荐](https://certd.docmirror.cn/guide/install/docker/)
|
||||
2. [1Panel面板方式部署 推荐](https://certd.docmirror.cn/guide/install/1panel/)
|
||||
3. [Docker方式部署 推荐](https://certd.docmirror.cn/guide/install/docker/)
|
||||
4. [源码方式部署 不建议](https://certd.docmirror.cn/guide/install/source/)
|
||||
|
||||
#### Docker镜像说明:
|
||||
* 国内镜像地址:
|
||||
@@ -104,86 +104,20 @@ https://certd.handfree.work/
|
||||
> * 请务必做好服务器本身的安全防护,防止数据库泄露
|
||||
> * 请务必做好数据备份,避免数据丢失
|
||||
|
||||
## 五、 升级
|
||||
|
||||
### docker-compose方式部署
|
||||
#### 1. 如果使用固定版本号
|
||||
1. 修改`docker-compose.yaml`中的镜像版本号
|
||||
2. 运行`docker compose up -d` 即可
|
||||
|
||||
#### 2. 如果需要使用最新版本
|
||||
```shell
|
||||
#重新拉取镜像
|
||||
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||
# 重新启动容器
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
> 数据默认存在`/data/certd`目录下,不用担心数据丢失
|
||||
|
||||
### 自动升级(仅限尝鲜建议非生产使用)
|
||||
```yaml
|
||||
version: '3.3'
|
||||
services:
|
||||
certd:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||
container_name: certd
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /data/certd:/app/data
|
||||
ports:
|
||||
- "7001:7001"
|
||||
- "7002:7002"
|
||||
environment:
|
||||
- certd_system_resetAdminPasswd=false
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
|
||||
certd-updater: # 添加 Watchtower 服务
|
||||
image: containrrr/watchtower:latest
|
||||
container_name: certd-updater
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# 配置 自动更新
|
||||
environment:
|
||||
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
|
||||
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
|
||||
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
||||
- WATCHTOWER_POLL_INTERVAL=300 # 每 5 分钟检查一次更新
|
||||
|
||||
```
|
||||
|
||||
### 其他部署方式升级方法
|
||||
请参考 https://certd.docmirror.cn/guide/install/upgrade.html
|
||||
|
||||
|
||||
|
||||
### 更新日志:
|
||||
## 五、更多帮助
|
||||
请访问官方文档:[https://certd.docmirror.cn/](https://certd.docmirror.cn/guide/)
|
||||
|
||||
[CHANGELOG](./CHANGELOG.md)
|
||||
* 升级方法:[升级方法](https://certd.docmirror.cn/guide/install/upgrade/)
|
||||
* 常见问题:[忘记密码](https://certd.docmirror.cn/guide/use/forgotpasswd/)
|
||||
* 多数据库:[多数据库配置](https://certd.docmirror.cn/guide/install/database/)
|
||||
* 站点安全:[站点安全特性](https://certd.docmirror.cn/guide/feature/safe/)
|
||||
* 更新日志:[CHANGELOG](./CHANGELOG.md)
|
||||
|
||||
|
||||
## 六、一些说明
|
||||
* 本项目ssl证书提供商为letencrypt/Google/ZeroSSL
|
||||
* 申请过程遵循acme协议
|
||||
* 证书续期:
|
||||
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
||||
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
||||
* 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
||||
* 设置每天自动运行,当证书过期前35天,会自动重新申请证书并部署
|
||||
|
||||
|
||||
## 七、不同平台的设置说明
|
||||
|
||||
* 已迁移到新的文档网站,请到常见问题章节查看
|
||||
* [最新文档站链接 https://certd.docmirror.cn](https://certd.docmirror.cn/)
|
||||
|
||||
## 八、问题处理
|
||||
### 7.1 忘记管理员密码
|
||||
[重置管理员密码方法](https://certd.docmirror.cn/guide/use/forgotpasswd/)
|
||||
|
||||
## 九、联系作者
|
||||
## 六、联系作者
|
||||
如有疑问,欢迎加入群聊(请备注certd)
|
||||
|
||||
| 加群 | 微信群 | QQ群 |
|
||||
@@ -197,7 +131,7 @@ services:
|
||||
| 二维码 | <img height="230" src="./docs/guide/contact/images/me.png"> |
|
||||
|
||||
|
||||
## 十、捐赠
|
||||
## 七、捐赠
|
||||
************************
|
||||
支持开源,为爱发电,我已入驻爱发电
|
||||
https://afdian.com/a/greper
|
||||
@@ -221,34 +155,26 @@ https://afdian.com/a/greper
|
||||
|
||||
************************
|
||||
|
||||
## 十一、贡献代码
|
||||
## 八、贡献代码
|
||||
|
||||
1. 本地开发 [贡献插件](https://certd.docmirror.cn/guide/development/)
|
||||
1. 本地开发请参考 [贡献插件向导](https://certd.docmirror.cn/guide/development/)
|
||||
2. 作为贡献者,代表您同意您贡献的代码如下许可:
|
||||
1. 可以调整开源协议以使其更严格或更宽松。
|
||||
2. 可以用于商业用途。
|
||||
|
||||
|
||||
|
||||
## 十二、 开源许可
|
||||
## 九、 开源许可
|
||||
* 本项目遵循 GNU Affero General Public License(AGPL)开源协议。
|
||||
* 允许个人和公司内部自由使用、复制、修改和分发本项目,未获得商业授权情况下禁止任何形式的商业用途
|
||||
* 未获得商业授权情况下,禁止任何对logo、版权信息及授权许可相关代码的修改。
|
||||
* 如需商业授权,请联系作者。
|
||||
|
||||
|
||||
## 十三、我的其他项目(求Star)
|
||||
## 十、我的其他项目(求Star)
|
||||
|
||||
| 项目名称 | stars | 项目描述 |
|
||||
|---------------------------------------------------------|-------------------------------------------------------------------------------------------------------|-----------------------------------|
|
||||
| [袖手AI](https://ai.handsfree.work/) | | 袖手GPT,国内可用,无需FQ,每日免费额度 |
|
||||
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
|
||||
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具,无需FQ,解决github无法访问的问题 |
|
||||
|
||||
|
||||
|
||||
## 十四、更新日志
|
||||
|
||||
更新日志:[CHANGELOG](./CHANGELOG.md)
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
22:26
|
||||
23:37
|
||||
|
||||
@@ -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.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 登录支持双重认证 ([48aef25](https://github.com/certd/certd/commit/48aef25b3f6499d674ca4e4ef16f4c62399fb735))
|
||||
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
|
||||
* 优化部署到华为云CDN,支持先上传到ccm,再使用证书id部署,修复offline状态下导致部署报错的bug ([79df39a](https://github.com/certd/certd/commit/79df39acabab10ae7e1864dadcdc186bb007a3c5))
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 补充类型断言 ([2143dff](https://github.com/certd/certd/commit/2143dff2ae96e6a78bef9f0498e36f8cd9e6941f))
|
||||
* 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题 ([32c714d](https://github.com/certd/certd/commit/32c714d1b6e68c71a74a7452115040c87ac4bfdc))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件支持导入导出 ([cf8abb4](https://github.com/certd/certd/commit/cf8abb45282070c8ba91469f93fd379fabf1f74a))
|
||||
* 支持上传证书到华为云CCM ([cfd3b66](https://github.com/certd/certd/commit/cfd3b66be9ebf53a26693057e70ed60c3f116be9))
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
BIN
docs/guide/feature/safe/images/2fa.png
Normal file
BIN
docs/guide/feature/safe/images/2fa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
@@ -22,9 +22,11 @@ Certd 存储了证书以及授权等敏感数据,所以需要严格保障安
|
||||
* [站点隐藏设置说明](./hidden/)
|
||||

|
||||
|
||||
## 4、登录二次验证
|
||||
## 4、登录双重验证
|
||||
|
||||
待实现
|
||||
支持2FA双重认证
|
||||
|
||||

|
||||
|
||||
## 5、数据库自动备份【建议开启】
|
||||
* [自动备份设置说明](../../use/backup/)
|
||||
|
||||
BIN
docs/guide/install/baota/images/network.png
Normal file
BIN
docs/guide/install/baota/images/network.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
@@ -13,7 +13,7 @@
|
||||
|
||||
#### 2.1 应用商店一键部署【推荐】
|
||||
|
||||
* 在应用商店中找到`certd`(要先点右上角更新应用)
|
||||
* 在宝塔Docker应用商店中找到`certd`(要先点右上角更新应用)
|
||||
* 点击安装,配置域名等基本信息即可完成安装
|
||||
|
||||
> 需要宝塔9.2.0及以上版本才支持
|
||||
@@ -70,3 +70,12 @@ admin/123456
|
||||
## 五、备份恢复
|
||||
|
||||
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
||||
|
||||
|
||||
## 六、宝塔部署相关问题排查
|
||||
|
||||
### 1. 无法访问Certd
|
||||
1. 确认服务器的安全规则,是否放开了对应端口
|
||||
2. 确认宝塔防火墙是否放开对应端口
|
||||
3. 尝试将Certd容器加入宝塔的`bridge`网络
|
||||

|
||||
@@ -1,5 +1,6 @@
|
||||
# 数据库自动备份
|
||||
|
||||
# 数据库备份
|
||||
* 两种备份方法: 1、手动备份 2、自动备份
|
||||
* 本文仅限sqlite数据库。
|
||||
## 一、手动备份
|
||||
数据库文件根据不同的部署方式保存的位置不一样,您可以手动复制出来进行备份
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.33.4"
|
||||
"version": "1.33.6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/publishlab/node-acme-client/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.33.5](https://github.com/publishlab/node-acme-client/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.33.4](https://github.com/publishlab/node-acme-client/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"type": "module",
|
||||
"module": "scr/index.js",
|
||||
"main": "src/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/basic": "^1.33.6",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.7.2",
|
||||
@@ -67,5 +67,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
@@ -1 +1 @@
|
||||
23:45
|
||||
00:04
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -44,5 +44,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { customAlphabet } from "nanoid";
|
||||
|
||||
export const randomNumber = customAlphabet("1234567890", 4);
|
||||
export const simpleNanoId = customAlphabet("1234567890abcdefghijklmopqrstuvwxyz", 12);
|
||||
export const simpleNanoId = customAlphabet("1234567890abcdefghijklmopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ", 12);
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -16,8 +16,8 @@
|
||||
"test": "mocha --loader=ts-node/esm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/plus-core": "^1.33.4",
|
||||
"@certd/basic": "^1.33.6",
|
||||
"@certd/plus-core": "^1.33.6",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
@@ -43,5 +43,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
@@ -23,5 +23,5 @@
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 更新license时同时绑定url ([78367af](https://github.com/certd/certd/commit/78367af8307f801e778c76d49f0918c21ffe032f))
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -30,5 +30,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export class IframeClient {
|
||||
return window.self !== window.top;
|
||||
}
|
||||
|
||||
register<T = any>(action: string, handler: (data: IframeMessageData<T>) => Promise<void>) {
|
||||
register<T = any>(action: string, handler: (data: IframeMessageData<T>) => Promise<any>) {
|
||||
this.handlers[action] = handler;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
@@ -60,5 +60,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -16,7 +16,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/basic": "^1.33.6",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -31,5 +31,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 登录支持双重认证 ([48aef25](https://github.com/certd/certd/commit/48aef25b3f6499d674ca4e4ef16f4c62399fb735))
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -27,10 +27,10 @@
|
||||
],
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.33.4",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/plus-core": "^1.33.4",
|
||||
"@certd/acme-client": "^1.33.6",
|
||||
"@certd/basic": "^1.33.6",
|
||||
"@certd/pipeline": "^1.33.6",
|
||||
"@certd/plus-core": "^1.33.6",
|
||||
"@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": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ export const Constants = {
|
||||
code: 10010,
|
||||
message: '站点已关闭',
|
||||
},
|
||||
need2fa:{
|
||||
code: 10020,
|
||||
message: '需要2FA认证',
|
||||
},
|
||||
openKeyError: {
|
||||
code: 20000,
|
||||
message: 'ApiToken错误',
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { TextException } from "./common-exception.js";
|
||||
/**
|
||||
* 授权异常
|
||||
*/
|
||||
export class AuthException extends BaseException {
|
||||
constructor(message) {
|
||||
constructor(message?:string) {
|
||||
super('AuthException', Constants.res.auth.code, message ? message : Constants.res.auth.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Need2FAException extends TextException {
|
||||
constructor(message:string,data:any) {
|
||||
super('Need2FAException', Constants.res.need2fa.code, message ? message : Constants.res.need2fa.message,data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
*/
|
||||
export class BaseException extends Error {
|
||||
code: number;
|
||||
constructor(name, code, message) {
|
||||
data?:any
|
||||
constructor(name, code, message,data?:any) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
|
||||
/**
|
||||
* 通用异常
|
||||
*/
|
||||
export class CommonException extends BaseException {
|
||||
constructor(message) {
|
||||
super('CommonException', Constants.res.error.code, message ? message : Constants.res.error.message);
|
||||
super("CommonException", Constants.res.error.code, message ? message : Constants.res.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeException extends BaseException {
|
||||
constructor(res: { code: number; message: string }) {
|
||||
super('CodeException', res.code, res.message);
|
||||
super("CodeException", res.code, res.message);
|
||||
}
|
||||
}
|
||||
|
||||
export class TextException extends BaseException {
|
||||
constructor(name, code,message, data?) {
|
||||
super(name, code, message, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@ export class Result<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
|
||||
constructor(code, msg, data?) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
static error(code = 1, msg) {
|
||||
return new Result(code, msg);
|
||||
static error(code = 1, msg, data?: any) {
|
||||
return new Result(code, msg, data);
|
||||
}
|
||||
|
||||
static success(msg, data?) {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -46,5 +46,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -15,10 +15,10 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.33.4",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/plugin-lib": "^1.33.4",
|
||||
"@certd/acme-client": "^1.33.6",
|
||||
"@certd/basic": "^1.33.6",
|
||||
"@certd/pipeline": "^1.33.6",
|
||||
"@certd/plugin-lib": "^1.33.6",
|
||||
"@google-cloud/publicca": "^1.3.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -41,5 +41,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -16,8 +16,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/basic": "^1.33.6",
|
||||
"@certd/pipeline": "^1.33.6",
|
||||
"@kubernetes/client-node": "0.21.0",
|
||||
"ali-oss": "^6.21.0",
|
||||
"basic-ftp": "^5.0.5",
|
||||
@@ -48,5 +48,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
"gitHead": "198a97b00c75219ea8efdc6db4676158506a07c1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,27 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 上传商用证书,直接粘贴文本报错的问题;修复无法上传ec加密证书的bug ([5750bb7](https://github.com/certd/certd/commit/5750bb706779da274d8e7a87e71416cb64d2df79))
|
||||
* 修复下载证书时提示token已过期的问题 ([0e07ae6](https://github.com/certd/certd/commit/0e07ae6ce84dcb9279d3c44060d621566afa593c))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 更新license时同时绑定url ([78367af](https://github.com/certd/certd/commit/78367af8307f801e778c76d49f0918c21ffe032f))
|
||||
* 切换到不同的分组后再打开创建对话框,会自动选择分组 ([893dcd4](https://github.com/certd/certd/commit/893dcd4f2487891199ed3e5a3d47a79a75efc942))
|
||||
* 优化/api缓存为0 ([dc05cd4](https://github.com/certd/certd/commit/dc05cd481f186b13375192be965000e6b4b429a5))
|
||||
* 优化证书流水线创建,支持选择分组 ([d613aa8](https://github.com/certd/certd/commit/d613aa8f3e85d8dc475ef1b62d49394ce7fd7d24))
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 登录支持双重认证 ([48aef25](https://github.com/certd/certd/commit/48aef25b3f6499d674ca4e4ef16f4c62399fb735))
|
||||
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -101,8 +101,8 @@
|
||||
"zod-defaults": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/lib-iframe": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/lib-iframe": "^1.33.6",
|
||||
"@certd/pipeline": "^1.33.6",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -3,6 +3,17 @@ import { get } from "lodash-es";
|
||||
import { errorLog, errorCreate } from "./tools";
|
||||
import { env } from "/src/utils/util.env";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
|
||||
export class CodeError extends Error {
|
||||
code: number;
|
||||
data?: any;
|
||||
constructor(message: string, code: number, data?: any) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建请求实例
|
||||
*/
|
||||
@@ -38,32 +49,32 @@ function createService() {
|
||||
}
|
||||
|
||||
// 这个状态码是和后端约定的
|
||||
const { code } = dataAxios;
|
||||
// 根据 code 进行判断
|
||||
if (code === undefined) {
|
||||
if (dataAxios?.code === undefined) {
|
||||
// 如果没有 code 代表这不是项目后端开发的接口
|
||||
errorCreate(`非标准返回:${dataAxios}, ${response.config.url}`);
|
||||
return dataAxios;
|
||||
} else {
|
||||
// 有 code 代表这是一个后端接口 可以进行进一步的判断
|
||||
switch (code) {
|
||||
case 0:
|
||||
// [ 示例 ] code === 0 代表没有错误
|
||||
}
|
||||
const { code } = dataAxios;
|
||||
// 有 code 代表这是一个后端接口 可以进行进一步的判断
|
||||
switch (code) {
|
||||
case 0:
|
||||
// [ 示例 ] code === 0 代表没有错误
|
||||
// @ts-ignore
|
||||
return dataAxios?.data;
|
||||
default:
|
||||
// 不是正确的 code
|
||||
const errorMessage = dataAxios.msg || dataAxios.message || "未知错误";
|
||||
// @ts-ignore
|
||||
if (response?.config?.onError) {
|
||||
const err = new CodeError(errorMessage, dataAxios.code, dataAxios.data);
|
||||
// @ts-ignore
|
||||
return dataAxios.data;
|
||||
default:
|
||||
// 不是正确的 code
|
||||
const errorMessage = dataAxios.msg || dataAxios.message || "未知错误";
|
||||
// @ts-ignore
|
||||
if (response?.config?.onError) {
|
||||
// @ts-ignore
|
||||
response.config.onError(new Error(errorMessage));
|
||||
}
|
||||
//@ts-ignore
|
||||
const showErrorNotify = response?.config?.showErrorNotify;
|
||||
errorCreate(`${errorMessage}: ${response.config.url}`, showErrorNotify);
|
||||
return dataAxios;
|
||||
}
|
||||
response.config.onError(err);
|
||||
return;
|
||||
}
|
||||
//@ts-ignore
|
||||
const showErrorNotify = response?.config?.showErrorNotify;
|
||||
errorCreate(`${errorMessage}: ${response.config.url}`, showErrorNotify, dataAxios);
|
||||
return dataAxios;
|
||||
}
|
||||
},
|
||||
error => {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @param {String} defaultValue 默认值
|
||||
*/
|
||||
import { uiContext } from "@fast-crud/fast-crud";
|
||||
import { CodeError } from "/@/api/service";
|
||||
|
||||
export function parse(jsonString = "{}", defaultValue = {}) {
|
||||
let result = defaultValue;
|
||||
@@ -68,8 +69,8 @@ export function errorLog(error: any, notify = true) {
|
||||
* @description 创建一个错误
|
||||
* @param {String} msg 错误信息
|
||||
*/
|
||||
export function errorCreate(msg: string, notify = true) {
|
||||
const err = new Error(msg);
|
||||
export function errorCreate(msg: string, notify = true, data?: any) {
|
||||
const err = new CodeError(msg, data.code, data.data);
|
||||
console.error("errorCreate", err);
|
||||
if (notify) {
|
||||
uiContext.get().notification.error({ message: err.message });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="pem-input">
|
||||
<FileInput v-bind="fileInput" class="mb-5" type="primary" text="选择文件" @change="onChange" />
|
||||
<a-textarea v-bind="textarea" v-model:value="textRef"></a-textarea>
|
||||
<a-textarea v-bind="textarea" :value="modelValue" @update:value="emitValue"></a-textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,7 +17,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const textRef = ref();
|
||||
|
||||
function emitValue(value: string) {
|
||||
emit("update:modelValue", value);
|
||||
@@ -39,16 +38,6 @@ function onChange(e: any) {
|
||||
};
|
||||
fileReader.readAsText(file); // 以文本形式读取文件
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
value => {
|
||||
textRef.value = value;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
@@ -3,12 +3,12 @@ export default {
|
||||
crud: { i18n: { name: "姓名", city: "城市", status: "状态" } },
|
||||
login: {
|
||||
logoutTip: "确认",
|
||||
logoutMessage: "确定要注销登录吗?"
|
||||
}
|
||||
logoutMessage: "确定要注销登录吗?",
|
||||
},
|
||||
},
|
||||
fs: {
|
||||
rowHandle: {
|
||||
title: "操作列"
|
||||
}
|
||||
}
|
||||
title: "操作列",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useI18n } from "vue-i18n";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
defineOptions({
|
||||
name: "FsUserInfo"
|
||||
name: "FsUserInfo",
|
||||
});
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
@@ -38,7 +38,7 @@ function doLogout() {
|
||||
content: t("app.login.logoutMessage"),
|
||||
onOk: async () => {
|
||||
await userStore.logout(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,7 @@ const avatar = computed(() => {
|
||||
});
|
||||
|
||||
async function handleLogout() {
|
||||
userStore.logout(true);
|
||||
await userStore.logout(true);
|
||||
}
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
@@ -143,6 +143,17 @@ export const certdResources = [
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "认证安全设置",
|
||||
name: "UserSecurity",
|
||||
path: "/certd/mine/security",
|
||||
component: "/certd/mine/security/index.vue",
|
||||
meta: {
|
||||
icon: "fluent:shield-keyhole-16-regular",
|
||||
auth: true,
|
||||
isMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "账号信息",
|
||||
name: "UserProfile",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { updatePreferences } from "/@/vben/preferences";
|
||||
import { useTitle } from "@vueuse/core";
|
||||
import { utils } from "/@/utils";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
|
||||
export interface SettingState {
|
||||
sysPublic?: SysPublicSetting;
|
||||
installInfo?: {
|
||||
@@ -184,6 +185,17 @@ export const useSettingStore = defineStore({
|
||||
useTitle(this.siteInfo.title);
|
||||
}
|
||||
},
|
||||
getBaseUrl() {
|
||||
let url = window.location.href;
|
||||
//只要hash前面的部分
|
||||
url = url.split("#")[0];
|
||||
return url;
|
||||
},
|
||||
async doBindUrl() {
|
||||
const url = this.getBaseUrl();
|
||||
await basicApi.bindUrl({ url });
|
||||
await this.loadSysSettings();
|
||||
},
|
||||
async checkUrlBound() {
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
@@ -193,22 +205,9 @@ export const useSettingStore = defineStore({
|
||||
|
||||
const bindUrl = this.installInfo.bindUrl;
|
||||
|
||||
function getBaseUrl() {
|
||||
let url = window.location.href;
|
||||
//只要hash前面的部分
|
||||
url = url.split("#")[0];
|
||||
return url;
|
||||
}
|
||||
|
||||
const doBindUrl = async (url: string) => {
|
||||
await basicApi.bindUrl({ url });
|
||||
await this.loadSysSettings();
|
||||
};
|
||||
|
||||
const baseUrl = getBaseUrl();
|
||||
if (!bindUrl) {
|
||||
//绑定url
|
||||
await doBindUrl(baseUrl);
|
||||
await this.doBindUrl();
|
||||
} else {
|
||||
//检查当前url 是否与绑定的url一致
|
||||
const url = window.location.href;
|
||||
@@ -217,7 +216,7 @@ export const useSettingStore = defineStore({
|
||||
title: "URL地址有变化",
|
||||
content: "以后都用这个新地址访问本系统吗?",
|
||||
onOk: async () => {
|
||||
await doBindUrl(baseUrl);
|
||||
await this.doBindUrl();
|
||||
},
|
||||
okText: "是的,继续",
|
||||
cancelText: "不是,回到原来的地址",
|
||||
|
||||
@@ -41,6 +41,12 @@ export async function register(user: RegisterReq): Promise<UserInfoRes> {
|
||||
data: user,
|
||||
});
|
||||
}
|
||||
export async function logout() {
|
||||
return await request({
|
||||
url: "/logout",
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
export async function login(data: LoginReq): Promise<LoginRes> {
|
||||
//如果开启了登录与权限模块,则真实登录
|
||||
@@ -66,3 +72,11 @@ export async function mine(): Promise<UserInfoRes> {
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
export async function loginByTwoFactor(data: any) {
|
||||
return await request({
|
||||
url: "/loginByTwoFactor",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const useUserStore = defineStore({
|
||||
setUserInfo(info: UserInfoRes) {
|
||||
this.userInfo = info;
|
||||
const userStore = vbenUserStore();
|
||||
userStore.setUserInfo(info);
|
||||
userStore.setUserInfo(info as any);
|
||||
LocalStorage.set(USER_INFO_KEY, info);
|
||||
},
|
||||
resetState() {
|
||||
@@ -71,23 +71,18 @@ export const useUserStore = defineStore({
|
||||
* @description: login
|
||||
*/
|
||||
async login(loginType: string, params: LoginReq | SmsLoginReq): Promise<any> {
|
||||
try {
|
||||
let loginRes: any = null;
|
||||
if (loginType === "sms") {
|
||||
loginRes = await UserApi.loginBySms(params as SmsLoginReq);
|
||||
} else {
|
||||
loginRes = await UserApi.login(params as LoginReq);
|
||||
}
|
||||
|
||||
const { token, expire } = loginRes;
|
||||
// save token
|
||||
this.setToken(token, expire);
|
||||
// get user info
|
||||
return await this.onLoginSuccess(loginRes);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
let loginRes: any = null;
|
||||
if (loginType === "sms") {
|
||||
loginRes = await UserApi.loginBySms(params as SmsLoginReq);
|
||||
} else {
|
||||
loginRes = await UserApi.login(params as LoginReq);
|
||||
}
|
||||
return await this.onLoginSuccess(loginRes);
|
||||
},
|
||||
|
||||
async loginByTwoFactor(form: any) {
|
||||
const loginRes = await UserApi.loginByTwoFactor(form);
|
||||
return await this.onLoginSuccess(loginRes);
|
||||
},
|
||||
async getUserInfoAction(): Promise<UserInfoRes> {
|
||||
const userInfo = await UserApi.mine();
|
||||
@@ -100,18 +95,23 @@ export const useUserStore = defineStore({
|
||||
},
|
||||
|
||||
async onLoginSuccess(loginData: any) {
|
||||
const { token, expire } = loginData;
|
||||
// save token
|
||||
this.setToken(token, expire);
|
||||
// get user info
|
||||
// await this.getUserInfoAction();
|
||||
// const userInfo = await this.getUserInfoAction();
|
||||
mitter.emit("app.login", { token: loginData });
|
||||
mitter.emit("app.login", { ...loginData });
|
||||
await router.replace("/");
|
||||
},
|
||||
|
||||
/**
|
||||
* @description: logout
|
||||
*/
|
||||
logout(goLogin = true) {
|
||||
async logout(goLogin = true) {
|
||||
this.resetState();
|
||||
resetAllStores();
|
||||
await UserApi.logout(); //主要是清空cookie
|
||||
goLogin && router.push("/login");
|
||||
mitter.emit("app.logout");
|
||||
},
|
||||
|
||||
@@ -105,4 +105,19 @@ span.fs-icon-svg{
|
||||
svg{
|
||||
vertical-align:0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fs-button{
|
||||
span{
|
||||
&:first-child{
|
||||
margin-right: 5px;
|
||||
}
|
||||
&:last-child{
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
.fs-icon,.fs-button-icon{
|
||||
margin: 0 !important;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { routerUtils } from "./util.router";
|
||||
import { treeUtils } from "./util.tree";
|
||||
import { hashUtils } from "./util.hash";
|
||||
import { amountUtils } from "./util.amount";
|
||||
import { cache } from "./util.cache";
|
||||
export const util = {
|
||||
...envs,
|
||||
...sites,
|
||||
@@ -17,5 +18,6 @@ export const util = {
|
||||
tree: treeUtils,
|
||||
hash: hashUtils,
|
||||
amount: amountUtils,
|
||||
cache,
|
||||
};
|
||||
export const utils = util;
|
||||
|
||||
17
packages/ui/certd-client/src/utils/util.cache.ts
Normal file
17
packages/ui/certd-client/src/utils/util.cache.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export class Cache {
|
||||
bucket: Record<string, any> = {};
|
||||
|
||||
async get(key: string) {
|
||||
return this.bucket[key];
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number) {
|
||||
this.bucket[key] = value;
|
||||
}
|
||||
|
||||
async del(key: string) {
|
||||
delete this.bucket[key];
|
||||
}
|
||||
}
|
||||
|
||||
export const cache = new Cache();
|
||||
@@ -0,0 +1,47 @@
|
||||
// @ts-ignore
|
||||
import { request } from "/@/api/service";
|
||||
const apiPrefix = "/user/settings";
|
||||
export type UserTwoFactorSetting = {
|
||||
authenticator: {
|
||||
enabled: boolean;
|
||||
verified: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type AuthenticatorSaveReq = {
|
||||
verifyCode?: string;
|
||||
};
|
||||
|
||||
export async function TwoFactorSettingsGet() {
|
||||
const res = await request({
|
||||
url: apiPrefix + "/twoFactor/get",
|
||||
method: "post",
|
||||
});
|
||||
if (!res) {
|
||||
return {};
|
||||
}
|
||||
return res as UserTwoFactorSetting;
|
||||
}
|
||||
|
||||
export async function TwoFactorAuthenticatorGet() {
|
||||
const res = await request({
|
||||
url: apiPrefix + "/twoFactor/authenticator/qrcode",
|
||||
method: "post",
|
||||
});
|
||||
return res as string; //base64
|
||||
}
|
||||
|
||||
export async function TwoFactorAuthenticatorSave(req: AuthenticatorSaveReq) {
|
||||
return await request({
|
||||
url: apiPrefix + "/twoFactor/authenticator/save",
|
||||
method: "post",
|
||||
data: req,
|
||||
});
|
||||
}
|
||||
|
||||
export async function TwoFactorAuthenticatorOff() {
|
||||
return await request({
|
||||
url: apiPrefix + "/twoFactor/authenticator/off",
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
162
packages/ui/certd-client/src/views/certd/mine/security/index.vue
Normal file
162
packages/ui/certd-client/src/views/certd/mine/security/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<fs-page class="page-user-settings page-two-factor">
|
||||
<template #header>
|
||||
<div class="title">认证安全设置</div>
|
||||
</template>
|
||||
<div class="user-settings-form settings-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<a-form-item label="2FA多重验证登录" :name="['authenticator', 'enabled']">
|
||||
<div class="flex mt-5">
|
||||
<a-switch v-model:checked="formState.authenticator.enabled" :disabled="!settingsStore.isPlus" @change="onAuthenticatorEnabledChanged" />
|
||||
|
||||
<a-button
|
||||
v-if="formState.authenticator.enabled && formState.authenticator.verified"
|
||||
:disabled="authenticatorOpenRef || !settingsStore.isPlus"
|
||||
size="small"
|
||||
class="ml-5"
|
||||
type="primary"
|
||||
@click="authenticatorForm.open = true"
|
||||
>
|
||||
重新绑定
|
||||
</a-button>
|
||||
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
|
||||
<div class="helper">是否开启多重验证登录</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="authenticatorOpenRef" label="绑定设备" class="authenticator-config">
|
||||
<h3 class="font-bold m-5">1. 安装任意一款支持Authenticator的验证APP,比如:</h3>
|
||||
<div class="ml-20">
|
||||
<ul>
|
||||
<li>
|
||||
<a-tooltip title="如果报没有找到谷歌服务的错误,您可以安装KK谷歌助手">
|
||||
<a href="https://appgallery.huawei.com/app/C100262999" target="_blank"> Microsoft Authenticator</a>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://sj.qq.com/appdetail/com.tencent.authenticator" target="_blank">腾讯身份验证器</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.synology.cn/zh-cn/dsm/feature/authentication" target="_blank">群晖身份验证器</a>
|
||||
</li>
|
||||
<li>
|
||||
<a-tooltip title="如果报没有找到谷歌服务的错误,您可以安装KK谷歌助手">
|
||||
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" target="_blank">Google Authenticator</a>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.authy.authy" target="_blank">Authy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="font-bold m-10">2. 扫描二维码添加账号</h3>
|
||||
<div v-if="authenticatorForm.qrcodeSrc" class="qrcode">
|
||||
<div class="ml-20">
|
||||
<img class="full-w" :src="authenticatorForm.qrcodeSrc" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="font-bold m-10">3. 输入验证码</h3>
|
||||
<div class="ml-20">
|
||||
<a-input v-model:value="authenticatorForm.verifyCode" placeholder="请输入验证码" />
|
||||
</div>
|
||||
<div class="ml-20 flex mt-10">
|
||||
<loading-button type="primary" html-type="button" :click="doAuthenticatorSave">确认</loading-button>
|
||||
<a-button class="ml-1" @click="authenticatorForm.open = false">取消</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { computed, reactive, watch } from "vue";
|
||||
import * as api from "./api";
|
||||
import { UserTwoFactorSetting } from "./api";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
const settingsStore = useSettingStore();
|
||||
defineOptions({
|
||||
name: "UserSecurity",
|
||||
});
|
||||
|
||||
const formState = reactive<Partial<UserTwoFactorSetting>>({
|
||||
authenticator: {
|
||||
enabled: false,
|
||||
verified: false,
|
||||
},
|
||||
});
|
||||
|
||||
const authenticatorForm = reactive({
|
||||
qrcodeSrc: "",
|
||||
verifyCode: "",
|
||||
open: false,
|
||||
});
|
||||
|
||||
const authenticatorOpenRef = computed(() => {
|
||||
return formState.authenticator.enabled && (authenticatorForm.open || !formState.authenticator.verified);
|
||||
});
|
||||
watch(
|
||||
() => {
|
||||
return authenticatorOpenRef.value;
|
||||
},
|
||||
async open => {
|
||||
if (open) {
|
||||
//base64 转图片
|
||||
authenticatorForm.qrcodeSrc = await api.TwoFactorAuthenticatorGet();
|
||||
} else {
|
||||
authenticatorForm.qrcodeSrc = "";
|
||||
authenticatorForm.verifyCode = "";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async function loadUserSettings() {
|
||||
const data: any = await api.TwoFactorSettingsGet();
|
||||
merge(formState, data);
|
||||
}
|
||||
|
||||
loadUserSettings();
|
||||
const doAuthenticatorSave = async (form: any) => {
|
||||
await api.TwoFactorAuthenticatorSave({
|
||||
verifyCode: authenticatorForm.verifyCode,
|
||||
});
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
authenticatorForm.open = false;
|
||||
};
|
||||
|
||||
function onAuthenticatorEnabledChanged(value: any) {
|
||||
if (!value) {
|
||||
//要关闭
|
||||
if (formState.authenticator.verified) {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要关闭多重验证登录吗?`,
|
||||
async onOk() {
|
||||
await api.TwoFactorAuthenticatorOff();
|
||||
notification.success({
|
||||
message: "关闭成功",
|
||||
});
|
||||
loadUserSettings();
|
||||
},
|
||||
onCancel() {
|
||||
formState.authenticator.enabled = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-user-settings {
|
||||
.user-settings-form {
|
||||
width: 600px;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query
|
||||
data: query,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/add",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/update",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
|
||||
@@ -39,14 +39,14 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/info",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
|
||||
async GetOptions(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/options",
|
||||
method: "post"
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -54,14 +54,14 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/setDefault",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
|
||||
async GetDefaultId() {
|
||||
return await request({
|
||||
url: apiPrefix + "/getDefaultId",
|
||||
method: "post"
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -69,14 +69,14 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/simpleInfo",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
|
||||
async GetDefineTypes() {
|
||||
return await request({
|
||||
url: apiPrefix + "/getTypeDict",
|
||||
method: "post"
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -84,7 +84,7 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/define",
|
||||
method: "post",
|
||||
params: { type }
|
||||
params: { type },
|
||||
});
|
||||
},
|
||||
|
||||
@@ -92,15 +92,15 @@ export function createNotificationApi() {
|
||||
return await request({
|
||||
url: apiPrefix + "/defineByType",
|
||||
method: "post",
|
||||
params: { type }
|
||||
params: { type },
|
||||
});
|
||||
},
|
||||
async GetOrCreateDefault(param: { email: any }) {
|
||||
return await request({
|
||||
url: apiPrefix + "/getOrCreateDefault",
|
||||
method: "post",
|
||||
data: param
|
||||
data: param,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,3 +122,11 @@ export async function GetCert(pipelineId: number): Promise<CertInfo> {
|
||||
params: { id: pipelineId },
|
||||
});
|
||||
}
|
||||
|
||||
export async function ReadCertDetail(crt: string): Promise<any> {
|
||||
return await request({
|
||||
url: certApiPrefix + "/readCertDetail",
|
||||
method: "post",
|
||||
data: { crt },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="tsx" setup>
|
||||
import { computed, inject } from "vue";
|
||||
import { computed, inject, watch, ref } from "vue";
|
||||
import { useCertUpload } from "./use";
|
||||
import { getAllDomainsFromCrt } from "/@/views/certd/pipeline/utils";
|
||||
|
||||
@@ -27,19 +27,36 @@ const emit = defineEmits(["updated", "update:modelValue"]);
|
||||
|
||||
const { openUpdateCertDialog } = useCertUpload();
|
||||
|
||||
const domain = computed(() => {
|
||||
if (!props.modelValue?.crt) {
|
||||
return "";
|
||||
}
|
||||
const domains = getAllDomainsFromCrt(props.modelValue?.crt);
|
||||
const domainsRef = ref([]);
|
||||
|
||||
return domains[0];
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue?.crt;
|
||||
},
|
||||
async crt => {
|
||||
if (crt) {
|
||||
domainsRef.value = await getAllDomainsFromCrt(crt);
|
||||
} else {
|
||||
domainsRef.value = [];
|
||||
}
|
||||
|
||||
emit("updated", { domains: domainsRef.value });
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const domain = computed(() => {
|
||||
if (domainsRef.value && domainsRef.value.length > 0) {
|
||||
return domainsRef.value[0];
|
||||
}
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
function onUpdated(res: { uploadCert: any }) {
|
||||
async function onUpdated(res: { uploadCert: any }) {
|
||||
emit("update:modelValue", res.uploadCert);
|
||||
const domains = getAllDomainsFromCrt(res.uploadCert.crt);
|
||||
emit("updated", { domains });
|
||||
}
|
||||
|
||||
const pipeline: any = inject("pipeline");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { compute, useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import { compute, dict, useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
import { cloneDeep, omit } from "lodash-es";
|
||||
import { useReference } from "/@/use/use-refrence";
|
||||
@@ -8,6 +8,7 @@ import { checkPipelineLimit, getAllDomainsFromCrt } from "/@/views/certd/pipelin
|
||||
import { useRouter } from "vue-router";
|
||||
import { nanoid } from "nanoid";
|
||||
import { usePluginStore } from "/@/store/plugin";
|
||||
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
|
||||
|
||||
export function useCertUpload() {
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
@@ -90,7 +91,7 @@ export function useCertUpload() {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
async function openUploadCreateDialog() {
|
||||
async function openUploadCreateDialog(req: { defaultGroupId?: number }) {
|
||||
//检查是否流水线数量超出限制
|
||||
await checkPipelineLimit();
|
||||
|
||||
@@ -102,7 +103,11 @@ export function useCertUpload() {
|
||||
return wrapperRef.value.getFormData();
|
||||
}
|
||||
const inputs = await buildUploadCertPluginInputs(getFormData);
|
||||
|
||||
const groupDictRef = dict({
|
||||
url: "/pi/pipeline/group/all",
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
function createCrudOptions() {
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -127,6 +132,19 @@ export function useCertUpload() {
|
||||
helper: "任务执行失败实时提醒",
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
title: "流水线分组",
|
||||
type: "dict-select",
|
||||
dict: groupDictRef,
|
||||
form: {
|
||||
component: {
|
||||
name: GroupSelector,
|
||||
vModel: "modelValue",
|
||||
},
|
||||
value: req.defaultGroupId || undefined,
|
||||
order: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
wrapper: {
|
||||
@@ -135,7 +153,7 @@ export function useCertUpload() {
|
||||
},
|
||||
async doSubmit({ form }: any) {
|
||||
const cert = form.uploadCert;
|
||||
const domains = getAllDomainsFromCrt(cert.crt);
|
||||
const domains = await getAllDomainsFromCrt(cert.crt);
|
||||
|
||||
const notifications = [];
|
||||
if (form.notification != null) {
|
||||
@@ -191,6 +209,7 @@ export function useCertUpload() {
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert_upload",
|
||||
groupId: form.groupId,
|
||||
});
|
||||
router.push({
|
||||
path: "/certd/pipeline/detail",
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import { compute, CreateCrudOptionsRet, dict } from "@fast-crud/fast-crud";
|
||||
import { useReference } from "/@/use/use-refrence";
|
||||
import { merge, cloneDeep } from "lodash-es";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
import { usePluginStore } from "/@/store/plugin";
|
||||
|
||||
export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOptionsRet {
|
||||
const inputs: any = {};
|
||||
const moreParams = [];
|
||||
for (const plugin of certPlugins) {
|
||||
for (const inputKey in plugin.input) {
|
||||
if (inputs[inputKey]) {
|
||||
//如果两个插件有的字段,直接显示
|
||||
inputs[inputKey].form.show = true;
|
||||
continue;
|
||||
}
|
||||
const inputDefine = cloneDeep(plugin.input[inputKey]);
|
||||
if (!inputDefine.required && !inputDefine.maybeNeed) {
|
||||
moreParams.push(inputKey);
|
||||
// continue;
|
||||
}
|
||||
useReference(inputDefine);
|
||||
inputs[inputKey] = {
|
||||
title: inputDefine.title,
|
||||
form: {
|
||||
...inputDefine,
|
||||
show: compute(ctx => {
|
||||
const form = formWrapperRef.value.getFormData();
|
||||
if (!form) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let inputDefineShow = true;
|
||||
if (inputDefine.show != null) {
|
||||
const computeShow = inputDefine.show as any;
|
||||
if (computeShow === false) {
|
||||
inputDefineShow = false;
|
||||
} else if (computeShow && computeShow.computeFn) {
|
||||
inputDefineShow = computeShow.computeFn({ form });
|
||||
}
|
||||
}
|
||||
return form?.certApplyPlugin === plugin.name && inputDefineShow;
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
const randomHour = Math.floor(Math.random() * 6);
|
||||
const randomMin = Math.floor(Math.random() * 60);
|
||||
return {
|
||||
crudOptions: {
|
||||
form: {
|
||||
wrapper: {
|
||||
width: 1350,
|
||||
saveRemind: false,
|
||||
title: "创建证书流水线",
|
||||
},
|
||||
group: {
|
||||
groups: {
|
||||
more: {
|
||||
header: "更多参数",
|
||||
columns: moreParams,
|
||||
collapsed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
certApplyPlugin: {
|
||||
title: "证书申请插件",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "CertApply", label: "JS-ACME" },
|
||||
{ value: "CertApplyLego", label: "Lego-ACME" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
order: 0,
|
||||
value: "CertApply",
|
||||
helper: {
|
||||
render: () => {
|
||||
return (
|
||||
<ul>
|
||||
<li>JS-ACME:使用简单方便,功能强大【推荐】</li>
|
||||
<li>Lego-ACME:基于Lego实现,支持海量DNS提供商,熟悉LEGO的用户可以使用</li>
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
},
|
||||
valueChange: {
|
||||
handle: async ({ form, value }) => {
|
||||
const config = await pluginStore.getPluginConfig({
|
||||
name: value,
|
||||
type: "builtIn",
|
||||
});
|
||||
if (config.sysSetting?.input) {
|
||||
merge(form, config.sysSetting.input);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...inputs,
|
||||
triggerCron: {
|
||||
title: "定时触发",
|
||||
type: "text",
|
||||
form: {
|
||||
value: `0 ${randomMin} ${randomHour} * * *`,
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
placeholder: "0 0 4 * * *",
|
||||
},
|
||||
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
|
||||
order: 100,
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
title: "失败通知",
|
||||
type: "text",
|
||||
form: {
|
||||
value: 0,
|
||||
component: {
|
||||
name: NotificationSelector,
|
||||
vModel: "modelValue",
|
||||
on: {
|
||||
selectedChange({ $event, form }) {
|
||||
form.notificationTarget = $event;
|
||||
},
|
||||
},
|
||||
},
|
||||
order: 101,
|
||||
helper: "任务执行失败实时提醒",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<fs-form-wrapper v-if="formWrapperOptions" ref="formWrapperRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useColumns } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud.jsx";
|
||||
import { ref } from "vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { PluginGroup, usePluginStore } from "/@/store/plugin";
|
||||
import { createNotificationApi } from "/@/views/certd/notification/api";
|
||||
|
||||
defineOptions({
|
||||
name: "PiCertdForm",
|
||||
});
|
||||
|
||||
const formWrapperRef = ref();
|
||||
const formWrapperOptions = ref();
|
||||
const doSubmitRef = ref();
|
||||
const pluginStore = usePluginStore();
|
||||
async function buildFormOptions() {
|
||||
const pluginGroup = await pluginStore.getGroups();
|
||||
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
|
||||
const certPluginGroup = pluginGroups.cert;
|
||||
|
||||
const certPlugins = [];
|
||||
for (const plugin of certPluginGroup.plugins) {
|
||||
const detail: any = await pluginStore.getPluginDefine(plugin.name);
|
||||
certPlugins.push(detail);
|
||||
}
|
||||
|
||||
// 自定义表单配置
|
||||
const { buildFormOptions } = useColumns();
|
||||
//使用crudOptions结构来构建自定义表单配置
|
||||
let { crudOptions } = createCrudOptions(certPlugins, formWrapperRef);
|
||||
|
||||
const formOptions = buildFormOptions(
|
||||
merge(crudOptions, {
|
||||
form: {
|
||||
async doSubmit({ form }: any) {
|
||||
// 创建certd 的pipeline
|
||||
await doSubmitRef.value({ form });
|
||||
|
||||
if (form.email) {
|
||||
//创建一个默认的邮件通知
|
||||
const notificationApi = createNotificationApi();
|
||||
await notificationApi.GetOrCreateDefault({ email: form.email });
|
||||
}
|
||||
},
|
||||
},
|
||||
}) as any
|
||||
);
|
||||
|
||||
formWrapperOptions.value = formOptions;
|
||||
}
|
||||
buildFormOptions();
|
||||
function open(doSubmit: any) {
|
||||
doSubmitRef.value = doSubmit;
|
||||
formWrapperRef.value.open(formWrapperOptions.value);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,126 +0,0 @@
|
||||
import { checkPipelineLimit, readCertDetail } from "/@/views/certd/pipeline/utils";
|
||||
import { omit } from "lodash-es";
|
||||
import * as api from "/@/views/certd/pipeline/api";
|
||||
import { message } from "ant-design-vue";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
export function setRunnableIds(pipeline: any) {
|
||||
const idMap: any = {};
|
||||
function createId(oldId: any) {
|
||||
if (oldId == null) {
|
||||
return nanoid();
|
||||
}
|
||||
const newId = nanoid();
|
||||
idMap[oldId] = newId;
|
||||
return newId;
|
||||
}
|
||||
if (pipeline.stages) {
|
||||
for (const stage of pipeline.stages) {
|
||||
stage.id = createId(stage.id);
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
task.id = createId(task.id);
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
step.id = createId(step.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const trigger of pipeline.triggers) {
|
||||
trigger.id = nanoid();
|
||||
}
|
||||
for (const notification of pipeline.notifications) {
|
||||
notification.id = nanoid();
|
||||
}
|
||||
|
||||
let content = JSON.stringify(pipeline);
|
||||
for (const key in idMap) {
|
||||
content = content.replaceAll(key, idMap[key]);
|
||||
}
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
export function useCertd(certdFormRef: any) {
|
||||
const router = useRouter();
|
||||
async function openAddCertdPipelineDialog() {
|
||||
//检查是否流水线数量超出限制
|
||||
await checkPipelineLimit();
|
||||
|
||||
certdFormRef.value.open(async ({ form }: any) => {
|
||||
// const certDetail = readCertDetail(form.cert.crt);
|
||||
// 添加certd pipeline
|
||||
const triggers = [];
|
||||
if (form.triggerCron) {
|
||||
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
|
||||
}
|
||||
const notifications = [];
|
||||
if (form.notification != null) {
|
||||
notifications.push({
|
||||
type: "custom",
|
||||
when: ["error", "turnToSuccess", "success"],
|
||||
notificationId: form.notification,
|
||||
title: form.notificationTarget?.name || "自定义通知",
|
||||
});
|
||||
}
|
||||
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin"]);
|
||||
let pipeline = {
|
||||
title: form.domains[0] + "证书自动化",
|
||||
runnableType: "pipeline",
|
||||
stages: [
|
||||
{
|
||||
title: "证书申请阶段",
|
||||
maxTaskCount: 1,
|
||||
runnableType: "stage",
|
||||
tasks: [
|
||||
{
|
||||
title: "证书申请任务",
|
||||
runnableType: "task",
|
||||
steps: [
|
||||
{
|
||||
title: "申请证书",
|
||||
runnableType: "step",
|
||||
input: {
|
||||
renewDays: 35,
|
||||
...pluginInput,
|
||||
},
|
||||
strategy: {
|
||||
runStrategy: 0, // 正常执行
|
||||
},
|
||||
type: form.certApplyPlugin,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
triggers,
|
||||
notifications,
|
||||
};
|
||||
pipeline = setRunnableIds(pipeline);
|
||||
|
||||
/**
|
||||
* // cert: 证书; backup: 备份; custom:自定义;
|
||||
* type: string;
|
||||
* // custom: 自定义; monitor: 监控;
|
||||
* from: string;
|
||||
*/
|
||||
const id = await api.Save({
|
||||
title: pipeline.title,
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert",
|
||||
});
|
||||
message.success("创建成功,请添加证书部署任务");
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
openAddCertdPipelineDialog,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
import { checkPipelineLimit } from "/@/views/certd/pipeline/utils";
|
||||
import { cloneDeep, merge, omit } from "lodash-es";
|
||||
import { message } from "ant-design-vue";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useRouter } from "vue-router";
|
||||
import { compute, CreateCrudOptionsRet, dict, useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
import { useReference } from "/@/use/use-refrence";
|
||||
import { ref } from "vue";
|
||||
import * as api from "../api";
|
||||
import { PluginGroup, usePluginStore } from "/@/store/plugin";
|
||||
import { createNotificationApi } from "/@/views/certd/notification/api";
|
||||
import GroupSelector from "../group/group-selector.vue";
|
||||
|
||||
export function setRunnableIds(pipeline: any) {
|
||||
const idMap: any = {};
|
||||
function createId(oldId: any) {
|
||||
if (oldId == null) {
|
||||
return nanoid();
|
||||
}
|
||||
const newId = nanoid();
|
||||
idMap[oldId] = newId;
|
||||
return newId;
|
||||
}
|
||||
if (pipeline.stages) {
|
||||
for (const stage of pipeline.stages) {
|
||||
stage.id = createId(stage.id);
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
task.id = createId(task.id);
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
step.id = createId(step.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const trigger of pipeline.triggers) {
|
||||
trigger.id = nanoid();
|
||||
}
|
||||
for (const notification of pipeline.notifications) {
|
||||
notification.id = nanoid();
|
||||
}
|
||||
|
||||
let content = JSON.stringify(pipeline);
|
||||
for (const key in idMap) {
|
||||
content = content.replaceAll(key, idMap[key]);
|
||||
}
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
export function useCertPipelineCreator() {
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
const router = useRouter();
|
||||
|
||||
function createCrudOptions(certPlugins: any[], getFormData: any, doSubmit: any): CreateCrudOptionsRet {
|
||||
const inputs: any = {};
|
||||
const moreParams = [];
|
||||
for (const plugin of certPlugins) {
|
||||
for (const inputKey in plugin.input) {
|
||||
if (inputs[inputKey]) {
|
||||
//如果两个插件有的字段,直接显示
|
||||
inputs[inputKey].form.show = true;
|
||||
continue;
|
||||
}
|
||||
const inputDefine = cloneDeep(plugin.input[inputKey]);
|
||||
if (!inputDefine.required && !inputDefine.maybeNeed) {
|
||||
moreParams.push(inputKey);
|
||||
// continue;
|
||||
}
|
||||
useReference(inputDefine);
|
||||
inputs[inputKey] = {
|
||||
title: inputDefine.title,
|
||||
form: {
|
||||
...inputDefine,
|
||||
show: compute(ctx => {
|
||||
const form = getFormData();
|
||||
if (!form) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let inputDefineShow = true;
|
||||
if (inputDefine.show != null) {
|
||||
const computeShow = inputDefine.show as any;
|
||||
if (computeShow === false) {
|
||||
inputDefineShow = false;
|
||||
} else if (computeShow && computeShow.computeFn) {
|
||||
inputDefineShow = computeShow.computeFn({ form });
|
||||
}
|
||||
}
|
||||
return form?.certApplyPlugin === plugin.name && inputDefineShow;
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
const randomHour = Math.floor(Math.random() * 6);
|
||||
const randomMin = Math.floor(Math.random() * 60);
|
||||
|
||||
const groupDictRef = dict({
|
||||
url: "/pi/pipeline/group/all",
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
form: {
|
||||
doSubmit,
|
||||
wrapper: {
|
||||
width: 1350,
|
||||
saveRemind: false,
|
||||
title: "创建证书流水线",
|
||||
},
|
||||
group: {
|
||||
groups: {
|
||||
more: {
|
||||
header: "更多参数",
|
||||
columns: moreParams,
|
||||
collapsed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
certApplyPlugin: {
|
||||
title: "证书申请插件",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "CertApply", label: "JS-ACME" },
|
||||
{ value: "CertApplyLego", label: "Lego-ACME" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
order: 0,
|
||||
value: "CertApply",
|
||||
helper: {
|
||||
render: () => {
|
||||
return (
|
||||
<ul>
|
||||
<li>JS-ACME:使用简单方便,功能强大【推荐】</li>
|
||||
<li>Lego-ACME:基于Lego实现,支持海量DNS提供商,熟悉LEGO的用户可以使用</li>
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
},
|
||||
valueChange: {
|
||||
handle: async ({ form, value }) => {
|
||||
const config = await pluginStore.getPluginConfig({
|
||||
name: value,
|
||||
type: "builtIn",
|
||||
});
|
||||
if (config.sysSetting?.input) {
|
||||
merge(form, config.sysSetting.input);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...inputs,
|
||||
triggerCron: {
|
||||
title: "定时触发",
|
||||
type: "text",
|
||||
form: {
|
||||
value: `0 ${randomMin} ${randomHour} * * *`,
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
placeholder: "0 0 4 * * *",
|
||||
},
|
||||
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
|
||||
order: 100,
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
title: "失败通知",
|
||||
type: "text",
|
||||
form: {
|
||||
value: 0,
|
||||
component: {
|
||||
name: NotificationSelector,
|
||||
vModel: "modelValue",
|
||||
on: {
|
||||
selectedChange({ $event, form }) {
|
||||
form.notificationTarget = $event;
|
||||
},
|
||||
},
|
||||
},
|
||||
order: 101,
|
||||
helper: "任务执行失败实时提醒",
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
title: "流水线分组",
|
||||
type: "dict-select",
|
||||
dict: groupDictRef,
|
||||
form: {
|
||||
component: {
|
||||
name: GroupSelector,
|
||||
vModel: "modelValue",
|
||||
},
|
||||
order: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function getCertPlugins() {
|
||||
const pluginGroup = await pluginStore.getGroups();
|
||||
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
|
||||
const certPluginGroup = pluginGroups.cert;
|
||||
|
||||
const certPlugins = [];
|
||||
for (const plugin of certPluginGroup.plugins) {
|
||||
const detail: any = await pluginStore.getPluginDefine(plugin.name);
|
||||
certPlugins.push(detail);
|
||||
}
|
||||
return certPlugins;
|
||||
}
|
||||
|
||||
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) {
|
||||
//检查是否流水线数量超出限制
|
||||
await checkPipelineLimit();
|
||||
|
||||
const wrapperRef = ref();
|
||||
function getFormData() {
|
||||
if (!wrapperRef.value) {
|
||||
return null;
|
||||
}
|
||||
return wrapperRef.value.getFormData();
|
||||
}
|
||||
|
||||
async function doSubmit({ form }: any) {
|
||||
// const certDetail = readCertDetail(form.cert.crt);
|
||||
// 添加certd pipeline
|
||||
const triggers = [];
|
||||
if (form.triggerCron) {
|
||||
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
|
||||
}
|
||||
const notifications = [];
|
||||
if (form.notification != null) {
|
||||
notifications.push({
|
||||
type: "custom",
|
||||
when: ["error", "turnToSuccess", "success"],
|
||||
notificationId: form.notification,
|
||||
title: form.notificationTarget?.name || "自定义通知",
|
||||
});
|
||||
}
|
||||
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]);
|
||||
let pipeline = {
|
||||
title: form.domains[0] + "证书自动化",
|
||||
runnableType: "pipeline",
|
||||
stages: [
|
||||
{
|
||||
title: "证书申请阶段",
|
||||
maxTaskCount: 1,
|
||||
runnableType: "stage",
|
||||
tasks: [
|
||||
{
|
||||
title: "证书申请任务",
|
||||
runnableType: "task",
|
||||
steps: [
|
||||
{
|
||||
title: "申请证书",
|
||||
runnableType: "step",
|
||||
input: {
|
||||
renewDays: 35,
|
||||
...pluginInput,
|
||||
},
|
||||
strategy: {
|
||||
runStrategy: 0, // 正常执行
|
||||
},
|
||||
type: form.certApplyPlugin,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
triggers,
|
||||
notifications,
|
||||
};
|
||||
pipeline = setRunnableIds(pipeline);
|
||||
|
||||
/**
|
||||
* // cert: 证书; backup: 备份; custom:自定义;
|
||||
* type: string;
|
||||
* // custom: 自定义; monitor: 监控;
|
||||
* from: string;
|
||||
*/
|
||||
const groupId = form.groupId;
|
||||
const id = await api.Save({
|
||||
title: pipeline.title,
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert",
|
||||
groupId,
|
||||
});
|
||||
if (form.email) {
|
||||
try {
|
||||
//创建一个默认的邮件通知
|
||||
const notificationApi = createNotificationApi();
|
||||
await notificationApi.GetOrCreateDefault({ email: form.email });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
message.success("创建成功,请添加证书部署任务");
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
}
|
||||
const certPlugins = await getCertPlugins();
|
||||
const { crudOptions } = createCrudOptions(certPlugins, getFormData, doSubmit);
|
||||
//@ts-ignore
|
||||
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
|
||||
const wrapper = await openCrudFormDialog({ crudOptions });
|
||||
wrapperRef.value = wrapper;
|
||||
}
|
||||
|
||||
return {
|
||||
openAddCertdPipelineDialog,
|
||||
};
|
||||
}
|
||||
@@ -13,15 +13,16 @@ import { cloneDeep } from "lodash-es";
|
||||
import { useModal } from "/@/use/use-modal";
|
||||
import CertView from "./cert-view.vue";
|
||||
import { eachStages } from "./utils";
|
||||
import { setRunnableIds, useCertd } from "/@/views/certd/pipeline/certd-form/use";
|
||||
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
|
||||
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
|
||||
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
|
||||
|
||||
export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const lastResRef = ref();
|
||||
|
||||
const { openAddCertdPipelineDialog } = useCertd(certdFormRef);
|
||||
const { openAddCertdPipelineDialog } = useCertPipelineCreator();
|
||||
const { openUploadCreateDialog } = useCertUpload();
|
||||
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
@@ -116,6 +117,13 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
function onDialogOpen(opt: any) {
|
||||
const searchForm = crudExpose.getSearchValidatedFormData();
|
||||
opt.initialForm = {
|
||||
groupId: searchForm.groupId,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
@@ -157,7 +165,9 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
type: "primary",
|
||||
icon: "ion:ios-add-circle-outline",
|
||||
click() {
|
||||
openAddCertdPipelineDialog();
|
||||
const searchForm = crudExpose.getSearchValidatedFormData();
|
||||
const defaultGroupId = searchForm.groupId;
|
||||
openAddCertdPipelineDialog({ defaultGroupId });
|
||||
},
|
||||
},
|
||||
uploadCert: {
|
||||
@@ -179,7 +189,8 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
},
|
||||
icon: "ion:cloud-upload-outline",
|
||||
click() {
|
||||
openUploadCreateDialog();
|
||||
const searchForm = crudExpose.getSearchValidatedFormData();
|
||||
openUploadCreateDialog({ defaultGroupId: searchForm.groupId });
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -190,6 +201,9 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: res.id, editMode: "true" } });
|
||||
}
|
||||
},
|
||||
wrapper: {
|
||||
onOpen: onDialogOpen,
|
||||
},
|
||||
},
|
||||
table: {
|
||||
scroll: { x: 1500 },
|
||||
@@ -418,7 +432,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
sorter: false,
|
||||
width: 150,
|
||||
align: "center",
|
||||
},
|
||||
@@ -489,6 +503,12 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
show: true,
|
||||
},
|
||||
dict: groupDictRef,
|
||||
form: {
|
||||
component: {
|
||||
name: GroupSelector,
|
||||
vModel: "modelValue",
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 130,
|
||||
align: "center",
|
||||
|
||||
@@ -5,7 +5,6 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
|
||||
import { pipelineGroupApi } from "./api";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { t } = useI18n();
|
||||
const api = pipelineGroupApi;
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
@@ -34,32 +33,32 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
mobile: {
|
||||
props: {
|
||||
rowHandle: {
|
||||
width: 160
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px"
|
||||
}
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600
|
||||
}
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
@@ -72,12 +71,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
click({ row, index }) {
|
||||
crudExpose.openEdit({
|
||||
index,
|
||||
row
|
||||
row,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
editable: {
|
||||
@@ -90,8 +89,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const { row, key, value } = opts;
|
||||
//如果是添加,需要返回{[rowKey]:xxx},比如:{id:2}
|
||||
return await api.UpdateObj({ id: row.id, [key]: value });
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
@@ -99,37 +98,37 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true
|
||||
}
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: "分组名称",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入分组名称"
|
||||
}
|
||||
]
|
||||
message: "请输入分组名称",
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
width: 400,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="pi-group-selector flex full-w">
|
||||
<div class="flex-1">
|
||||
<fs-dict-select :value="modelValue" :dict="groupDictRef" @update:value="doUpdate"></fs-dict-select>
|
||||
</div>
|
||||
|
||||
<fs-table-select
|
||||
class="flex-0"
|
||||
:create-crud-options="createCrudOptions"
|
||||
:crud-options-override="{
|
||||
search: { show: false },
|
||||
table: {
|
||||
scroll: {
|
||||
x: 540,
|
||||
},
|
||||
},
|
||||
}"
|
||||
:model-value="modelValue"
|
||||
:dict="groupDictRef"
|
||||
:show-current="false"
|
||||
:show-select="false"
|
||||
:dialog="{ width: 960 }"
|
||||
:destroy-on-close="false"
|
||||
height="400px"
|
||||
@update:model-value="doUpdate"
|
||||
@dialog-closed="doRefresh"
|
||||
>
|
||||
<template #default="scope">
|
||||
<fs-button class="ml-5" type="primary" icon="ant-design:edit-outlined" @click="scope.open"></fs-button>
|
||||
</template>
|
||||
</fs-table-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import createCrudOptions from "./crud";
|
||||
import { dict, FsDictSelect } from "@fast-crud/fast-crud";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: number;
|
||||
}>();
|
||||
|
||||
defineOptions({
|
||||
name: "GroupSelector",
|
||||
});
|
||||
const groupDictRef = dict({
|
||||
url: "/pi/pipeline/group/all",
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
const emit = defineEmits(["refresh", "update:modelValue"]);
|
||||
function doRefresh() {
|
||||
emit("refresh");
|
||||
groupDictRef.reloadDict();
|
||||
}
|
||||
|
||||
function doUpdate(value: any) {
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
</script>
|
||||
@@ -15,7 +15,6 @@
|
||||
<template #form-bottom>
|
||||
<div>申请证书</div>
|
||||
</template>
|
||||
<pi-certd-form ref="certdFormRef"></pi-certd-form>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
@@ -33,7 +32,6 @@ defineOptions({
|
||||
name: "PipelineManager",
|
||||
});
|
||||
|
||||
const certdFormRef = ref();
|
||||
const groupDictRef = dict({
|
||||
url: "/pi/pipeline/group/all",
|
||||
value: "id",
|
||||
@@ -41,7 +39,6 @@ const groupDictRef = dict({
|
||||
});
|
||||
const selectedRowKeys = ref([]);
|
||||
const context: any = {
|
||||
certdFormRef,
|
||||
groupDictRef,
|
||||
selectedRowKeys,
|
||||
};
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
<a-tab-pane v-for="item of detail.nodes" :key="item.node.id">
|
||||
<template #tab>
|
||||
<div class="tab-title flex-between" :title="item.node.title">
|
||||
<span class="tab-title-text flex items-center md:w-40">
|
||||
<span class="tab-title-text flex items-center md:w-48">
|
||||
<pi-status-show class="mr-1" :status="item.node.status?.result" type="icon"></pi-status-show>
|
||||
<!-- <fs-icon icon="ion:chevron-forward-circle" class="text-md mr-1"></fs-icon>-->
|
||||
{{ item.node.title }}
|
||||
<span class="flex-1 ellipsis">{{ item.node.title }}</span>
|
||||
|
||||
<a-tooltip title="强制重新执行此步骤">
|
||||
<fs-icon class="pointer color-blue ml-1" style="font-size: 16px" title="强制重新执行此步骤" icon="icon-park-outline:replay-music" @click="triggerRun(item.node.id)"></fs-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -31,13 +35,14 @@ export default {
|
||||
name: "PiTaskView",
|
||||
components: { PiStatusShow },
|
||||
props: {},
|
||||
emits: ["run"],
|
||||
setup(props: any, ctx: any) {
|
||||
const taskModal = ref({
|
||||
open: false,
|
||||
onOk() {
|
||||
taskViewClose();
|
||||
},
|
||||
cancelText: "关闭"
|
||||
cancelText: "关闭",
|
||||
});
|
||||
const { isMobile } = usePreferences();
|
||||
const tabPosition = computed(() => {
|
||||
@@ -65,7 +70,7 @@ export default {
|
||||
node: step,
|
||||
type: "步骤",
|
||||
tab: 2,
|
||||
logs: []
|
||||
logs: [],
|
||||
});
|
||||
}
|
||||
for (let node of nodes) {
|
||||
@@ -82,7 +87,7 @@ export default {
|
||||
list.push({
|
||||
time,
|
||||
content,
|
||||
color
|
||||
color,
|
||||
});
|
||||
}
|
||||
return list;
|
||||
@@ -111,12 +116,12 @@ export default {
|
||||
if (isBottom && el) {
|
||||
el?.scrollTo({
|
||||
top: el.scrollHeight,
|
||||
behavior: "smooth"
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -135,15 +140,21 @@ export default {
|
||||
taskModal.value.open = false;
|
||||
};
|
||||
|
||||
function triggerRun(id: string) {
|
||||
ctx.emit("run", id);
|
||||
taskModal.value.open = false;
|
||||
}
|
||||
|
||||
return {
|
||||
detail,
|
||||
taskModal,
|
||||
activeKey,
|
||||
taskViewOpen,
|
||||
taskViewClose,
|
||||
tabPosition
|
||||
tabPosition,
|
||||
triggerRun,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -154,7 +165,7 @@ export default {
|
||||
|
||||
.tab-title-text {
|
||||
display: flex;
|
||||
max-width: 180px;
|
||||
//max-width: 180px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
|
||||
<pi-task-form ref="taskFormRef" :edit-mode="editMode"></pi-task-form>
|
||||
<pi-trigger-form ref="triggerFormRef" :edit-mode="editMode"></pi-trigger-form>
|
||||
<pi-task-view ref="taskViewRef"></pi-task-view>
|
||||
<pi-task-view ref="taskViewRef" @run="run"></pi-task-view>
|
||||
<PiNotificationForm ref="notificationFormRef" :edit-mode="editMode"></PiNotificationForm>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
@@ -2,8 +2,8 @@ import { forEach } from "lodash-es";
|
||||
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
//@ts-ignore
|
||||
import forge from "node-forge";
|
||||
import { ReadCertDetail } from "./api";
|
||||
import { util } from "/@/utils";
|
||||
export function eachStages(list: any[], exec: (item: any, runnableType: string) => void, runnableType: string = "stage") {
|
||||
if (!list || list.length <= 0) {
|
||||
return;
|
||||
@@ -70,33 +70,22 @@ export async function checkPipelineLimit() {
|
||||
}
|
||||
}
|
||||
|
||||
export function readCertDetail(crt: string) {
|
||||
const detail = forge.pki.certificateFromPem(crt);
|
||||
const expires = detail.notAfter;
|
||||
return { detail, expires };
|
||||
export async function readCertDetail(crt: string) {
|
||||
const cached = await util.cache.get(crt);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const res = await ReadCertDetail(crt);
|
||||
await util.cache.set(crt, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getAllDomainsFromCrt(crt: string) {
|
||||
const { detail } = readCertDetail(crt);
|
||||
const domains = [];
|
||||
|
||||
// 1. 提取SAN中的DNS名称
|
||||
const sanExtension = detail.extensions.find((ext: any) => ext.name === "subjectAltName");
|
||||
if (sanExtension) {
|
||||
sanExtension.altNames.forEach((altName: any) => {
|
||||
if (altName.type === 2) {
|
||||
// type=2 表示DNS名称
|
||||
domains.push(altName.value);
|
||||
}
|
||||
});
|
||||
export async function getAllDomainsFromCrt(crt: string) {
|
||||
const { detail } = await readCertDetail(crt);
|
||||
const altNames = detail.domains.altNames;
|
||||
const commonName = detail.domains.commonName;
|
||||
if (altNames.includes(commonName)) {
|
||||
return altNames;
|
||||
}
|
||||
|
||||
// 2. 如果没有SAN,回退到CN(通用名称)
|
||||
if (domains.length === 0) {
|
||||
const cnAttr = detail.subject.attributes.find((attr: any) => attr.name === "commonName");
|
||||
if (cnAttr) {
|
||||
domains.push(cnAttr.value);
|
||||
}
|
||||
}
|
||||
return domains;
|
||||
return [commonName, ...altNames];
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// @ts-ignore
|
||||
import { request } from "/@/api/service";
|
||||
const apiPrefix = "/user/settings";
|
||||
export type UserSettings = {
|
||||
defaultNotification?: number;
|
||||
defaultCron?: string;
|
||||
};
|
||||
|
||||
export async function UserSettingsGet() {
|
||||
const res = await request({
|
||||
url: apiPrefix + "/getDefault",
|
||||
method: "post",
|
||||
});
|
||||
if (!res) {
|
||||
return {};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function UserSettingsSave(setting: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/saveDefault",
|
||||
method: "post",
|
||||
data: setting,
|
||||
});
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<fs-page class="page-user-settings">
|
||||
<template #header>
|
||||
<div class="title">设置</div>
|
||||
</template>
|
||||
<div class="user-settings-form settings-form">
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="basic"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
autocomplete="off"
|
||||
@finish="onFinish"
|
||||
@finish-failed="onFinishFailed"
|
||||
>
|
||||
<a-form-item label="默认定时设置" name="defaultCron">
|
||||
<notification-selector v-model="formState.defaultCron" />
|
||||
<div class="helper">创建流水线时默认使用此定时时间</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button :loading="saveLoading" type="primary" html-type="submit">保存</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref } from "vue";
|
||||
import * as api from "./api";
|
||||
import { UserSettings } from "./api";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { merge } from "lodash-es";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "UserSettings"
|
||||
});
|
||||
|
||||
const formState = reactive<Partial<UserSettings>>({});
|
||||
|
||||
async function loadUserSettings() {
|
||||
const data: any = await api.UserSettingsGet();
|
||||
merge(formState, data);
|
||||
}
|
||||
|
||||
const saveLoading = ref(false);
|
||||
loadUserSettings();
|
||||
const onFinish = async (form: any) => {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await api.UserSettingsSave(form);
|
||||
notification.success({
|
||||
message: "保存成功"
|
||||
});
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
// console.log("Failed:", errorInfo);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-user-settings {
|
||||
.user-settings-form {
|
||||
width: 500px;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,6 @@
|
||||
<template>
|
||||
<div class="main login-page">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
class="user-layout-login"
|
||||
name="custom-validation"
|
||||
:model="formState"
|
||||
v-bind="layout"
|
||||
@finish="handleFinish"
|
||||
@finish-failed="handleFinishFailed"
|
||||
>
|
||||
<a-form v-if="!twoFactor.loginId" ref="formRef" class="user-layout-login" name="custom-validation" :model="formState" v-bind="layout" @finish="handleFinish" @finish-failed="handleFinishFailed">
|
||||
<!-- <div class="login-title">登录</div>-->
|
||||
<a-tabs v-model:active-key="formState.loginType" :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
|
||||
<a-tab-pane key="password" tab="密码登录" :disabled="sysPublicSettings.passwordLoginEnabled !== true">
|
||||
@@ -44,13 +36,7 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="smsCode" :rules="rules.smsCode">
|
||||
<sms-code
|
||||
v-model:value="formState.smsCode"
|
||||
:img-code="formState.imgCode"
|
||||
:mobile="formState.mobile"
|
||||
:phone-code="formState.phoneCode"
|
||||
:random-str="formState.randomStr"
|
||||
/>
|
||||
<sms-code v-model:value="formState.smsCode" :img-code="formState.imgCode" :mobile="formState.mobile" :phone-code="formState.phoneCode" :random-str="formState.randomStr" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
@@ -63,6 +49,23 @@
|
||||
<router-link v-if="hasRegisterTypeEnabled()" class="register" :to="{ name: 'register' }"> 注册 </router-link>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form v-else ref="twoFactorFormRef" class="user-layout-login" :model="twoFactor" v-bind="layout">
|
||||
<div class="mb-10 flex flex-center">请打开您的Authenticator APP,获取动态验证码。</div>
|
||||
<a-form-item name="verifyCode">
|
||||
<a-input v-model:value="twoFactor.verifyCode" placeholder="请输入动态验证码" allow-clear>
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<loading-button type="primary" size="large" html-type="button" class="login-button" :click="handleTwoFactorSubmit">OTP验证登录</loading-button>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="user-login-other">
|
||||
<a class="register" @click="twoFactor.loginId = null"> 返回 </a>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
@@ -89,42 +92,51 @@ export default defineComponent({
|
||||
loginType: "password", //password
|
||||
imgCode: "",
|
||||
smsCode: "",
|
||||
randomStr: ""
|
||||
randomStr: "",
|
||||
});
|
||||
|
||||
const rules = {
|
||||
mobile: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入手机号"
|
||||
}
|
||||
message: "请输入手机号",
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入用户名"
|
||||
}
|
||||
message: "请输入用户名",
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入登录密码"
|
||||
}
|
||||
message: "请输入登录密码",
|
||||
},
|
||||
],
|
||||
smsCode: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入短信验证码"
|
||||
}
|
||||
]
|
||||
message: "请输入短信验证码",
|
||||
},
|
||||
],
|
||||
};
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 0
|
||||
span: 0,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 24
|
||||
}
|
||||
span: 24,
|
||||
},
|
||||
};
|
||||
|
||||
const twoFactor = reactive({
|
||||
loginId: "",
|
||||
verifyCode: "",
|
||||
});
|
||||
|
||||
const handleTwoFactorSubmit = async () => {
|
||||
await userStore.loginByTwoFactor(twoFactor);
|
||||
};
|
||||
|
||||
const handleFinish = async (values: any) => {
|
||||
@@ -132,6 +144,14 @@ export default defineComponent({
|
||||
try {
|
||||
const loginType = formState.loginType;
|
||||
await userStore.login(loginType, toRaw(formState));
|
||||
} catch (e: any) {
|
||||
//@ts-ignore
|
||||
if (e.code === 10020) {
|
||||
//@ts-ignore
|
||||
twoFactor.loginId = e.data;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -163,9 +183,11 @@ export default defineComponent({
|
||||
resetForm,
|
||||
isLoginError,
|
||||
sysPublicSettings,
|
||||
hasRegisterTypeEnabled
|
||||
hasRegisterTypeEnabled,
|
||||
twoFactor,
|
||||
handleTwoFactorSubmit,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ onMounted(() => {
|
||||
iframeClient.register("updateLicense", async req => {
|
||||
await api.UpdateLicense(req.data);
|
||||
await settingStore.init();
|
||||
await settingStore.doBindUrl();
|
||||
notification.success({
|
||||
message: "更新成功",
|
||||
description: "专业版/商业版已激活",
|
||||
|
||||
@@ -66,34 +66,34 @@ import { useSettingStore } from "/@/store/settings";
|
||||
import { notification } from "ant-design-vue";
|
||||
|
||||
defineOptions({
|
||||
name: "SettingRegister"
|
||||
name: "SettingRegister",
|
||||
});
|
||||
|
||||
const testMobile = ref("");
|
||||
async function testSendSms() {
|
||||
if (!testMobile.value) {
|
||||
notification.error({
|
||||
message: "请输入测试手机号"
|
||||
message: "请输入测试手机号",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await api.TestSms({
|
||||
mobile: testMobile.value
|
||||
mobile: testMobile.value,
|
||||
});
|
||||
notification.success({
|
||||
message: "发送成功"
|
||||
message: "发送成功",
|
||||
});
|
||||
}
|
||||
const formState = reactive<Partial<SysSettings>>({
|
||||
public: {
|
||||
registerEnabled: false
|
||||
registerEnabled: false,
|
||||
},
|
||||
private: {
|
||||
sms: {
|
||||
type: "aliyun",
|
||||
config: {}
|
||||
}
|
||||
}
|
||||
config: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const rules = {
|
||||
@@ -103,13 +103,13 @@ const rules = {
|
||||
return Promise.reject("密码登录和手机号登录至少开启一个");
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
},
|
||||
required: {
|
||||
required: true,
|
||||
trigger: "change",
|
||||
message: "此项必填"
|
||||
}
|
||||
message: "此项必填",
|
||||
},
|
||||
};
|
||||
|
||||
async function smsTypeChange(value: string) {
|
||||
@@ -124,13 +124,13 @@ async function loadTypeDefine(type: string) {
|
||||
const define: any = await api.GetSmsTypeDefine(type);
|
||||
const keys = Object.keys(define.input);
|
||||
const inputs: any = {};
|
||||
keys.forEach((key) => {
|
||||
keys.forEach(key => {
|
||||
const value = define.input[key];
|
||||
value.simpleKey = key;
|
||||
value.key = "private.sms.config." + key;
|
||||
if (!value.component) {
|
||||
value.component = {
|
||||
name: "a-input"
|
||||
name: "a-input",
|
||||
};
|
||||
}
|
||||
if (!value.component.name) {
|
||||
@@ -165,7 +165,7 @@ const onFinish = async (form: any) => {
|
||||
await api.SysSettingsSave(form);
|
||||
await settingsStore.loadSysSettings();
|
||||
notification.success({
|
||||
message: "保存成功"
|
||||
message: "保存成功",
|
||||
});
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
<a-form ref="formRef" :model="formState" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<h2>站点隐藏</h2>
|
||||
<a-form-item label="启用站点隐藏" :name="['hidden', 'enabled']" :required="true">
|
||||
<a-switch v-model:checked="formState.hidden.enabled" />
|
||||
<div class="flex">
|
||||
<a-switch v-model:checked="formState.hidden.enabled" />
|
||||
</div>
|
||||
|
||||
<div class="helper">
|
||||
可以在平时关闭站点的可访问性,需要时再打开,增强站点安全性
|
||||
<a href="https://certd.docmirror.cn/guide/feature/safe/hidden" class="flex items-center" target="_blank">
|
||||
@@ -52,10 +55,11 @@ import { merge } from "lodash-es";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { request } from "/@/api/service";
|
||||
import { util, utils } from "/@/utils";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
defineOptions({
|
||||
name: "SettingSafe",
|
||||
});
|
||||
|
||||
const settingsStore = useSettingStore();
|
||||
const api = {
|
||||
async SettingGet() {
|
||||
return await request({
|
||||
|
||||
@@ -3,6 +3,27 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 上传商用证书,直接粘贴文本报错的问题;修复无法上传ec加密证书的bug ([5750bb7](https://github.com/certd/certd/commit/5750bb706779da274d8e7a87e71416cb64d2df79))
|
||||
* 修复下载证书时提示token已过期的问题 ([0e07ae6](https://github.com/certd/certd/commit/0e07ae6ce84dcb9279d3c44060d621566afa593c))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 新增部署到火山引擎ALB/CLB、上传到证书中心 ([c9a3e3d](https://github.com/certd/certd/commit/c9a3e3d9d26f964c7af7b56667936f1414fbf42a))
|
||||
* 优化/api缓存为0 ([dc05cd4](https://github.com/certd/certd/commit/dc05cd481f186b13375192be965000e6b4b429a5))
|
||||
* 优化华为cdn插件引用ccm证书 ([b565b4b](https://github.com/certd/certd/commit/b565b4b3b919b71b98ea2517670bc1ef00e00dc9))
|
||||
|
||||
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 登录支持双重认证 ([48aef25](https://github.com/certd/certd/commit/48aef25b3f6499d674ca4e4ef16f4c62399fb735))
|
||||
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
|
||||
* 优化部署到华为云CDN,支持先上传到ccm,再使用证书id部署,修复offline状态下导致部署报错的bug ([79df39a](https://github.com/certd/certd/commit/79df39acabab10ae7e1864dadcdc186bb007a3c5))
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.33.4",
|
||||
"version": "1.33.6",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -38,19 +38,19 @@
|
||||
"@aws-sdk/client-acm": "^3.699.0",
|
||||
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||
"@aws-sdk/client-s3": "^3.705.0",
|
||||
"@certd/acme-client": "^1.33.4",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/commercial-core": "^1.33.4",
|
||||
"@certd/jdcloud": "^1.33.4",
|
||||
"@certd/lib-huawei": "^1.33.4",
|
||||
"@certd/lib-k8s": "^1.33.4",
|
||||
"@certd/lib-server": "^1.33.4",
|
||||
"@certd/midway-flyway-js": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/plugin-cert": "^1.33.4",
|
||||
"@certd/plugin-lib": "^1.33.4",
|
||||
"@certd/plugin-plus": "^1.33.4",
|
||||
"@certd/plus-core": "^1.33.4",
|
||||
"@certd/acme-client": "^1.33.6",
|
||||
"@certd/basic": "^1.33.6",
|
||||
"@certd/commercial-core": "^1.33.6",
|
||||
"@certd/jdcloud": "^1.33.6",
|
||||
"@certd/lib-huawei": "^1.33.6",
|
||||
"@certd/lib-k8s": "^1.33.6",
|
||||
"@certd/lib-server": "^1.33.6",
|
||||
"@certd/midway-flyway-js": "^1.33.6",
|
||||
"@certd/pipeline": "^1.33.6",
|
||||
"@certd/plugin-cert": "^1.33.6",
|
||||
"@certd/plugin-lib": "^1.33.6",
|
||||
"@certd/plugin-plus": "^1.33.6",
|
||||
"@certd/plus-core": "^1.33.6",
|
||||
"@corsinvest/cv4pve-api-javascript": "^8.3.0",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||
@@ -97,9 +97,11 @@
|
||||
"nanoid": "^5.0.7",
|
||||
"node-forge": "^1.3.1",
|
||||
"nodemailer": "^6.9.16",
|
||||
"otplib": "^12.0.1",
|
||||
"pg": "^8.12.0",
|
||||
"psl": "^1.9.0",
|
||||
"qiniu": "^7.12.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"qs": "^6.13.1",
|
||||
"querystring": "^0.2.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
||||
53
packages/ui/certd-server/plugin-doc-gen.mjs
Normal file
53
packages/ui/certd-server/plugin-doc-gen.mjs
Normal file
@@ -0,0 +1,53 @@
|
||||
// 扫描目录,列出文件,然后加载为模块
|
||||
|
||||
import { join } from 'path';
|
||||
import fs from 'fs'
|
||||
import { pathToFileURL } from "node:url";
|
||||
import path from 'path'
|
||||
function scanDir(dir) {
|
||||
const files = fs.readdirSync(dir);
|
||||
const result = [];
|
||||
// 扫描目录及子目录
|
||||
for (const file of files) {
|
||||
if (file.includes("index.js")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
result.push(...scanDir(filePath));
|
||||
} else {
|
||||
if (!file.endsWith(".js")) {
|
||||
continue;
|
||||
}
|
||||
result.push(filePath);
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export default async function loadModules(dir) {
|
||||
const files = scanDir(dir);
|
||||
const modules = {}
|
||||
for (const file of files) {
|
||||
|
||||
try {
|
||||
// 转换为 file:// URL(Windows 必需)
|
||||
const moduleUrl = pathToFileURL(file).href
|
||||
const module = await import(moduleUrl)
|
||||
|
||||
// 如果模块有默认导出,优先使用
|
||||
modules[file] = module.default || module
|
||||
} catch (err) {
|
||||
console.error(`加载模块 ${file} 失败:`, err)
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
const modules = await loadModules('./dist/plugins');
|
||||
|
||||
for (const key in modules) {
|
||||
console.log(key)
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class MainConfiguration {
|
||||
|
||||
this.app.getMiddleware().insertFirst(async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
||||
await next();
|
||||
if (ctx.path === '/' || ctx.path === '/index.html') {
|
||||
if (ctx.path === '/' || ctx.path === '/index.html' || ctx.path.startsWith("/api")) {
|
||||
ctx.response.set('Cache-Control', 'public,max-age=0');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {ALL, Body, Controller, Inject, Post, Provide} from '@midwayjs/core';
|
||||
import {BaseController, SysSafeSetting} from '@certd/lib-server';
|
||||
import {cloneDeep} from 'lodash-es';
|
||||
import {SafeService} from "../../../modules/sys/settings/safe-service.js";
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
|
||||
import { BaseController, SysSafeSetting } from "@certd/lib-server";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { SafeService } from "../../../modules/sys/settings/safe-service.js";
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,13 +23,16 @@ export class LoginController extends BaseController {
|
||||
user: any
|
||||
) {
|
||||
const token = await this.loginService.loginByPassword(user);
|
||||
this.ctx.cookies.set('token', token.token, {
|
||||
maxAge: 1000 * token.expire,
|
||||
});
|
||||
|
||||
this.writeTokenCookie(token);
|
||||
return this.ok(token);
|
||||
}
|
||||
|
||||
private writeTokenCookie(token: { expire: any; token: any }) {
|
||||
this.ctx.cookies.set("certd_token", token.token, {
|
||||
maxAge: 1000 * token.expire
|
||||
});
|
||||
}
|
||||
|
||||
@Post('/loginBySms', { summary: Constants.per.guest })
|
||||
public async loginBySms(
|
||||
@Body(ALL)
|
||||
@@ -48,13 +51,31 @@ export class LoginController extends BaseController {
|
||||
randomStr: body.randomStr,
|
||||
});
|
||||
|
||||
this.ctx.cookies.set('token', token.token, {
|
||||
maxAge: 1000 * token.expire,
|
||||
});
|
||||
this.writeTokenCookie(token);
|
||||
|
||||
return this.ok(token);
|
||||
}
|
||||
|
||||
@Post('/loginByTwoFactor', { summary: Constants.per.guest })
|
||||
public async loginByTwoFactor(
|
||||
@Body(ALL)
|
||||
body: any
|
||||
) {
|
||||
|
||||
const token = await this.loginService.loginByTwoFactor({
|
||||
loginId: body.loginId,
|
||||
verifyCode: body.verifyCode,
|
||||
});
|
||||
|
||||
this.writeTokenCookie(token);
|
||||
return this.ok(token);
|
||||
}
|
||||
|
||||
@Post('/logout', { summary: Constants.per.authOnly })
|
||||
public logout() {}
|
||||
public logout() {
|
||||
this.ctx.cookies.set("certd_token", "", {
|
||||
maxAge: 0
|
||||
});
|
||||
return this.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
|
||||
import { BaseController, Constants } from "@certd/lib-server";
|
||||
import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js";
|
||||
import { UserTwoFactorSetting } from "../../../modules/mine/service/models.js";
|
||||
import { merge } from "lodash-es";
|
||||
import { TwoFactorService } from "../../../modules/mine/service/two-factor-service.js";
|
||||
import {isPlus} from "@certd/plus-core";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller("/api/user/settings/twoFactor")
|
||||
export class UserTwoFactorSettingController extends BaseController {
|
||||
@Inject()
|
||||
service: UserSettingsService;
|
||||
|
||||
@Inject()
|
||||
twoFactorService: TwoFactorService;
|
||||
|
||||
|
||||
|
||||
@Post("/get", { summary: Constants.per.authOnly })
|
||||
async get() {
|
||||
const userId = this.getUserId();
|
||||
const setting = await this.service.getSetting<UserTwoFactorSetting>(userId, UserTwoFactorSetting);
|
||||
return this.ok(setting);
|
||||
}
|
||||
|
||||
@Post("/save", { summary: Constants.per.authOnly })
|
||||
async save(@Body(ALL) bean: any) {
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通专业版')
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
const setting = new UserTwoFactorSetting();
|
||||
merge(setting, bean);
|
||||
|
||||
// 禁用时清除
|
||||
if(!setting.authenticator.enabled){
|
||||
setting.authenticator.secret = null;
|
||||
setting.authenticator.verified = false;
|
||||
}
|
||||
|
||||
await this.service.saveSetting(userId, setting);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
@Post("/authenticator/qrcode", { summary: Constants.per.authOnly })
|
||||
async authenticatorQrcode() {
|
||||
const userId = this.getUserId();
|
||||
const qrcode = await this.twoFactorService.getAuthenticatorQrCode(userId);
|
||||
return this.ok(qrcode);
|
||||
}
|
||||
|
||||
@Post("/authenticator/save", { summary: Constants.per.authOnly })
|
||||
async authenticatorSave(@Body(ALL) bean: any) {
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通专业版')
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
await this.twoFactorService.saveAuthenticator({
|
||||
userId,
|
||||
verifyCode: bean.verifyCode,
|
||||
});
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
@Post("/authenticator/off", { summary: Constants.per.authOnly })
|
||||
async authenticatorOff() {
|
||||
const userId = this.getUserId();
|
||||
await this.twoFactorService.offAuthenticator(userId);
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { CrudController } from '@certd/lib-server';
|
||||
import { Constants } from '@certd/lib-server';
|
||||
import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js';
|
||||
import { UserSettingsEntity } from '../../../modules/mine/entity/user-settings.js';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||
import { Constants, CrudController } from "@certd/lib-server";
|
||||
import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js";
|
||||
import { UserSettingsEntity } from "../../../modules/mine/entity/user-settings.js";
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -66,4 +65,6 @@ export class UserSettingsController extends CrudController<UserSettingsService>
|
||||
const entity = await this.service.getByKey(key, this.getUserId());
|
||||
return this.ok(entity);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import {Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core';
|
||||
import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js';
|
||||
import { BaseController, Constants } from '@certd/lib-server';
|
||||
import { StorageService } from '../../../modules/pipeline/service/storage-service.js';
|
||||
import {CertReader} from "@certd/plugin-cert";
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/pi/cert')
|
||||
@@ -18,4 +19,14 @@ export class CertController extends BaseController {
|
||||
const privateVars = await this.storeService.getPipelinePrivateVars(id);
|
||||
return this.ok(privateVars.cert);
|
||||
}
|
||||
|
||||
|
||||
@Post('/readCertDetail', { summary: Constants.per.authOnly })
|
||||
async readCertDetail(@Body('crt') crt: string) {
|
||||
if (!crt) {
|
||||
throw new Error('crt is required');
|
||||
}
|
||||
const certDetail = CertReader.readCertDetail(crt)
|
||||
return this.ok(certDetail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,20 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
||||
token = token.replace('Bearer ', '').trim();
|
||||
if (!token) {
|
||||
//尝试从cookie中获取token
|
||||
token = ctx.cookies.get('token') || '';
|
||||
const cookie = ctx.headers.cookie;
|
||||
if (cookie) {
|
||||
const items = cookie.split(';');
|
||||
for (const item of items) {
|
||||
if (!item || !item.trim()) {
|
||||
continue;
|
||||
}
|
||||
const [key, value] = item.split('=');
|
||||
if (key.trim() === 'certd_token') {
|
||||
token = value.trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!token) {
|
||||
//尝试从query中获取token
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
|
||||
import { logger } from '@certd/basic';
|
||||
import { Result } from '@certd/lib-server';
|
||||
import { Result, TextException } from "@certd/lib-server";
|
||||
|
||||
@Provide()
|
||||
export class GlobalExceptionMiddleware implements IWebMiddleware {
|
||||
@@ -14,12 +14,15 @@ export class GlobalExceptionMiddleware implements IWebMiddleware {
|
||||
await next();
|
||||
logger.info('请求完成:', url, Date.now() - startTime + 'ms');
|
||||
} catch (err) {
|
||||
if(err instanceof TextException){
|
||||
delete err.stack
|
||||
}
|
||||
logger.error('请求异常:', url, Date.now() - startTime + 'ms', err);
|
||||
ctx.status = 200;
|
||||
if (err.code == null || typeof err.code !== 'number') {
|
||||
err.code = 1;
|
||||
}
|
||||
ctx.body = Result.error(err.code, err.message);
|
||||
ctx.body = Result.error(err.code, err.message,err.data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import {Config, Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {UserService} from '../../sys/authority/service/user-service.js';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import {CommonException} from '@certd/lib-server';
|
||||
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} from '@certd/basic';
|
||||
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";
|
||||
|
||||
/**
|
||||
* 系统用户
|
||||
@@ -28,6 +31,10 @@ export class LoginService {
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
@Inject()
|
||||
userSettingsService: UserSettingsService;
|
||||
@Inject()
|
||||
twoFactorService: TwoFactorService;
|
||||
|
||||
checkIsBlocked(username: string) {
|
||||
const blockDurationKey = `login_block_duration:${username}`;
|
||||
@@ -138,21 +145,56 @@ export class LoginService {
|
||||
return this.onLoginSuccess(info);
|
||||
}
|
||||
|
||||
private async onLoginSuccess(info: UserEntity) {
|
||||
async checkTwoFactorEnabled(userId: number) {
|
||||
//检查是否开启多重认证
|
||||
if (!isPlus()) {
|
||||
return true
|
||||
}
|
||||
|
||||
const twoFactorSetting = await this.twoFactorService.getSetting(userId)
|
||||
|
||||
const authenticatorSetting = twoFactorSetting.authenticator
|
||||
if (authenticatorSetting.enabled) {
|
||||
//要检查
|
||||
const randomKey = utils.id.simpleNanoId(12)
|
||||
cache.set(`login_2fa_code:${randomKey}`, userId, {
|
||||
ttl: 60 * 1000 * 2,
|
||||
})
|
||||
throw new Need2FAException('已开启多重认证,请在2分钟内输入OPT验证码',randomKey)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async loginByTwoFactor(req: { loginId: string; verifyCode: string }) {
|
||||
//检查是否开启多重认证
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通专业版')
|
||||
}
|
||||
const userId = cache.get(`login_2fa_code:${req.loginId}`)
|
||||
if (!userId) {
|
||||
throw new AuthException('已超时,请返回重新登录')
|
||||
}
|
||||
await this.twoFactorService.verifyAuthenticatorCode(userId, req.verifyCode)
|
||||
|
||||
return this.generateToken(await this.userService.findOne(userId))
|
||||
}
|
||||
|
||||
private async onLoginSuccess(info: UserEntity) {
|
||||
if (info.status === 0) {
|
||||
throw new CommonException('用户已被禁用');
|
||||
}
|
||||
const roleIds = await this.roleService.getRoleIdsByUserId(info.id);
|
||||
return this.generateToken(info, roleIds);
|
||||
await this.checkTwoFactorEnabled(info.id)
|
||||
return this.generateToken(info);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成token
|
||||
* @param user 用户对象
|
||||
* @param roleIds
|
||||
*/
|
||||
async generateToken(user: UserEntity, roleIds: number[]) {
|
||||
async generateToken(user: UserEntity) {
|
||||
const roleIds = await this.roleService.getRoleIdsByUserId(user.id);
|
||||
const tokenInfo = {
|
||||
username: user.username,
|
||||
id: user.id,
|
||||
|
||||
21
packages/ui/certd-server/src/modules/mine/service/models.ts
Normal file
21
packages/ui/certd-server/src/modules/mine/service/models.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { BaseSettings } from "@certd/lib-server";
|
||||
|
||||
export type TwoFactorAuthenticator = {
|
||||
enabled: boolean;
|
||||
secret?: string;
|
||||
type?: string;
|
||||
verified?:boolean;
|
||||
}
|
||||
|
||||
export class UserTwoFactorSetting extends BaseSettings {
|
||||
static __title__ = "用户多重认证设置";
|
||||
static __key__ = "user.two.factor";
|
||||
|
||||
authenticator: TwoFactorAuthenticator = {
|
||||
enabled:false,
|
||||
verified:false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { UserSettingsService } from "./user-settings-service.js";
|
||||
import { UserTwoFactorSetting } from "./models.js";
|
||||
import { UserService } from "../../sys/authority/service/user-service.js";
|
||||
|
||||
/**
|
||||
* 授权
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class TwoFactorService {
|
||||
@Inject()
|
||||
userSettingsService: UserSettingsService;
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
|
||||
async getAuthenticatorQrCode(userId: any) {
|
||||
const setting = await this.getSetting(userId)
|
||||
|
||||
const authenticatorSetting = setting.authenticator;
|
||||
if (!authenticatorSetting.secret) {
|
||||
const { authenticator } = await import("otplib");
|
||||
|
||||
authenticatorSetting.secret = authenticator.generateSecret()
|
||||
await this.userSettingsService.saveSetting(userId, setting);
|
||||
}
|
||||
|
||||
const user = await this.userService.info(userId);
|
||||
const username = user.username;
|
||||
const secret = authenticatorSetting.secret;
|
||||
const qrcodeContent = `otpauth://totp/Certd:${username}?secret=${secret}&issuer=Certd`;
|
||||
|
||||
//生成qrcode base64
|
||||
const qrcode = await import("qrcode");
|
||||
return await qrcode.toDataURL(qrcodeContent);
|
||||
|
||||
}
|
||||
|
||||
async saveAuthenticator(req: { userId: any; verifyCode: any }) {
|
||||
const userId = req.userId;
|
||||
const { authenticator } = await import("otplib");
|
||||
const setting = await this.getSetting(userId)
|
||||
|
||||
const authenticatorSetting = setting.authenticator;
|
||||
if (!authenticatorSetting.secret) {
|
||||
throw new Error("secret is required");
|
||||
}
|
||||
const secret = authenticatorSetting.secret;
|
||||
const token = req.verifyCode;
|
||||
|
||||
const isValid = authenticator.verify({ token, secret });
|
||||
if (!isValid) {
|
||||
throw new Error("authenticator 校验错误");
|
||||
}
|
||||
|
||||
//校验成功,保存开启状态
|
||||
authenticatorSetting.enabled = true;
|
||||
authenticatorSetting.verified = true;
|
||||
|
||||
await this.userSettingsService.saveSetting(userId, setting);
|
||||
}
|
||||
|
||||
async offAuthenticator(userId:number) {
|
||||
if (!userId) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
|
||||
const setting = await this.getSetting(userId)
|
||||
setting.authenticator.enabled = false;
|
||||
setting.authenticator.verified = false;
|
||||
setting.authenticator.secret = '';
|
||||
await this.userSettingsService.saveSetting(userId, setting);
|
||||
}
|
||||
|
||||
async getSetting(userId:number) {
|
||||
return await this.userSettingsService.getSetting<UserTwoFactorSetting>(userId, UserTwoFactorSetting);
|
||||
|
||||
}
|
||||
|
||||
async verifyAuthenticatorCode(userId: any, verifyCode: string) {
|
||||
const { authenticator } = await import("otplib");
|
||||
const setting = await this.getSetting(userId)
|
||||
if (!setting.authenticator.enabled) {
|
||||
throw new Error("authenticator 未开启");
|
||||
}
|
||||
if (!authenticator.verify({ token: verifyCode, secret: setting.authenticator.secret })) {
|
||||
throw new Error("验证码错误");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService } from '@certd/lib-server';
|
||||
import { UserSettingsEntity } from '../entity/user-settings.js';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { BaseService, BaseSettings } from "@certd/lib-server";
|
||||
import { UserSettingsEntity } from "../entity/user-settings.js";
|
||||
import { merge } from "lodash-es";
|
||||
|
||||
/**
|
||||
* 授权
|
||||
@@ -27,23 +28,29 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
const setting = JSON.parse(entity.setting);
|
||||
return {
|
||||
id: entity.id,
|
||||
...setting,
|
||||
...setting
|
||||
};
|
||||
}
|
||||
|
||||
async getByKey(key: string, userId: number): Promise<UserSettingsEntity | null> {
|
||||
if(!userId){
|
||||
throw new Error('userId is required');
|
||||
}
|
||||
if (!key || !userId) {
|
||||
return null;
|
||||
}
|
||||
return await this.repository.findOne({
|
||||
where: {
|
||||
key,
|
||||
userId,
|
||||
},
|
||||
userId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getSettingByKey(key: string, userId: number): Promise<any | null> {
|
||||
if(!userId){
|
||||
throw new Error('userId is required');
|
||||
}
|
||||
const entity = await this.getByKey(key, userId);
|
||||
if (!entity) {
|
||||
return null;
|
||||
@@ -55,8 +62,8 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
const entity = await this.repository.findOne({
|
||||
where: {
|
||||
key: bean.key,
|
||||
userId: bean.userId,
|
||||
},
|
||||
userId: bean.userId
|
||||
}
|
||||
});
|
||||
if (entity) {
|
||||
entity.setting = bean.setting;
|
||||
@@ -66,4 +73,42 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
await this.repository.save(bean);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getSetting<T>( userId: number,type: any): Promise<T> {
|
||||
if(!userId){
|
||||
throw new Error('userId is required');
|
||||
}
|
||||
const key = type.__key__;
|
||||
let newSetting: T = new type();
|
||||
const savedSettings = await this.getSettingByKey(key, userId);
|
||||
newSetting = merge(newSetting, savedSettings);
|
||||
return newSetting;
|
||||
}
|
||||
|
||||
async saveSetting<T extends BaseSettings>(userId:number,bean: T) {
|
||||
if(!userId){
|
||||
throw new Error('userId is required');
|
||||
}
|
||||
const old = await this.getSetting(userId,bean.constructor)
|
||||
bean = merge(old,bean)
|
||||
|
||||
const type: any = bean.constructor;
|
||||
const key = type.__key__;
|
||||
if(!key){
|
||||
throw new Error(`${type.name} must have __key__`);
|
||||
}
|
||||
const entity = await this.getByKey(key,userId);
|
||||
const newEntity = new UserSettingsEntity();
|
||||
if (entity) {
|
||||
newEntity.id = entity.id;
|
||||
}else{
|
||||
newEntity.key = key;
|
||||
newEntity.title = type.__title__;
|
||||
newEntity.userId = userId;
|
||||
}
|
||||
newEntity.setting = JSON.stringify(bean);
|
||||
await this.repository.save(newEntity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
|
||||
export class HauweiDeployCertToCDN extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: '域名证书',
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
helper: '请选择前置任务输出的域名证书\n如果你选择使用ccm证书ID,则需要在[域名管理页面右上角开启SCM授权](https://console.huaweicloud.com/cdn/#/cdn/domain)',
|
||||
component: {
|
||||
name: 'output-selector',
|
||||
from: [...CertApplyPluginNames],
|
||||
from: [...CertApplyPluginNames,'HauweiUploadToCCM'],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
cert!: CertInfo | string;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
@@ -53,14 +53,26 @@ export class HauweiDeployCertToCDN extends AbstractTaskPlugin {
|
||||
domains!: string[];
|
||||
|
||||
async execute(): Promise<void> {
|
||||
if (!this.cert) {
|
||||
throw new Error('域名证书不能为空');
|
||||
}
|
||||
this.logger.info('开始部署证书到华为云cdn');
|
||||
const { cdn, client } = await this.getCdnClient();
|
||||
const httpsConfig = new cdn.HttpPutBody()
|
||||
let httpsConfig = new cdn.HttpPutBody()
|
||||
.withHttpsStatus('on')
|
||||
.withCertificateType('server')
|
||||
.withCertificateName(this.appendTimeSuffix('certd'))
|
||||
.withCertificateValue(this.cert.crt)
|
||||
.withPrivateKey(this.cert.key);
|
||||
|
||||
if(typeof this.cert === 'object'){
|
||||
httpsConfig= httpsConfig.withCertificateSource(0)
|
||||
.withCertificateName(this.appendTimeSuffix('certd'))
|
||||
.withCertificateValue(this.cert.crt)
|
||||
.withPrivateKey(this.cert.key);
|
||||
}else{
|
||||
this.logger.info('使用已有域名证书:', this.cert);
|
||||
httpsConfig= httpsConfig.withCertificateSource(2)//scm证书
|
||||
.withCertificateName(this.appendTimeSuffix('certd'))
|
||||
.withScmCertificateId(this.cert)
|
||||
}
|
||||
|
||||
const config = new cdn.Configs().withHttps(httpsConfig);
|
||||
const body = new cdn.ModifyDomainConfigRequestBody().withConfigs(config);
|
||||
@@ -70,9 +82,28 @@ export class HauweiDeployCertToCDN extends AbstractTaskPlugin {
|
||||
this.logger.info('部署域名:', JSON.stringify(this.domains));
|
||||
for (const domain of this.domains) {
|
||||
this.logger.info('部署到域名:', domain);
|
||||
const req = new cdn.UpdateDomainFullConfigRequest().withDomainName(domain).withBody(body);
|
||||
await client.updateDomainFullConfig(req);
|
||||
this.logger.info(`部署到域名${domain}完成:`);
|
||||
|
||||
const queryReq = new cdn.ShowDomainDetailByNameRequest(domain);
|
||||
const domainDetail = await client.showDomainDetailByName(queryReq);
|
||||
//@ts-ignore
|
||||
const status = domainDetail.domain.domainStatus || domainDetail.domain.domain_status
|
||||
this.logger.info(`当前域名状态:`, status);
|
||||
let ignoreError = false
|
||||
if (status === 'offline') {
|
||||
ignoreError = true
|
||||
}
|
||||
try{
|
||||
const req = new cdn.UpdateDomainFullConfigRequest().withDomainName(domain).withBody(body);
|
||||
await client.updateDomainFullConfig(req);
|
||||
this.logger.info(`部署到域名${domain}完成:`);
|
||||
}catch (e) {
|
||||
if (ignoreError){
|
||||
this.logger.warn(`部署到域名${domain}失败,由于其处于offline状态,忽略部署错误,继续执行:`, e);
|
||||
}else{
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.logger.info('部署证书到华为云cdn完成');
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export * from './plugin-deploy-to-cdn.js'
|
||||
export * from './plugin-deploy-to-clb.js'
|
||||
export * from './plugin-upload-to-cert-center.js'
|
||||
export * from './plugin-deploy-to-alb.js'
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { VolcengineAccess } from "../access.js";
|
||||
import { VolcengineClient } from "../ve-client.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "VolcengineDeployToALB",
|
||||
title: "火山引擎-部署证书至ALB",
|
||||
icon: "svg:icon-volcengine",
|
||||
group: pluginGroups.volcengine.key,
|
||||
desc: "部署至火山引擎应用负载均衡",
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed
|
||||
}
|
||||
}
|
||||
})
|
||||
export class VolcengineDeployToALB extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames, "VolcengineUploadToCertCenter"]
|
||||
},
|
||||
required: true
|
||||
})
|
||||
cert!: CertInfo | string;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
|
||||
@TaskInput({
|
||||
title: "Access授权",
|
||||
helper: "火山引擎AccessKeyId、AccessKeySecret",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "volcengine"
|
||||
},
|
||||
required: true
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: "Region",
|
||||
helper: "地区选择",
|
||||
component: {
|
||||
name: "a-select",
|
||||
options: [
|
||||
/**
|
||||
* 中国地区
|
||||
* 华北2
|
||||
* 北京
|
||||
* cn-beijing
|
||||
* 4
|
||||
* 可用区A:cn-beijing-a
|
||||
* 可用区B:cn-beijing-b
|
||||
* 可用区C:cn-beijing-c
|
||||
* 可用区D:cn-beijing-d
|
||||
* 商用
|
||||
* 华东2
|
||||
* 上海
|
||||
* cn-shanghai
|
||||
* 4
|
||||
* 可用区A:cn-shanghai-a
|
||||
* 可用区B:cn-shanghai-b
|
||||
* 可用区C:cn-shanghai-c
|
||||
* 可用区E:cn-shanghai-e
|
||||
* 商用
|
||||
* 华南1
|
||||
* 广州
|
||||
* cn-guangzhou
|
||||
* 3
|
||||
* 可用区A:cn-guangzhou-a
|
||||
* 可用区B:cn-guangzhou-b
|
||||
* 可用区C:cn-guangzhou-c
|
||||
* 商用
|
||||
* 中国香港
|
||||
* 香港
|
||||
* cn-hongkong
|
||||
* 2
|
||||
* 可用区A:cn-hongkong-a
|
||||
* 可用区B:cn-hongkong-b
|
||||
* 商用
|
||||
* 其他国家和地区
|
||||
* 亚太东南
|
||||
* 柔佛
|
||||
* ap-southeast-1
|
||||
* 2
|
||||
* 可用区A:ap-southeast-1a
|
||||
* 可用区B:ap-southeast-1b
|
||||
* 商用
|
||||
* 雅加达
|
||||
* ap-southeast-3
|
||||
* 2
|
||||
* 可用区A:ap-southeast-3a
|
||||
* 可用区B:ap-southeast-3b
|
||||
* 商用
|
||||
*/
|
||||
{ label: "北京", value: "cn-beijing" },
|
||||
{ label: "上海", value: "cn-shanghai" },
|
||||
{ label: "广州", value: "cn-guangzhou" },
|
||||
{ label: "香港", value: "cn-hongkong" },
|
||||
{ label: "柔佛", value: "ap-southeast-1" },
|
||||
{ label: "雅加达", value: "ap-southeast-3" }
|
||||
|
||||
]
|
||||
},
|
||||
value: "cn-beijing",
|
||||
required: true
|
||||
})
|
||||
regionId!: string;
|
||||
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "监听器列表",
|
||||
helper: "选择要部署证书的监听器\n需要在监听器中选择证书中心,进行跨服务访问授权",
|
||||
action: VolcengineDeployToALB.prototype.onGetListenerList.name,
|
||||
watches: ["certDomains", "accessId", "regionId"],
|
||||
required: true
|
||||
})
|
||||
)
|
||||
listenerList!: string | string[];
|
||||
|
||||
|
||||
async onInstance() {
|
||||
}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info("开始部署证书到火山引擎ALB");
|
||||
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
||||
const certService = await this.getCertService(access);
|
||||
let certId = this.cert;
|
||||
if (typeof certId !== "string") {
|
||||
const certInfo = this.cert as CertInfo;
|
||||
this.logger.info(`开始上传证书`);
|
||||
certId = await certService.ImportCertificate({
|
||||
certName:this.appendTimeSuffix("certd"),
|
||||
cert:certInfo
|
||||
});
|
||||
this.logger.info(`上传证书成功:${certId}`);
|
||||
} else {
|
||||
this.logger.info(`使用已有证书ID:${certId}`);
|
||||
}
|
||||
|
||||
const service = await this.getAlbService();
|
||||
for (const listener of this.listenerList) {
|
||||
this.logger.info(`开始部署监听器${listener}证书`);
|
||||
await service.request({
|
||||
action: "ModifyListenerAttributes",
|
||||
query: {
|
||||
ListenerId: listener,
|
||||
CertificateSource: "cert_center",
|
||||
CertCenterCertificateId: certId
|
||||
}
|
||||
});
|
||||
this.logger.info(`部署监听器${listener}证书成功`);
|
||||
}
|
||||
|
||||
this.logger.info("部署完成");
|
||||
}
|
||||
|
||||
|
||||
private async getCertService(access: VolcengineAccess) {
|
||||
const client = new VolcengineClient({
|
||||
logger: this.logger,
|
||||
access,
|
||||
http: this.http
|
||||
});
|
||||
|
||||
return await client.getCertCenterService();
|
||||
}
|
||||
|
||||
private async getAlbService() {
|
||||
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
||||
|
||||
const client = new VolcengineClient({
|
||||
logger: this.logger,
|
||||
access,
|
||||
http: this.http
|
||||
});
|
||||
|
||||
const service = await client.getAlbService({
|
||||
region: this.regionId
|
||||
});
|
||||
return service;
|
||||
}
|
||||
|
||||
async onGetListenerList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error("请选择Access授权");
|
||||
}
|
||||
const service = await this.getAlbService();
|
||||
|
||||
const res = await service.request({
|
||||
action: "DescribeListeners",
|
||||
method: "GET",
|
||||
query: {
|
||||
PageSize: 100,
|
||||
Protocol: "HTTPS"
|
||||
},
|
||||
});
|
||||
|
||||
const list = res.Result.Listeners;
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("找不到HTTPS类型的负载均衡监听器,您也可以手动输入监听器ID");
|
||||
}
|
||||
return list.map((item: any) => {
|
||||
return {
|
||||
value: item.ListenerId,
|
||||
label: `${item.ListenerName}<${item.Description}:${item.ListenerId}>`
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new VolcengineDeployToALB();
|
||||
@@ -23,7 +23,7 @@ export class VolcengineDeployToCDN extends AbstractTaskPlugin {
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'output-selector',
|
||||
from: [...CertApplyPluginNames, 'VolcengineUploadCert'],
|
||||
from: [...CertApplyPluginNames, 'VolcengineUploadToCertCenter'],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
@@ -82,11 +82,17 @@ export class VolcengineDeployToCDN extends AbstractTaskPlugin {
|
||||
|
||||
const client = await this.getClient(access)
|
||||
const service = await client.getCdnClient()
|
||||
if (!this.cert) {
|
||||
throw new Error('你还未选择证书');
|
||||
}
|
||||
let certId = this.cert
|
||||
if (typeof certId !== 'string') {
|
||||
const certInfo = this.cert as CertInfo
|
||||
this.logger.info(`开始上传证书`)
|
||||
certId = await client.uploadCert(certInfo, this.appendTimeSuffix('certd'))
|
||||
this.logger.info(`上传证书成功:${certId}`);
|
||||
}else{
|
||||
this.logger.info(`使用已有证书ID:${certId}`);
|
||||
}
|
||||
|
||||
for (const domain of this.domainName) {
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { VolcengineAccess } from "../access.js";
|
||||
import { VolcengineClient } from "../ve-client.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "VolcengineDeployToCLB",
|
||||
title: "火山引擎-部署证书至CLB",
|
||||
icon: "svg:icon-volcengine",
|
||||
group: pluginGroups.volcengine.key,
|
||||
desc: "部署至火山引擎负载均衡",
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed
|
||||
}
|
||||
}
|
||||
})
|
||||
export class VolcengineDeployToCLB extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames, "VolcengineUploadToCertCenter"]
|
||||
},
|
||||
required: true
|
||||
})
|
||||
cert!: CertInfo | string;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
|
||||
@TaskInput({
|
||||
title: "Access授权",
|
||||
helper: "火山引擎AccessKeyId、AccessKeySecret",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "volcengine"
|
||||
},
|
||||
required: true
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: "Region",
|
||||
helper: "地区选择",
|
||||
component: {
|
||||
name: "a-select",
|
||||
options: [
|
||||
/**
|
||||
* 中国地区
|
||||
* 华北2
|
||||
* 北京
|
||||
* cn-beijing
|
||||
* 4
|
||||
* 可用区A:cn-beijing-a
|
||||
* 可用区B:cn-beijing-b
|
||||
* 可用区C:cn-beijing-c
|
||||
* 可用区D:cn-beijing-d
|
||||
* 商用
|
||||
* 华东2
|
||||
* 上海
|
||||
* cn-shanghai
|
||||
* 4
|
||||
* 可用区A:cn-shanghai-a
|
||||
* 可用区B:cn-shanghai-b
|
||||
* 可用区C:cn-shanghai-c
|
||||
* 可用区E:cn-shanghai-e
|
||||
* 商用
|
||||
* 华南1
|
||||
* 广州
|
||||
* cn-guangzhou
|
||||
* 3
|
||||
* 可用区A:cn-guangzhou-a
|
||||
* 可用区B:cn-guangzhou-b
|
||||
* 可用区C:cn-guangzhou-c
|
||||
* 商用
|
||||
* 中国香港
|
||||
* 香港
|
||||
* cn-hongkong
|
||||
* 2
|
||||
* 可用区A:cn-hongkong-a
|
||||
* 可用区B:cn-hongkong-b
|
||||
* 商用
|
||||
* 其他国家和地区
|
||||
* 亚太东南
|
||||
* 柔佛
|
||||
* ap-southeast-1
|
||||
* 2
|
||||
* 可用区A:ap-southeast-1a
|
||||
* 可用区B:ap-southeast-1b
|
||||
* 商用
|
||||
* 雅加达
|
||||
* ap-southeast-3
|
||||
* 2
|
||||
* 可用区A:ap-southeast-3a
|
||||
* 可用区B:ap-southeast-3b
|
||||
* 商用
|
||||
*/
|
||||
{ label: "北京", value: "cn-beijing" },
|
||||
{ label: "上海", value: "cn-shanghai" },
|
||||
{ label: "广州", value: "cn-guangzhou" },
|
||||
{ label: "深圳", value: "cn-shenzhen" },
|
||||
{ label: "杭州", value: "cn-hangzhou" },
|
||||
{ label: "南京", value: "cn-north-1" },
|
||||
{ label: "青岛", value: "cn-qingdao" },
|
||||
{ label: "重庆", value: "cn-chengdu" },
|
||||
{ label: "香港", value: "cn-hongkong" },
|
||||
{ label: "柔佛", value: "ap-southeast-1" },
|
||||
{ label: "雅加达", value: "ap-southeast-3" }
|
||||
|
||||
]
|
||||
},
|
||||
value: "cn-beijing",
|
||||
required: true
|
||||
})
|
||||
regionId!: string;
|
||||
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "监听器列表",
|
||||
helper: "选择要部署证书的监听器\n需要在监听器中选择证书中心,进行跨服务访问授权",
|
||||
action: VolcengineDeployToCLB.prototype.onGetListenerList.name,
|
||||
watches: ["certDomains", "accessId", "regionId"],
|
||||
required: true
|
||||
})
|
||||
)
|
||||
listenerList!: string | string[];
|
||||
|
||||
|
||||
async onInstance() {
|
||||
}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info("开始部署证书到火山引擎CLB");
|
||||
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
||||
const certService = await this.getCertService(access);
|
||||
let certId = this.cert;
|
||||
if (typeof certId !== "string") {
|
||||
const certInfo = this.cert as CertInfo;
|
||||
this.logger.info(`开始上传证书`);
|
||||
certId = await certService.ImportCertificate({
|
||||
certName:this.appendTimeSuffix("certd"),
|
||||
cert:certInfo
|
||||
});
|
||||
this.logger.info(`上传证书成功:${certId}`);
|
||||
} else {
|
||||
this.logger.info(`使用已有证书ID:${certId}`);
|
||||
}
|
||||
|
||||
const service = await this.getClbService();
|
||||
for (const listener of this.listenerList) {
|
||||
this.logger.info(`开始部署监听器${listener}证书`);
|
||||
await service.request({
|
||||
action: "ModifyListenerAttributes",
|
||||
query: {
|
||||
ListenerId: listener,
|
||||
CertificateSource: "cert_center",
|
||||
CertCenterCertificateId: certId
|
||||
}
|
||||
});
|
||||
this.logger.info(`部署监听器${listener}证书成功`);
|
||||
}
|
||||
|
||||
this.logger.info("部署完成");
|
||||
}
|
||||
|
||||
|
||||
private async getCertService(access: VolcengineAccess) {
|
||||
const client = new VolcengineClient({
|
||||
logger: this.logger,
|
||||
access,
|
||||
http: this.http
|
||||
});
|
||||
|
||||
return await client.getCertCenterService();
|
||||
}
|
||||
|
||||
async onGetClbList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error("请选择Access授权");
|
||||
}
|
||||
const service = await this.getClbService();
|
||||
const res = await service.request({
|
||||
action: "DescribeLoadBalancers",
|
||||
method: "GET",
|
||||
query: {
|
||||
PageSize: 100
|
||||
},
|
||||
});
|
||||
|
||||
const list = res.Result.LoadBalancers;
|
||||
|
||||
return list.map((item: any) => {
|
||||
return {
|
||||
value: item.LoadBalancerId,
|
||||
label: `${item.LoadBalancerName}<${item.Description}>`
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async getClbService() {
|
||||
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
||||
|
||||
const client = new VolcengineClient({
|
||||
logger: this.logger,
|
||||
access,
|
||||
http: this.http
|
||||
});
|
||||
|
||||
const service = await client.getClbService({
|
||||
region: this.regionId
|
||||
});
|
||||
return service;
|
||||
}
|
||||
|
||||
async onGetListenerList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error("请选择Access授权");
|
||||
}
|
||||
const service = await this.getClbService();
|
||||
|
||||
const res = await service.request({
|
||||
action: "DescribeListeners",
|
||||
method: "GET",
|
||||
query: {
|
||||
PageSize: 100,
|
||||
Protocol: "HTTPS"
|
||||
},
|
||||
});
|
||||
|
||||
const list = res.Result.Listeners;
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("找不到HTTPS类型的负载均衡监听器,您也可以手动输入监听器ID");
|
||||
}
|
||||
return list.map((item: any) => {
|
||||
return {
|
||||
value: item.ListenerId,
|
||||
label: `${item.ListenerName}<${item.Description}:${item.ListenerId}>`
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new VolcengineDeployToCLB();
|
||||
@@ -0,0 +1,77 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
|
||||
import { createCertDomainGetterInputDefine } from "@certd/plugin-lib";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { VolcengineAccess } from "../access.js";
|
||||
import { VolcengineClient } from "../ve-client.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'VolcengineUploadToCertCenter',
|
||||
title: '火山引擎-上传证书至证书中心',
|
||||
icon: 'svg:icon-volcengine',
|
||||
group: pluginGroups.volcengine.key,
|
||||
desc: '上传证书至火山引擎证书中心',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class VolcengineUploadToCertCenter extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: '域名证书',
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'output-selector',
|
||||
from: [...CertApplyPluginNames],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
|
||||
@TaskInput({
|
||||
title: 'Access授权',
|
||||
helper: '火山引擎AccessKeyId、AccessKeySecret',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'volcengine',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
|
||||
@TaskOutput({
|
||||
title: '上传成功后的火山引擎证书Id',
|
||||
})
|
||||
volcengineCertId?: string;
|
||||
|
||||
async onInstance() {}
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info('开始上传证书到证书中心');
|
||||
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
||||
|
||||
const client = await this.getClient(access)
|
||||
const service = await client.getCertCenterService()
|
||||
const certInfo = this.cert
|
||||
this.logger.info(`开始上传证书`)
|
||||
this.volcengineCertId = await service.ImportCertificate({
|
||||
certName: this.appendTimeSuffix('certd'),
|
||||
cert: certInfo
|
||||
})
|
||||
this.logger.info(`上传完成:${this.volcengineCertId}`);
|
||||
}
|
||||
|
||||
|
||||
async getClient(access: VolcengineAccess) {
|
||||
return new VolcengineClient({
|
||||
logger: this.logger,
|
||||
access,
|
||||
http:this.http
|
||||
})
|
||||
}
|
||||
}
|
||||
new VolcengineUploadToCertCenter();
|
||||
@@ -0,0 +1,122 @@
|
||||
import { VolcengineAccess } from "./access.js";
|
||||
import { HttpClient, ILogger } from "@certd/basic";
|
||||
|
||||
export type VolcengineOpts = {
|
||||
access: VolcengineAccess
|
||||
logger: ILogger
|
||||
http: HttpClient
|
||||
}
|
||||
|
||||
export class VolcengineClient {
|
||||
opts: VolcengineOpts;
|
||||
CommonService: any;
|
||||
|
||||
constructor(opts: VolcengineOpts) {
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
async getCertCenterService() {
|
||||
const CommonService = await this.getServiceCls();
|
||||
|
||||
const service = new CommonService({
|
||||
serviceName: "certificate_service",
|
||||
defaultVersion: "2024-10-01"
|
||||
});
|
||||
service.setAccessKeyId(this.opts.access.accessKeyId);
|
||||
service.setSecretKey(this.opts.access.secretAccessKey);
|
||||
service.setRegion("cn-beijing");
|
||||
|
||||
service.ImportCertificate = async (body: { certName: string, cert: any }) => {
|
||||
const { certName, cert } = body;
|
||||
const res = await service.request({
|
||||
action: "ImportCertificate",
|
||||
method: "POST",
|
||||
body: {
|
||||
Tag: certName,
|
||||
Repeatable: false,
|
||||
CertificateInfo: {
|
||||
CertificateChain: cert.crt,
|
||||
PrivateKey: cert.key
|
||||
}
|
||||
}
|
||||
});
|
||||
return res.Result.InstanceId || res.Result.RepeatId
|
||||
};
|
||||
return service;
|
||||
}
|
||||
|
||||
async getClbService(opts: { region?: string }) {
|
||||
const CommonService = await this.getServiceCls();
|
||||
|
||||
const service = new CommonService({
|
||||
serviceName: "clb",
|
||||
defaultVersion: "2020-04-01"
|
||||
});
|
||||
service.setAccessKeyId(this.opts.access.accessKeyId);
|
||||
service.setSecretKey(this.opts.access.secretAccessKey);
|
||||
service.setRegion(opts.region);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
async getAlbService(opts: { region?: string }) {
|
||||
const CommonService = await this.getServiceCls();
|
||||
|
||||
const service = new CommonService({
|
||||
serviceName: "alb",
|
||||
defaultVersion: "2020-04-01"
|
||||
});
|
||||
service.setAccessKeyId(this.opts.access.accessKeyId);
|
||||
service.setSecretKey(this.opts.access.secretAccessKey);
|
||||
service.setRegion(opts.region);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
async getServiceCls() {
|
||||
if (this.CommonService) {
|
||||
return this.CommonService;
|
||||
}
|
||||
const { Service } = await import("@volcengine/openapi");
|
||||
|
||||
class CommonService extends Service {
|
||||
Generic: any;
|
||||
|
||||
constructor(options: {
|
||||
serviceName: string;
|
||||
defaultVersion: string;
|
||||
}) {
|
||||
super(Object.assign({ host: "open.volcengineapi.com" }, options));
|
||||
this.Generic = async (req: { action: string, body?: any, method?: string, query?: any }) => {
|
||||
const { action, method, body, query } = req;
|
||||
return await this.fetchOpenAPI({
|
||||
Action: action,
|
||||
Version: options.defaultVersion,
|
||||
method: method as any,
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
query: query || {},
|
||||
data: body
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async request(req: { action: string, body?: any, method?: string, query?: any }) {
|
||||
const res = await this.Generic(req);
|
||||
if (res.errorcode) {
|
||||
throw new Error(`${res.errorcode}:${res.message}`);
|
||||
}
|
||||
if (res.ResponseMetadata?.Error) {
|
||||
throw new Error(res.ResponseMetadata?.Error?.Message);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
this.CommonService = CommonService;
|
||||
return CommonService;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user