mirror of
https://github.com/certd/certd.git
synced 2026-05-16 13:17:29 +08:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 667e7b185b | |||
| 8483ee0d41 | |||
| c3baaf3ac7 | |||
| c63745d1ba | |||
| 59b9ffadd0 | |||
| d131ea3790 | |||
| b849d34be5 | |||
| 58fc9a551c | |||
| 5801f34b3a | |||
| 17cf16ca92 | |||
| 7015b1b232 | |||
| 3b72ca09c6 | |||
| a815d0245b | |||
| 229f22d5a9 | |||
| 22f5cfcfd8 | |||
| 90ba55c043 | |||
| 9f878a353c | |||
| af7297d671 | |||
| 2f172b56e9 | |||
| 9076c8b20e | |||
| 639756dfcd | |||
| 7aa0c7e491 | |||
| 45dedf5bc7 | |||
| 4681ec9008 | |||
| b91826c6e6 | |||
| 686856d0ae | |||
| 9b09d2578d | |||
| f8f51adf88 | |||
| f8ce639717 | |||
| 1c6dc169ac | |||
| 3e5366c74e | |||
| b49ddbfef9 | |||
| b92fd73f53 | |||
| 41b8f51a6a | |||
| aad9045de5 | |||
| fdd5848df4 | |||
| 118c15d046 | |||
| bae4f8e320 | |||
| e0189a566e | |||
| 1cd8d73cdb | |||
| d6e9e5987b | |||
| 8c5aa37745 | |||
| a18a871ac3 | |||
| 90cbff9cf9 |
@@ -105,7 +105,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
|||||||
|
|
||||||
- 前端 `pnpm dev`:启动 Vite 开发服务
|
- 前端 `pnpm dev`:启动 Vite 开发服务
|
||||||
- 前端 `pnpm build`:生产构建
|
- 前端 `pnpm build`:生产构建
|
||||||
- 前端 `pnpm tsc`:类型检查
|
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
|
||||||
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
|
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
|
||||||
|
|
||||||
## 流水线与插件模型
|
## 流水线与插件模型
|
||||||
@@ -216,5 +216,5 @@ Get-ChildItem packages\ui\certd-client\src\views\certd
|
|||||||
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
|
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
|
||||||
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。
|
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。
|
||||||
- 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`、`corepack pnpm --dir packages\core\basic test:unit`、`corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。
|
- 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`、`corepack pnpm --dir packages\core\basic test:unit`、`corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。
|
||||||
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要运行 `vue-tsc` / `pnpm tsc`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
||||||
- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。
|
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
|
||||||
|
|||||||
@@ -3,6 +3,42 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录丢失state时无法在用户信息页面绑定第三方账号的bug ([45dedf5](https://github.com/certd/certd/commit/45dedf5bc779fea852e1f33dda4f31db2765633c))
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复自动注册后没有跳转到控制台的bug ([4681ec9](https://github.com/certd/certd/commit/4681ec90088a3eb665427b2ac4047ec5ccefd7b3))
|
||||||
|
* 修复clogin登录丢失state问题 ([22f5cfc](https://github.com/certd/certd/commit/22f5cfcfd8462ca74128329eefb3f48b3ee0b7ea))
|
||||||
|
* 修复clogin多选类型登录失败的bug ([9f878a3](https://github.com/certd/certd/commit/9f878a353cd49b7b10bb0a95610ad236bc920dd2))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 第三方登录自动注册的用户支持设置初始化密码 ([a815d02](https://github.com/certd/certd/commit/a815d0245b97efbb948b33d6fc9d49862ce06889))
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package root
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录彩虹登录不上的bug ([bae4f8e](https://github.com/certd/certd/commit/bae4f8e3209d9f9869ecbd7c01655383bac2fe21))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/certd/certd/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永
|
|||||||
* **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
|
* **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
|
||||||
* **站点证书监控**: 定时监控网站证书的过期时间
|
* **站点证书监控**: 定时监控网站证书的过期时间
|
||||||
* **多用户管理**: 用户可以管理自己的证书流水线
|
* **多用户管理**: 用户可以管理自己的证书流水线
|
||||||
|
* **项目管理**: 企业级项目管理模式
|
||||||
* **多语言支持**: 中英双语切换
|
* **多语言支持**: 中英双语切换
|
||||||
* **无忧升级**: 版本向下兼容
|
* **无忧升级**: 版本向下兼容
|
||||||
|
|
||||||
@@ -179,19 +180,23 @@ https://certd.handfree.work/
|
|||||||
|
|
||||||
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
|
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
|
||||||
|
|
||||||
专业版特权对比
|
专业版、商业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
|
||||||
|---------|---------------------------------------|--------------------------------|
|
|
||||||
| 免费证书申请 | 免费无限制 | 免费无限制 |
|
|
||||||
| 证书域名数量 | 无限制 | 无限制 |
|
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
|
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
|
|
||||||
| 站点监控 | 限制1条 | 无限制 |
|
|
||||||
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
|
|
||||||
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
|
|
||||||
|
|
||||||
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|
|---------|---------------------------------------|--------------------------------|---------------------------------|
|
||||||
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
|
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
## 九、贡献代码
|
## 九、贡献代码
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
version: '3.3' # 兼容旧版docker-compose
|
||||||
|
services:
|
||||||
|
certd:
|
||||||
|
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号,例如:certd:1.29.0
|
||||||
|
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
# image: ghcr.io/certd/certd:latest # --------- 如果 报镜像not found,可以尝试其他镜像源
|
||||||
|
# image: greper/certd:latest
|
||||||
|
container_name: certd # 容器名
|
||||||
|
restart: unless-stopped # 自动重启
|
||||||
|
volumes:
|
||||||
|
# ↓↓↓↓↓ -------------------------------------------------------- 数据库以及证书存储路径,默认存在宿主机的/data/certd/目录下,【您需要定时备份此目录,以保障数据容灾】
|
||||||
|
- /data/certd:/app/data # 只要修改冒号前面的,冒号后面的/app/data切记切记不要动
|
||||||
|
#- /volume1/docker/certd:/app/data:delegated #群晖使用这个配置
|
||||||
|
# ↓↓↓↓↓ -------------------------------------------------------- 如果走时不准,考虑挂载localtime文件
|
||||||
|
#- /etc/localtime:/etc/localtime
|
||||||
|
#- /etc/timezone:/etc/timezone
|
||||||
|
ports: # 端口映射
|
||||||
|
# ↓↓↓↓ ---------------------------------------------------------- 如果端口有冲突,可以修改第一个7001为其他不冲突的端口号,第二个7001不要动
|
||||||
|
- "7001:7001"
|
||||||
|
# ↓↓↓↓ ---------------------------------------------------------- https端口,可以根据实际情况,是否暴露该端口
|
||||||
|
- "7002:7002"
|
||||||
|
#↓↓↓↓ -------------------------------------------------------------- 如果出现getaddrinfo EAI_AGAIN 或 getaddrinfo ENOTFOUND 错误,可以尝试设置dns
|
||||||
|
# dns:
|
||||||
|
# - 223.5.5.5 # 阿里云公共dns
|
||||||
|
# - 223.6.6.6
|
||||||
|
# # ↓↓↓↓ --------------------------------------------------------- 如果你服务器在腾讯云,可以用这个替换上面阿里云的公共dns
|
||||||
|
# - 119.29.29.29 # 腾讯云公共dns
|
||||||
|
# - 182.254.116.116
|
||||||
|
# # ↓↓↓↓ --------------------------------------------------------- 如果你服务器部署在国外,可以用这个替换上面阿里云的公共dns
|
||||||
|
# - 8.8.8.8 # 谷歌公共dns
|
||||||
|
# - 8.8.4.4
|
||||||
|
# extra_hosts:
|
||||||
|
# # ↓↓↓↓ -------------------------------------------------------- 这里可以配置自定义hosts,外网域名可以指向本地局域网ip地址
|
||||||
|
# - "localdomain.com:192.168.1.3"
|
||||||
|
# # ↓↓↓↓ ------------------------------------------------ 直接使用主机的网络,如果网络问题实在找不到原因,可以尝试打开此参数
|
||||||
|
# network_mode: host
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: "true"
|
||||||
|
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络,还需要把下面networks的注释放开
|
||||||
|
# networks:
|
||||||
|
# - ip6net
|
||||||
|
environment:
|
||||||
|
# ↓↓↓↓ ----------------------------------------------------- 使用上海东八时区
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
# 设置环境变量即可自定义certd配置
|
||||||
|
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
||||||
|
# 配置规则: certd_ + 配置项, 点号用_代替
|
||||||
|
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,docker compose up -d 重建容器之后,管理员密码将改成123456,然后请及时修改回false
|
||||||
|
- certd_system_resetAdminPasswd=false
|
||||||
|
# ↓↓↓ 要使用ipv6,将此配置修改为::
|
||||||
|
- certd_koa_hostname=0.0.0.0
|
||||||
|
|
||||||
|
# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
||||||
|
# 注意: 选定使用一种数据库之后,不支持更换数据库。
|
||||||
|
# 数据库迁移方法:1、使用新数据库重新部署一套,然后将旧数据同步过去,注意flyway_history表的数据不要同步
|
||||||
|
# #↓↓↓↓ ----------------------------- 使用postgresql数据库,需要提前创建数据库
|
||||||
|
# - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录
|
||||||
|
# - certd_typeorm_dataSource_default_type=postgres # 数据库类型
|
||||||
|
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
||||||
|
# - certd_typeorm_dataSource_default_port=5433 # 数据库端口
|
||||||
|
# - certd_typeorm_dataSource_default_username=postgres # 用户名
|
||||||
|
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
||||||
|
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
||||||
|
|
||||||
|
# #↓↓↓↓ ----------------------------- 使用mysql8数据库,需要提前创建数据库 charset=utf8mb4, collation=utf8mb4_bin
|
||||||
|
# - certd_flyway_scriptDir=./db/migration-mysql # 升级脚本目录
|
||||||
|
# - certd_typeorm_dataSource_default_type=mysql # 数据库类型, 或者 mariadb
|
||||||
|
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
||||||
|
# - certd_typeorm_dataSource_default_port=3306 # 数据库端口
|
||||||
|
# - certd_typeorm_dataSource_default_username=root # 用户名
|
||||||
|
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
||||||
|
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 自动升级,上面certd的版本号要保持为latest
|
||||||
|
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=600 # 每 10 分钟检查一次更新
|
||||||
|
|
||||||
|
|
||||||
|
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络,还需要把上面networks的注释放开
|
||||||
|
#networks:
|
||||||
|
# ip6net:
|
||||||
|
# enable_ipv6: true
|
||||||
|
# ipam:
|
||||||
|
# config:
|
||||||
|
# - subnet: 2001:db8::/64
|
||||||
@@ -3,6 +3,49 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录丢失state时无法在用户信息页面绑定第三方账号的bug ([45dedf5](https://github.com/certd/certd/commit/45dedf5bc779fea852e1f33dda4f31db2765633c))
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复自动注册后没有跳转到控制台的bug ([4681ec9](https://github.com/certd/certd/commit/4681ec90088a3eb665427b2ac4047ec5ccefd7b3))
|
||||||
|
* 修复clogin登录丢失state问题 ([22f5cfc](https://github.com/certd/certd/commit/22f5cfcfd8462ca74128329eefb3f48b3ee0b7ea))
|
||||||
|
* 修复clogin多选类型登录失败的bug ([9f878a3](https://github.com/certd/certd/commit/9f878a353cd49b7b10bb0a95610ad236bc920dd2))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 第三方登录自动注册的用户支持设置初始化密码 ([a815d02](https://github.com/certd/certd/commit/a815d0245b97efbb948b33d6fc9d49862ce06889))
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package root
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录彩虹登录不上的bug ([bae4f8e](https://github.com/certd/certd/commit/bae4f8e3209d9f9869ecbd7c01655383bac2fe21))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/certd/certd/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复阿里云订阅流水线创建对话框无法获取阿里订单列表的bug ([a362860](https://github.com/certd/certd/commit/a362860137bfb7072893c844fe775edc46070ee1))
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+16
-10
@@ -16,16 +16,22 @@
|
|||||||
****------------------****
|
****------------------****
|
||||||
## 专业版特权对比
|
## 专业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|---------|---------------------------------------|--------------------------------|
|
|---------|---------------------------------------|--------------------------------|---------------------------------|
|
||||||
| 证书申请 | 无限制 | 无限制 |
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
| 证书域名数量 | 无限制 | 无限制 |
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
|
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
| 站点监控 | 限制1条 | 无限制 |
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 专业版激活方式
|
## 专业版激活方式
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
## 自动升级配置
|
||||||
|
|
||||||
|
### 1. 方法一:使用watchtower监控自动升级【推荐】
|
||||||
|
|
||||||
|
1. 修改docker-compose.yaml文件增加如下配置
|
||||||
|
或 [下载完整的自动升级docker-compose.yaml配置](https://gitee.com/certd/certd/raw/v2/docker/auto/docker-compose.yaml)
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
certd:
|
||||||
|
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号 这里要保持为latest
|
||||||
|
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
... # 这里是你原来的docker-compose.yaml配置
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 增加一个标签,表示certd需要自动升级
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: "true"
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 自动升级watchtower配置,注意:上面certd的版本号要保持为latest
|
||||||
|
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=600 # 每 10 分钟检查一次更新
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 重启certd容器
|
||||||
|
```shell
|
||||||
|
cd certd
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 2. 方法二:使用Certd版本监控功能【不太稳定】
|
||||||
|
|
||||||
|
1. 选择Github-检查Release版本插件
|
||||||
|

|
||||||
|
按如下图填写配置
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
2. 检测到新版本后执行宿主机升级命令:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 拉取最新镜像
|
||||||
|
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
# 升级容器命令, 替换成你自己的certd更新命令
|
||||||
|
export RESTART_CERT='sleep 10; cd ~/deploy/certd/ ; docker compose down; docker compose up -d'
|
||||||
|
# 构造一个脚本10s后在后台执行,避免容器销毁时执行太快,导致流水线任务无法结束
|
||||||
|
nohup sh -c '$RESTART_CERT' >/dev/null 2>&1 & echo '10秒后重启' && exit
|
||||||
|
```
|
||||||
@@ -22,51 +22,3 @@
|
|||||||
可以查看最新版本号,以及所有版本的更新日志
|
可以查看最新版本号,以及所有版本的更新日志
|
||||||
[CHANGELOG](../changelogs/CHANGELOG.md)
|
[CHANGELOG](../changelogs/CHANGELOG.md)
|
||||||
|
|
||||||
|
|
||||||
## 自动升级配置
|
|
||||||
|
|
||||||
### 1. 方法一:使用watchtower监控
|
|
||||||
|
|
||||||
修改docker-compose.yaml文件增加如下配置, 使用watchtower监控自动升级
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
certd:
|
|
||||||
...
|
|
||||||
labels:
|
|
||||||
com.centurylinklabs.watchtower.enable: "true"
|
|
||||||
|
|
||||||
# ↓↓↓↓ --------------------------------------------------------- 自动升级,上面certd的版本号要保持为latest
|
|
||||||
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=600 # 每 10 分钟检查一次更新
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 2. 方法二:使用Certd版本监控功能
|
|
||||||
|
|
||||||
选择Github-检查Release版本插件
|
|
||||||

|
|
||||||
按如下图填写配置
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
检测到新版本后执行宿主机升级命令:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# 拉取最新镜像
|
|
||||||
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
|
||||||
# 升级容器命令, 替换成你自己的certd更新命令
|
|
||||||
export RESTART_CERT='sleep 10; cd ~/deploy/certd/ ; docker compose down; docker compose up -d'
|
|
||||||
# 构造一个脚本10s后在后台执行,避免容器销毁时执行太快,导致流水线任务无法结束
|
|
||||||
nohup sh -c '$RESTART_CERT' >/dev/null 2>&1 & echo '10秒后重启' && exit
|
|
||||||
```
|
|
||||||
+1
-1
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.39.14"
|
"version": "1.40.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,20 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/publishlab/node-acme-client/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/publishlab/node-acme-client/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/publishlab/node-acme-client/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/publishlab/node-acme-client/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
|
||||||
## [1.39.14](https://github.com/publishlab/node-acme-client/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/publishlab/node-acme-client/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/acme-client
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"private": false,
|
"private": false,
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.14",
|
"@certd/basic": "^1.40.0",
|
||||||
"@peculiar/x509": "^1.11.0",
|
"@peculiar/x509": "^1.11.0",
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
@@ -76,5 +76,5 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ export default async (client, userOpts) => {
|
|||||||
await client.completeChallenge(challenge);
|
await client.completeChallenge(challenge);
|
||||||
}catch (e) {
|
}catch (e) {
|
||||||
await deactivateAuth(e);
|
await deactivateAuth(e);
|
||||||
|
e.message = `[${d}] ${e.message || "completeChallenge error"}`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
challengeCompleted = true;
|
challengeCompleted = true;
|
||||||
@@ -178,6 +179,7 @@ export default async (client, userOpts) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message || e}`);
|
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message || e}`);
|
||||||
await deactivateAuth(e);
|
await deactivateAuth(e);
|
||||||
|
e.message = `[${d}] ${e.message || "challengeCreateFn error"}`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,20 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/basic
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
00:06
|
00:44
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -52,5 +52,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ export class LocalCache<V = any> {
|
|||||||
cache: Map<string, { value: V; expiresAt: number }>;
|
cache: Map<string, { value: V; expiresAt: number }>;
|
||||||
constructor(opts: { clearInterval?: number } = {}) {
|
constructor(opts: { clearInterval?: number } = {}) {
|
||||||
this.cache = new Map();
|
this.cache = new Map();
|
||||||
setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
this.clearExpires();
|
this.clearExpires();
|
||||||
}, opts.clearInterval ?? 5 * 60 * 1000);
|
}, opts.clearInterval ?? 5 * 60 * 1000);
|
||||||
|
intervalId.unref?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): V | undefined {
|
get(key: string): V | undefined {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
import { createAxiosService, HttpClient, setGlobalHeaders } from "./util.request.js";
|
||||||
|
import { ILogger } from "./util.log.js";
|
||||||
|
|
||||||
|
const testLogger = {
|
||||||
|
info() {},
|
||||||
|
error() {},
|
||||||
|
} as unknown as ILogger;
|
||||||
|
|
||||||
|
describe("util.request", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
setGlobalHeaders({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should merge global headers without overriding request headers", async () => {
|
||||||
|
setGlobalHeaders({
|
||||||
|
"X-Common": "common",
|
||||||
|
"X-Override": "global",
|
||||||
|
});
|
||||||
|
|
||||||
|
const http = createAxiosService({ logger: testLogger }) as HttpClient;
|
||||||
|
const res = await http.request({
|
||||||
|
url: "http://example.com",
|
||||||
|
method: "get",
|
||||||
|
logReq: false,
|
||||||
|
logRes: false,
|
||||||
|
headers: {
|
||||||
|
"X-Override": "request",
|
||||||
|
"X-Request": "request",
|
||||||
|
},
|
||||||
|
adapter: async config => {
|
||||||
|
const headers = config.headers;
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
data: {
|
||||||
|
common: headers.get("X-Common"),
|
||||||
|
override: headers.get("X-Override"),
|
||||||
|
request: headers.get("X-Request"),
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
status: 200,
|
||||||
|
statusText: "OK",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).to.deep.equal({
|
||||||
|
common: "common",
|
||||||
|
override: "request",
|
||||||
|
request: "request",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -82,6 +82,7 @@ export class HttpError extends Error {
|
|||||||
export const HttpCommonError = HttpError;
|
export const HttpCommonError = HttpError;
|
||||||
|
|
||||||
let defaultAgents = createAgent();
|
let defaultAgents = createAgent();
|
||||||
|
let defaultHeaders: Record<string, string> = {};
|
||||||
|
|
||||||
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
||||||
logger.info("setGlobalProxy:", opts);
|
logger.info("setGlobalProxy:", opts);
|
||||||
@@ -92,6 +93,15 @@ export function getGlobalAgents() {
|
|||||||
return defaultAgents;
|
return defaultAgents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setGlobalHeaders(headers: Record<string, string> = {}) {
|
||||||
|
logger.info("setGlobalHeaders:", Object.keys(headers));
|
||||||
|
defaultHeaders = { ...headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGlobalHeaders() {
|
||||||
|
return defaultHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 创建请求实例
|
* @description 创建请求实例
|
||||||
*/
|
*/
|
||||||
@@ -148,6 +158,12 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
config.httpsAgent = agents.httpsAgent;
|
config.httpsAgent = agents.httpsAgent;
|
||||||
config.httpAgent = agents.httpAgent;
|
config.httpAgent = agents.httpAgent;
|
||||||
|
|
||||||
|
if (Object.keys(defaultHeaders).length > 0) {
|
||||||
|
const headers = AxiosHeaders.from(defaultHeaders);
|
||||||
|
headers.set(config.headers || {});
|
||||||
|
config.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
// const agent = new https.Agent({
|
// const agent = new https.Agent({
|
||||||
// rejectUnauthorized: false // 允许自签名证书
|
// rejectUnauthorized: false // 允许自签名证书
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/pipeline
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.14",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.14",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"reflect-metadata": "^0.1.13"
|
"reflect-metadata": "^0.1.13"
|
||||||
@@ -49,5 +49,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-huawei
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-huawei",
|
"name": "@certd/lib-huawei",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
"types": "./dist/d/index.d.ts",
|
"types": "./dist/d/index.d.ts",
|
||||||
@@ -27,5 +27,5 @@
|
|||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-iframe
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-iframe",
|
"name": "@certd/lib-iframe",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -34,5 +34,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/jdcloud
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/jdcloud",
|
"name": "@certd/jdcloud",
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"description": "jdcloud openApi sdk",
|
"description": "jdcloud openApi sdk",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
@@ -58,5 +58,5 @@
|
|||||||
"fetch"
|
"fetch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-k8s
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-k8s",
|
"name": "@certd/lib-k8s",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.14",
|
"@certd/basic": "^1.40.0",
|
||||||
"@kubernetes/client-node": "0.21.0"
|
"@kubernetes/client-node": "0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -36,5 +36,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.39.14",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.14",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.14",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plugin-lib": "^1.39.14",
|
"@certd/plugin-lib": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.14",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"@midwayjs/cache": "3.14.0",
|
"@midwayjs/cache": "3.14.0",
|
||||||
"@midwayjs/core": "3.20.11",
|
"@midwayjs/core": "3.20.11",
|
||||||
"@midwayjs/i18n": "3.20.13",
|
"@midwayjs/i18n": "3.20.13",
|
||||||
@@ -69,5 +69,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export class SysPublicSettings extends BaseSettings {
|
|||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
addonId: number;
|
addonId: number;
|
||||||
|
icon?: string;
|
||||||
}> = {};
|
}> = {};
|
||||||
|
|
||||||
notice?: string;
|
notice?: string;
|
||||||
@@ -80,6 +81,7 @@ export class SysPrivateSettings extends BaseSettings {
|
|||||||
|
|
||||||
httpsProxy? = '';
|
httpsProxy? = '';
|
||||||
httpProxy? = '';
|
httpProxy? = '';
|
||||||
|
commonHeaders?: string = '';
|
||||||
|
|
||||||
reverseProxies?: Record<string, string> = {};
|
reverseProxies?: Record<string, string> = {};
|
||||||
|
|
||||||
@@ -251,6 +253,14 @@ export class SysSuiteSetting extends BaseSettings {
|
|||||||
intro?: string;
|
intro?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SysAutoFixSetting extends BaseSettings {
|
||||||
|
static __title__ = '自动修复记录';
|
||||||
|
static __key__ = 'sys.auto.fix';
|
||||||
|
static __access__ = 'private';
|
||||||
|
|
||||||
|
fixed: Record<string, boolean> = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type SiteHidden = {
|
export type SiteHidden = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { SysSettingsEntity } from '../entity/sys-settings.js';
|
|||||||
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
||||||
|
|
||||||
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
||||||
import { cache, logger, mergeUtils, setGlobalProxy } from '@certd/basic';
|
import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from '@certd/basic';
|
||||||
import { isPlus } from '@certd/plus-core';
|
import { isPlus } from '@certd/plus-core';
|
||||||
import * as dns from 'node:dns';
|
import * as dns from 'node:dns';
|
||||||
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
||||||
@@ -167,6 +167,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
httpsProxy: privateSetting.httpsProxy,
|
httpsProxy: privateSetting.httpsProxy,
|
||||||
};
|
};
|
||||||
setGlobalProxy(opts);
|
setGlobalProxy(opts);
|
||||||
|
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
|
||||||
|
|
||||||
if (privateSetting.dnsResultOrder) {
|
if (privateSetting.dnsResultOrder) {
|
||||||
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
||||||
@@ -185,12 +186,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnvironmentVars(vars: string) {
|
parseKeyValueText(text: string) {
|
||||||
const envVars = {}
|
const values = {};
|
||||||
if (typeof vars !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
vars = ""
|
text = "";
|
||||||
}
|
}
|
||||||
vars.split('\n').forEach(line => {
|
text.split('\n').forEach(line => {
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
if (!line || line.startsWith('#')) {
|
if (!line || line.startsWith('#')) {
|
||||||
return
|
return
|
||||||
@@ -204,11 +205,18 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const [key, value] = line.split('=');
|
const eqIndex = line.indexOf('=');
|
||||||
|
const key = line.substring(0, eqIndex).trim();
|
||||||
|
const value = line.substring(eqIndex + 1).trim();
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
envVars[key.trim()] = value.trim();
|
values[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnvironmentVars(vars: string) {
|
||||||
|
const envVars = this.parseKeyValueText(vars);
|
||||||
//先删除旧环境变量
|
//先删除旧环境变量
|
||||||
if (lastSaveEnvVars) {
|
if (lastSaveEnvVars) {
|
||||||
for (const key in lastSaveEnvVars) {
|
for (const key in lastSaveEnvVars) {
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/midway-flyway-js",
|
"name": "@certd/midway-flyway-js",
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -49,5 +49,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/plugin-cert
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-cert",
|
"name": "@certd/plugin-cert",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.39.14",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.14",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.14",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plugin-lib": "^1.39.14",
|
"@certd/plugin-lib": "^1.40.0",
|
||||||
"psl": "^1.9.0",
|
"psl": "^1.9.0",
|
||||||
"punycode.js": "^2.3.1"
|
"punycode.js": "^2.3.1"
|
||||||
},
|
},
|
||||||
@@ -41,5 +41,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/plugin-lib
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
"@alicloud/tea-util": "^1.4.11",
|
"@alicloud/tea-util": "^1.4.11",
|
||||||
"@aws-sdk/client-s3": "^3.964.0",
|
"@aws-sdk/client-s3": "^3.964.0",
|
||||||
"@certd/acme-client": "^1.39.14",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.14",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.14",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.14",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"@kubernetes/client-node": "0.21.0",
|
"@kubernetes/client-node": "0.21.0",
|
||||||
"ali-oss": "^6.22.0",
|
"ali-oss": "^6.22.0",
|
||||||
"basic-ftp": "^5.0.5",
|
"basic-ftp": "^5.0.5",
|
||||||
@@ -61,5 +61,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录丢失state时无法在用户信息页面绑定第三方账号的bug ([45dedf5](https://github.com/certd/certd/commit/45dedf5bc779fea852e1f33dda4f31db2765633c))
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复自动注册后没有跳转到控制台的bug ([4681ec9](https://github.com/certd/certd/commit/4681ec90088a3eb665427b2ac4047ec5ccefd7b3))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/ui-client
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
"zod-defaults": "^0.1.3"
|
"zod-defaults": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.39.14",
|
"@certd/lib-iframe": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.14",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -78,8 +78,13 @@ export default {
|
|||||||
passkeyRegisterFailed: "Passkey registration failed",
|
passkeyRegisterFailed: "Passkey registration failed",
|
||||||
title: "Change Password",
|
title: "Change Password",
|
||||||
weakPasswordWarning: "For your account security, please change your password immediately",
|
weakPasswordWarning: "For your account security, please change your password immediately",
|
||||||
|
initPasswordWarning: "This account does not have a login password yet. Please set one first",
|
||||||
|
initPasswordTitle: "Set Password",
|
||||||
changeNow: "Change Now",
|
changeNow: "Change Now",
|
||||||
|
setNow: "Set Now",
|
||||||
|
notNow: "Not Now",
|
||||||
successMessage: "Changed successfully",
|
successMessage: "Changed successfully",
|
||||||
|
initPasswordSuccessMessage: "Set successfully",
|
||||||
oldPassword: "Old Password",
|
oldPassword: "Old Password",
|
||||||
oldPasswordRequired: "Please enter the old password",
|
oldPasswordRequired: "Please enter the old password",
|
||||||
newPassword: "New Password",
|
newPassword: "New Password",
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export default {
|
|||||||
specifications: "Specifications",
|
specifications: "Specifications",
|
||||||
pipeline: "Pipeline",
|
pipeline: "Pipeline",
|
||||||
domain: "Domain",
|
domain: "Domain",
|
||||||
|
totalDomain: "Total Domain Count",
|
||||||
|
wildcardDomain: "Wildcard Domain",
|
||||||
|
includedWildcardDomain: "Included Wildcard Domain Count",
|
||||||
deployTimes: "Deployments",
|
deployTimes: "Deployments",
|
||||||
monitorCount: "DomainMonitors",
|
monitorCount: "DomainMonitors",
|
||||||
duration: "Duration",
|
duration: "Duration",
|
||||||
@@ -24,7 +27,8 @@ export default {
|
|||||||
please_select_package: "Please select a package",
|
please_select_package: "Please select a package",
|
||||||
package: "Package",
|
package: "Package",
|
||||||
addon_package: "Addon Package",
|
addon_package: "Addon Package",
|
||||||
domain_count: "Domain Count",
|
domain_count: "Total Domain Count",
|
||||||
|
wildcard_domain_count: "Wildcard Domain Count",
|
||||||
unit_count: "pcs",
|
unit_count: "pcs",
|
||||||
pipeline_count: "Pipeline Count",
|
pipeline_count: "Pipeline Count",
|
||||||
unit_item: "items",
|
unit_item: "items",
|
||||||
@@ -42,6 +46,7 @@ export default {
|
|||||||
intro: "Introduction",
|
intro: "Introduction",
|
||||||
packageContent: "Package Content",
|
packageContent: "Package Content",
|
||||||
maxDomainCount: "Max Domain Count",
|
maxDomainCount: "Max Domain Count",
|
||||||
|
maxWildcardDomainCount: "Max Wildcard Domain Count",
|
||||||
maxPipelineCount: "Max Pipeline Count",
|
maxPipelineCount: "Max Pipeline Count",
|
||||||
maxDeployCount: "Max Deploy Count",
|
maxDeployCount: "Max Deploy Count",
|
||||||
maxMonitorCount: "Max Monitor Count",
|
maxMonitorCount: "Max Monitor Count",
|
||||||
@@ -51,6 +56,10 @@ export default {
|
|||||||
addon: "Addon",
|
addon: "Addon",
|
||||||
typeHelper: "Suite: Only the most recently purchased one is active at a time\nAddon: Multiple can be purchased, effective immediately without affecting the suite\nThe quantities of suite and addon can be accumulated",
|
typeHelper: "Suite: Only the most recently purchased one is active at a time\nAddon: Multiple can be purchased, effective immediately without affecting the suite\nThe quantities of suite and addon can be accumulated",
|
||||||
pipelineCount: "Pipeline Count",
|
pipelineCount: "Pipeline Count",
|
||||||
|
wildcardDomainCount: "Wildcard Domain Count",
|
||||||
|
wildcardDomainCountPart: "Included Wildcard Domain Count",
|
||||||
|
wildcardDomainCountSub: "- Wildcard Domain Count",
|
||||||
|
wildcardDomainCountHelper: "Wildcard domains are also limited by the total domain count: each wildcard domain consumes both domain count and wildcard domain count quota.",
|
||||||
unitPipeline: "pipelines",
|
unitPipeline: "pipelines",
|
||||||
deployCount: "Deployment Count",
|
deployCount: "Deployment Count",
|
||||||
unitDeploy: "times",
|
unitDeploy: "times",
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export default {
|
|||||||
reverseProxyEmpty: "No reverse proxy list configured",
|
reverseProxyEmpty: "No reverse proxy list configured",
|
||||||
environmentVars: "Environment Variables",
|
environmentVars: "Environment Variables",
|
||||||
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
|
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
|
||||||
|
commonHeaders: "Common Headers",
|
||||||
|
commonHeadersHelper: "Common headers automatically added to server-side HTTP requests, one per line, format: KEY=VALUE. Existing request headers with the same name are not overwritten.",
|
||||||
|
|
||||||
bindUrl: "Bind URL",
|
bindUrl: "Bind URL",
|
||||||
bindUrlHelper: "Bind URL, used as your site URL in notifications",
|
bindUrlHelper: "Bind URL, used as your site URL in notifications",
|
||||||
|
|||||||
@@ -79,8 +79,13 @@ export default {
|
|||||||
|
|
||||||
title: "修改密码",
|
title: "修改密码",
|
||||||
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
|
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
|
||||||
|
initPasswordWarning: "当前账号还未设置登录密码,请先设置密码",
|
||||||
|
initPasswordTitle: "设置密码",
|
||||||
changeNow: "立即修改",
|
changeNow: "立即修改",
|
||||||
|
setNow: "立即设置",
|
||||||
|
notNow: "暂不设置",
|
||||||
successMessage: "修改成功",
|
successMessage: "修改成功",
|
||||||
|
initPasswordSuccessMessage: "设置成功",
|
||||||
oldPassword: "旧密码",
|
oldPassword: "旧密码",
|
||||||
oldPasswordRequired: "请输入旧密码",
|
oldPasswordRequired: "请输入旧密码",
|
||||||
newPassword: "新密码",
|
newPassword: "新密码",
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export default {
|
|||||||
specifications: "规格",
|
specifications: "规格",
|
||||||
pipeline: "流水线",
|
pipeline: "流水线",
|
||||||
domain: "域名",
|
domain: "域名",
|
||||||
|
totalDomain: "域名总数量",
|
||||||
|
wildcardDomain: "泛域名",
|
||||||
|
includedWildcardDomain: "其中泛域名数量",
|
||||||
deployTimes: "部署次数",
|
deployTimes: "部署次数",
|
||||||
monitorCount: "域名监控数",
|
monitorCount: "域名监控数",
|
||||||
duration: "时长",
|
duration: "时长",
|
||||||
@@ -24,7 +27,8 @@ export default {
|
|||||||
please_select_package: "请选择套餐",
|
please_select_package: "请选择套餐",
|
||||||
package: "套餐",
|
package: "套餐",
|
||||||
addon_package: "加量包",
|
addon_package: "加量包",
|
||||||
domain_count: "域名数量",
|
domain_count: "域名总数量",
|
||||||
|
wildcard_domain_count: "泛域名数量",
|
||||||
unit_count: "个",
|
unit_count: "个",
|
||||||
pipeline_count: "流水线数量",
|
pipeline_count: "流水线数量",
|
||||||
unit_item: "条",
|
unit_item: "条",
|
||||||
@@ -42,6 +46,7 @@ export default {
|
|||||||
intro: "介绍",
|
intro: "介绍",
|
||||||
packageContent: "套餐内容",
|
packageContent: "套餐内容",
|
||||||
maxDomainCount: "最大域名数",
|
maxDomainCount: "最大域名数",
|
||||||
|
maxWildcardDomainCount: "最大泛域名数",
|
||||||
maxPipelineCount: "最大流水线数",
|
maxPipelineCount: "最大流水线数",
|
||||||
maxDeployCount: "最大部署数",
|
maxDeployCount: "最大部署数",
|
||||||
maxMonitorCount: "最大监控数",
|
maxMonitorCount: "最大监控数",
|
||||||
@@ -51,6 +56,10 @@ export default {
|
|||||||
addon: "加量包",
|
addon: "加量包",
|
||||||
typeHelper: "套餐:同一时间只有最新购买的一个生效\n加量包:可购买多个,购买后立即生效,不影响套餐\n套餐和加量包数量可叠加",
|
typeHelper: "套餐:同一时间只有最新购买的一个生效\n加量包:可购买多个,购买后立即生效,不影响套餐\n套餐和加量包数量可叠加",
|
||||||
pipelineCount: "流水线数量",
|
pipelineCount: "流水线数量",
|
||||||
|
wildcardDomainCount: "泛域名数量",
|
||||||
|
wildcardDomainCountPart: "其中泛域名数量",
|
||||||
|
wildcardDomainCountSub: "- 泛域名数量",
|
||||||
|
wildcardDomainCountHelper: "泛域名数量受域名总数量限制:泛域名会同时占用域名总数量和泛域名数量额度;注意:如果域名总数有限制,泛域名数量不要设置为无限制。",
|
||||||
unitPipeline: "条",
|
unitPipeline: "条",
|
||||||
deployCount: "部署次数",
|
deployCount: "部署次数",
|
||||||
unitDeploy: "次",
|
unitDeploy: "次",
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ export default {
|
|||||||
reverseProxyEmpty: "未配置反向代理",
|
reverseProxyEmpty: "未配置反向代理",
|
||||||
environmentVars: "环境变量",
|
environmentVars: "环境变量",
|
||||||
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
|
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
|
||||||
|
commonHeaders: "公共请求头",
|
||||||
|
commonHeadersHelper: "服务端发起 HTTP 请求时自动附加的公共请求头,每行一个,格式:KEY=VALUE;请求中已设置同名 Header 时不会覆盖\n注意: 不要将token等敏感内容放在此处,仅限个人和公司内部使用,商业版不要设置",
|
||||||
bindUrl: "绑定URL",
|
bindUrl: "绑定URL",
|
||||||
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
|
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export type SysPublicSetting = {
|
|||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
addonId: number;
|
addonId: number;
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
// 系统通知
|
// 系统通知
|
||||||
@@ -99,6 +100,7 @@ export type SuiteSetting = {
|
|||||||
export type SysPrivateSetting = {
|
export type SysPrivateSetting = {
|
||||||
httpProxy?: string;
|
httpProxy?: string;
|
||||||
httpsProxy?: string;
|
httpsProxy?: string;
|
||||||
|
commonHeaders?: string;
|
||||||
reverseProxies?: any;
|
reverseProxies?: any;
|
||||||
dnsResultOrder?: string;
|
dnsResultOrder?: string;
|
||||||
commonCnameEnabled?: boolean;
|
commonCnameEnabled?: boolean;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface UserInfoRes {
|
|||||||
avatar?: string;
|
avatar?: string;
|
||||||
roleIds: number[];
|
roleIds: number[];
|
||||||
isWeak?: boolean;
|
isWeak?: boolean;
|
||||||
|
needInitPassword?: boolean;
|
||||||
validTime?: number;
|
validTime?: number;
|
||||||
status?: number;
|
status?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export const useUserStore = defineStore({
|
|||||||
getToken(): string {
|
getToken(): string {
|
||||||
return this.token || LocalStorage.get(TOKEN_KEY);
|
return this.token || LocalStorage.get(TOKEN_KEY);
|
||||||
},
|
},
|
||||||
|
isLogined(): boolean {
|
||||||
|
return !!this.getToken;
|
||||||
|
},
|
||||||
isAdmin(): boolean {
|
isAdmin(): boolean {
|
||||||
return this.getUserInfo.roleIds?.includes(1) || this.getUserInfo.id === 1;
|
return this.getUserInfo.roleIds?.includes(1) || this.getUserInfo.id === 1;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ export function useReference(formItem: any) {
|
|||||||
const ctx = {
|
const ctx = {
|
||||||
compute: (opts: any) => {
|
compute: (opts: any) => {
|
||||||
const func = (context: any) => {
|
const func = (context: any) => {
|
||||||
|
debugger;
|
||||||
let form = context.form || {};
|
let form = context.form || {};
|
||||||
form = form.input || form.body || form.access || form;
|
form = form.input || form.body || form; // form.access去掉,历史原因,access的mergeScript会处理form.access
|
||||||
return opts({
|
return opts({
|
||||||
...context,
|
...context,
|
||||||
form,
|
form,
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ export async function changePassword(form: any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initPassword(form: any) {
|
||||||
|
return await request({
|
||||||
|
url: "/mine/initPassword",
|
||||||
|
method: "POST",
|
||||||
|
data: form,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function UpdateProfile(form: any) {
|
export async function UpdateProfile(form: any) {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/mine/updateProfile",
|
url: "/mine/updateProfile",
|
||||||
@@ -68,20 +76,23 @@ export async function GetOauthProviders() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function UnbindOauth(type: string) {
|
export async function UnbindOauth(type: string, subtype?: string) {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/oauth/unbind",
|
url: "/oauth/unbind",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: { type },
|
data: {
|
||||||
|
type: subtype ? `${type}:${subtype}` : type,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function OauthBoundUrl(type: string) {
|
export async function OauthBoundUrl(type: string, subtype?: string) {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/oauth/login",
|
url: "/oauth/login",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
type,
|
type,
|
||||||
|
subtype,
|
||||||
forType: "bind",
|
forType: "bind",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ref } from "vue";
|
|||||||
import { useI18n } from "/src/locales";
|
import { useI18n } from "/src/locales";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
import { compute, CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
||||||
import * as api from "/@/views/certd/mine/api";
|
import * as api from "/@/views/certd/mine/api";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import { useUserStore } from "/@/store/user";
|
import { useUserStore } from "/@/store/user";
|
||||||
@@ -20,6 +20,11 @@ defineProps<{
|
|||||||
|
|
||||||
let passwordFormRef = ref();
|
let passwordFormRef = ref();
|
||||||
|
|
||||||
|
type OpenOptions = {
|
||||||
|
password?: string;
|
||||||
|
init?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const validatePass1 = async (rule: any, value: any) => {
|
const validatePass1 = async (rule: any, value: any) => {
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
throw new Error(t("authentication.enterPassword"));
|
throw new Error(t("authentication.enterPassword"));
|
||||||
@@ -53,19 +58,33 @@ const passwordFormOptions: CrudOptions = {
|
|||||||
width: "500px",
|
width: "500px",
|
||||||
},
|
},
|
||||||
async doSubmit({ form }) {
|
async doSubmit({ form }) {
|
||||||
|
if (form.init) {
|
||||||
|
await api.initPassword(form);
|
||||||
|
} else {
|
||||||
await api.changePassword(form);
|
await api.changePassword(form);
|
||||||
|
}
|
||||||
//重新加载用户信息
|
//重新加载用户信息
|
||||||
await userStore.loadUserInfo();
|
await userStore.loadUserInfo();
|
||||||
},
|
},
|
||||||
async afterSubmit() {
|
async afterSubmit() {
|
||||||
notification.success({ message: t("authentication.successMessage") });
|
const formData = passwordFormRef.value?.getFormData?.();
|
||||||
|
const message = formData?.init ? t("authentication.initPasswordSuccessMessage") : t("authentication.successMessage");
|
||||||
|
notification.success({ message });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
init: {
|
||||||
|
title: "init",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
password: {
|
password: {
|
||||||
title: t("authentication.oldPassword"),
|
title: t("authentication.oldPassword"),
|
||||||
type: "password",
|
type: "password",
|
||||||
form: {
|
form: {
|
||||||
|
show: compute(({ form }) => form.init !== true),
|
||||||
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
|
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -97,12 +116,16 @@ const passwordFormOptions: CrudOptions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function open(opts: { password: "" }) {
|
async function open(opts: OpenOptions = {}) {
|
||||||
const formOptions = buildFormOptions(passwordFormOptions);
|
const formOptions = buildFormOptions(passwordFormOptions);
|
||||||
formOptions.newInstance = true; //新实例打开
|
formOptions.newInstance = true; //新实例打开
|
||||||
|
if (opts.init) {
|
||||||
|
formOptions.wrapper.title = t("authentication.initPasswordTitle");
|
||||||
|
}
|
||||||
passwordFormRef.value = await openDialog(formOptions);
|
passwordFormRef.value = await openDialog(formOptions);
|
||||||
passwordFormRef.value.setFormData({
|
passwordFormRef.value.setFormData({
|
||||||
password: opts.password,
|
init: opts.init === true,
|
||||||
|
password: opts.password || "",
|
||||||
});
|
});
|
||||||
console.log(passwordFormRef.value);
|
console.log(passwordFormRef.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons gap-2">
|
<div class="action-buttons gap-2">
|
||||||
<change-password-button :show-button="true" />
|
<change-password-button ref="changePasswordButtonRef" :show-button="true" />
|
||||||
|
|
||||||
<a-button type="primary" class="action-btn" @click="goSecuritySetting">
|
<a-button type="primary" class="action-btn" @click="goSecuritySetting">
|
||||||
{{ t("authentication.securitySettingTip") }}
|
{{ t("authentication.securitySettingTip") }}
|
||||||
@@ -78,11 +78,11 @@
|
|||||||
<a-tag v-else color="red" class="bound-tag1">未绑定</a-tag>
|
<a-tag v-else color="red" class="bound-tag1">未绑定</a-tag>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a-button v-if="item.bound" type="primary" danger class="action-btn" @click="unbind(item.name)">
|
<a-button v-if="item.bound" type="primary" danger class="action-btn" @click="unbind(item)">
|
||||||
<template #icon><fs-icon icon="ion:unlink-outline" /></template>
|
<template #icon><fs-icon icon="ion:unlink-outline" /></template>
|
||||||
解绑
|
解绑
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button v-else type="primary" class="action-btn" @click="bind(item.name)">
|
<a-button v-else type="primary" class="action-btn" @click="bind(item)">
|
||||||
<template #icon><fs-icon icon="ion:link-outline" /></template>
|
<template #icon><fs-icon icon="ion:link-outline" /></template>
|
||||||
绑定
|
绑定
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -214,7 +214,7 @@ async function loadOauthProviders() {
|
|||||||
|
|
||||||
const computedOauthBounds = computed(() => {
|
const computedOauthBounds = computed(() => {
|
||||||
const list = oauthProviders.value.map(item => {
|
const list = oauthProviders.value.map(item => {
|
||||||
const bound = oauthBounds.value.find(bound => bound.type === item.name);
|
const bound = oauthBounds.value.find(bound => bound.type === buildOauthBoundType(item));
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
bound,
|
bound,
|
||||||
@@ -223,20 +223,24 @@ const computedOauthBounds = computed(() => {
|
|||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function unbind(type: string) {
|
function buildOauthBoundType(item: any) {
|
||||||
|
return item.subtype ? `${item.name}:${item.subtype}` : item.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unbind(item: any) {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "确认解绑吗?",
|
title: "确认解绑吗?",
|
||||||
okText: "确认",
|
okText: "确认",
|
||||||
okType: "danger",
|
okType: "danger",
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
await api.UnbindOauth(type);
|
await api.UnbindOauth(item.name, item.subtype);
|
||||||
await loadOauthBounds();
|
await loadOauthBounds();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bind(type: string) {
|
async function bind(item: any) {
|
||||||
const res = await api.OauthBoundUrl(type);
|
const res = await api.OauthBoundUrl(item.name, item.subtype);
|
||||||
const loginUrl = res.loginUrl;
|
const loginUrl = res.loginUrl;
|
||||||
window.location.href = loginUrl;
|
window.location.href = loginUrl;
|
||||||
}
|
}
|
||||||
@@ -383,6 +387,7 @@ const checkPasskeySupport = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const changePasswordButtonRef = ref();
|
||||||
const userAvatar = computed(() => {
|
const userAvatar = computed(() => {
|
||||||
if (isEmpty(userInfo.value.avatar)) {
|
if (isEmpty(userInfo.value.avatar)) {
|
||||||
return "";
|
return "";
|
||||||
@@ -391,11 +396,26 @@ const userAvatar = computed(() => {
|
|||||||
return userInfo.value.avatar;
|
return userInfo.value.avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `api/basic/file/download?token=${userStore.getToken}&key=${userInfo.value.avatar}`;
|
return `api/basic/file/download?key=${userInfo.value.avatar}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getUserInfo();
|
await getUserInfo();
|
||||||
|
userStore.setUserInfo(userInfo.value);
|
||||||
|
if (userInfo.value.needInitPassword === true) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t("authentication.initPasswordTitle"),
|
||||||
|
content: t("authentication.initPasswordWarning"),
|
||||||
|
okText: t("authentication.setNow"),
|
||||||
|
cancelText: t("authentication.notNow"),
|
||||||
|
closable: true,
|
||||||
|
onOk: () => {
|
||||||
|
changePasswordButtonRef.value.open({
|
||||||
|
init: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
await loadContactCapability();
|
await loadContactCapability();
|
||||||
await loadOauthBounds();
|
await loadOauthBounds();
|
||||||
await loadOauthProviders();
|
await loadOauthProviders();
|
||||||
|
|||||||
@@ -320,6 +320,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.siteName"),
|
title: t("monitor.siteName"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
@@ -333,6 +336,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.domain"),
|
title: t("monitor.domain"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
@@ -411,6 +417,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.certDomains"),
|
title: t("monitor.certDomains"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
@@ -451,6 +460,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.certStatus"),
|
title: t("monitor.certStatus"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
@@ -472,7 +484,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
checkStatus: {
|
checkStatus: {
|
||||||
title: t("monitor.checkStatus"),
|
title: t("monitor.checkStatus"),
|
||||||
search: {
|
search: {
|
||||||
show: false,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
dict: checkStatusDict,
|
dict: checkStatusDict,
|
||||||
@@ -578,6 +593,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dict: groupDictRef,
|
dict: groupDictRef,
|
||||||
form: {
|
form: {
|
||||||
@@ -631,7 +649,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
disabled: {
|
disabled: {
|
||||||
title: t("monitor.disabled"),
|
title: t("monitor.disabled"),
|
||||||
search: {
|
search: {
|
||||||
show: false,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "dict-switch",
|
type: "dict-switch",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type SuiteDetail = {
|
|||||||
expiresTime?: number;
|
expiresTime?: number;
|
||||||
pipelineCount?: SuiteValue;
|
pipelineCount?: SuiteValue;
|
||||||
domainCount?: SuiteValue;
|
domainCount?: SuiteValue;
|
||||||
|
wildcardDomainCount?: SuiteValue;
|
||||||
deployCount?: SuiteValue;
|
deployCount?: SuiteValue;
|
||||||
monitorCount?: SuiteValue;
|
monitorCount?: SuiteValue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"content.maxDomainCount": {
|
"content.maxDomainCount": {
|
||||||
title: "域名数量",
|
title: "域名总数量",
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
key: ["content", "maxDomainCount"],
|
key: ["content", "maxDomainCount"],
|
||||||
@@ -168,6 +168,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
align: "center",
|
align: "center",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"content.maxWildcardDomainCount": {
|
||||||
|
title: "其中泛域名数量",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
key: ["content", "maxWildcardDomainCount"],
|
||||||
|
component: {
|
||||||
|
name: SuiteValueEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: "个",
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: {
|
||||||
|
name: SuiteValue,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: "个",
|
||||||
|
},
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
},
|
||||||
"content.maxPipelineCount": {
|
"content.maxPipelineCount": {
|
||||||
title: "流水线数量",
|
title: "流水线数量",
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
<span class="label">{{ $t("certd.order.specifications") }}:</span>
|
<span class="label">{{ $t("certd.order.specifications") }}:</span>
|
||||||
<span class="flex-o flex-wrap">
|
<span class="flex-o flex-wrap">
|
||||||
<span class="flex-o"> {{ $t("certd.order.pipeline") }}<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" :unit="$t('certd.order.unit.pieces')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.pipeline") }}<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" :unit="$t('certd.order.unit.pieces')" />; </span>
|
||||||
<span class="flex-o"> {{ $t("certd.order.domain") }}<suite-value class="ml-5" :model-value="product.content.maxDomainCount" :unit="$t('certd.order.unit.count')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.totalDomain") }}<suite-value class="ml-5" :model-value="product.content.maxDomainCount" :unit="$t('certd.order.unit.count')" />; </span>
|
||||||
|
<span class="flex-o" style="padding-left: 2em">
|
||||||
|
- {{ $t("certd.order.includedWildcardDomain") }}<suite-value class="ml-5" :model-value="product.content.maxWildcardDomainCount" :unit="$t('certd.order.unit.count')" />;
|
||||||
|
</span>
|
||||||
<span class="flex-o"> {{ $t("certd.order.deployTimes") }}<suite-value class="ml-5" :model-value="product.content.maxDeployCount" :unit="$t('certd.order.unit.times')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.deployTimes") }}<suite-value class="ml-5" :model-value="product.content.maxDeployCount" :unit="$t('certd.order.unit.times')" />; </span>
|
||||||
<span class="flex-o"> {{ $t("certd.order.monitorCount") }}<suite-value class="ml-5" :model-value="product.content.maxMonitorCount" :unit="$t('certd.order.unit.times')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.monitorCount") }}<suite-value class="ml-5" :model-value="product.content.maxMonitorCount" :unit="$t('certd.order.unit.times')" />; </span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :title="product.title" class="product-card">
|
<a-card :title="product.title" class="product-card">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<fs-values-format v-model="product.type" :dict="productTypeDictRef"></fs-values-format>
|
<fs-values-format :model-value="product.type" :dict="productTypeDictRef"></fs-values-format>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="product-intro">{{ product.intro || "暂无介绍" }}</div>
|
<div class="product-intro">{{ product.intro || "暂无介绍" }}</div>
|
||||||
@@ -12,9 +12,13 @@
|
|||||||
<suite-value :model-value="product.content.maxPipelineCount" unit="条" />
|
<suite-value :model-value="product.content.maxPipelineCount" unit="条" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />域名数量:</div>
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />域名总数量:</div>
|
||||||
<suite-value :model-value="product.content.maxDomainCount" unit="个" />
|
<suite-value :model-value="product.content.maxDomainCount" unit="个" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o" style="padding-left: 2em">- 其中泛域名数量:</div>
|
||||||
|
<suite-value :model-value="product.content.maxWildcardDomainCount" unit="个" />
|
||||||
|
</div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
||||||
|
|||||||
@@ -20,10 +20,14 @@
|
|||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
||||||
域名数量:
|
域名总数量:
|
||||||
</div>
|
</div>
|
||||||
<suite-value :model-value="detail.domainCount.max" :used="detail.domainCount.used" unit="个" />
|
<suite-value :model-value="detail.domainCount.max" :used="detail.domainCount.used" unit="个" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o ml-20">- 其中泛域名数量:</div>
|
||||||
|
<suite-value :model-value="detail.wildcardDomainCount.max" :used="detail.wildcardDomainCount.used" unit="个" />
|
||||||
|
</div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { request } from "/src/api/service";
|
|||||||
|
|
||||||
const apiPrefix = "/oauth";
|
const apiPrefix = "/oauth";
|
||||||
|
|
||||||
export async function OauthLogin(type: string, forType?: string, from?: string) {
|
export async function OauthLogin(type: string, forType?: string, from?: string, subtype?: string) {
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + `/login`,
|
url: apiPrefix + `/login`,
|
||||||
method: "post",
|
method: "post",
|
||||||
@@ -10,6 +10,7 @@ export async function OauthLogin(type: string, forType?: string, from?: string)
|
|||||||
type,
|
type,
|
||||||
forType: forType || "login",
|
forType: forType || "login",
|
||||||
from: from || "web",
|
from: from || "web",
|
||||||
|
subtype,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
<div>第三方({{ oauthType }})登录成功,您还未绑定账号,请选择</div>
|
<div>第三方({{ oauthType }})登录成功,您还未绑定账号,请选择</div>
|
||||||
|
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<a-button class="w-full mt-10" type="primary" @click="goBindUser">绑定已有账号</a-button>
|
<a-button v-if="!userStore.isLogined" class="w-full mt-10" type="primary" @click="goBindUser">绑定已有账号</a-button>
|
||||||
<a-button v-if="settingStore.sysPublic.registerEnabled" class="w-full mt-10" type="primary" @click="autoRegister">创建新账号</a-button>
|
<a-button v-else class="w-full mt-10" type="primary" @click="doBindCurrent">绑定当前登录账号({{ userStore.getUserInfo.username }} - {{ userStore.getUserInfo.nickName }})</a-button>
|
||||||
|
<a-button v-if="settingStore.sysPublic.registerEnabled" class="w-full mt-10" type="primary" @click="autoRegister">创建新账号绑定</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full mt-10">
|
<div class="w-full mt-10">
|
||||||
@@ -48,7 +49,7 @@ async function handleOauthToken() {
|
|||||||
//登录成功
|
//登录成功
|
||||||
userStore.onLoginSuccess(res);
|
userStore.onLoginSuccess(res);
|
||||||
//跳转到首页
|
//跳转到首页
|
||||||
router.replace("/");
|
router.replace("/index");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.bindRequired) {
|
if (res.bindRequired) {
|
||||||
@@ -63,6 +64,15 @@ async function handleOauthToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function doBindCurrent() {
|
||||||
|
await api.BindUser(validationCode);
|
||||||
|
notification.success({
|
||||||
|
message: "绑定成功",
|
||||||
|
});
|
||||||
|
//跳转到首页
|
||||||
|
router.replace("/certd/mine/user-profile");
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
return;
|
return;
|
||||||
@@ -70,12 +80,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
if (forType === "bind") {
|
if (forType === "bind") {
|
||||||
//从用户中心页面,进行第三方账号的绑定
|
//从用户中心页面,进行第三方账号的绑定
|
||||||
await api.BindUser(validationCode);
|
await doBindCurrent();
|
||||||
notification.success({
|
|
||||||
message: "绑定成功",
|
|
||||||
});
|
|
||||||
//跳转到首页
|
|
||||||
router.replace("/certd/mine/user-profile");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +103,7 @@ async function autoRegister() {
|
|||||||
//登录成功
|
//登录成功
|
||||||
userStore.onLoginSuccess(res);
|
userStore.onLoginSuccess(res);
|
||||||
//跳转到首页
|
//跳转到首页
|
||||||
router.replace("/");
|
router.replace("/index");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center items-center gap-4 flex-wrap md:flex-nowrap">
|
<div class="flex justify-center items-center gap-4 flex-wrap md:flex-nowrap">
|
||||||
<passkey-login></passkey-login>
|
<passkey-login></passkey-login>
|
||||||
<template v-for="item in oauthProviderList" :key="item.type">
|
<template v-for="item in oauthProviderList" :key="buildProviderKey(item)">
|
||||||
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item.name)">
|
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item)">
|
||||||
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
|
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
|
||||||
<div class="ellipsis title" :title="item.addonTitle || item.title">{{ item.addonTitle || item.title }}</div>
|
<div class="ellipsis title" :title="item.addonTitle || item.title">{{ item.addonTitle || item.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,7 +22,17 @@ import { useSettingStore } from "/@/store/settings";
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import PasskeyLogin from "../login/passkey-login.vue";
|
import PasskeyLogin from "../login/passkey-login.vue";
|
||||||
|
|
||||||
const oauthProviderList = ref([]);
|
type OauthProviderItem = {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
subtype?: string;
|
||||||
|
title: string;
|
||||||
|
addonTitle?: string;
|
||||||
|
icon: string;
|
||||||
|
addonId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const oauthProviderList = ref<OauthProviderItem[]>([]);
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
oauthOnly?: boolean;
|
oauthOnly?: boolean;
|
||||||
}>();
|
}>();
|
||||||
@@ -42,15 +52,19 @@ onMounted(async () => {
|
|||||||
if (settingStore.sysPublic.oauthAutoRedirect && queryOauthOnly !== "false") {
|
if (settingStore.sysPublic.oauthAutoRedirect && queryOauthOnly !== "false") {
|
||||||
const firstOauth = oauthProviderList.value.find(item => item.addonId > 0);
|
const firstOauth = oauthProviderList.value.find(item => item.addonId > 0);
|
||||||
if (firstOauth) {
|
if (firstOauth) {
|
||||||
goOauthLogin(firstOauth.name);
|
goOauthLogin(firstOauth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function goOauthLogin(type: string) {
|
function buildProviderKey(item: OauthProviderItem) {
|
||||||
|
return `${item.name}:${item.subtype || ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function goOauthLogin(item: OauthProviderItem) {
|
||||||
//获取第三方登录URL
|
//获取第三方登录URL
|
||||||
const from = "web";
|
const from = "web";
|
||||||
const res = await api.OauthLogin(type, from);
|
const res = await api.OauthLogin(item.name, "login", from, item.subtype);
|
||||||
const loginUrl = res.loginUrl;
|
const loginUrl = res.loginUrl;
|
||||||
window.location.href = loginUrl;
|
window.location.href = loginUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export async function GetSmsTypeDefine(type: string) {
|
|||||||
|
|
||||||
export async function GetOauthProviders() {
|
export async function GetOauthProviders() {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/oauth/providers",
|
url: apiPrefix + "/oauth/providers",
|
||||||
method: "post",
|
method: "post",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
|
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="t('certd.sys.setting.commonHeaders')" :name="['private', 'commonHeaders']">
|
||||||
|
<a-textarea v-model:value="formState.private.commonHeaders" :placeholder="commonHeadersExample" rows="4" />
|
||||||
|
<div class="helper">{{ t("certd.sys.setting.commonHeadersHelper") }}</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="t('certd.dualStackNetwork')" :name="['private', 'dnsResultOrder']">
|
<a-form-item :label="t('certd.dualStackNetwork')" :name="['private', 'dnsResultOrder']">
|
||||||
<a-select v-model:value="formState.private.dnsResultOrder">
|
<a-select v-model:value="formState.private.dnsResultOrder">
|
||||||
<a-select-option value="verbatim">{{ t("certd.default") }}</a-select-option>
|
<a-select-option value="verbatim">{{ t("certd.default") }}</a-select-option>
|
||||||
@@ -64,6 +69,10 @@ const environmentVarsExample = ref(
|
|||||||
`ALIYUN_CLIENT_CONNECT_TIMEOUT=16000 #连接超时,单位毫秒
|
`ALIYUN_CLIENT_CONNECT_TIMEOUT=16000 #连接超时,单位毫秒
|
||||||
ALIYUN_CLIENT_READ_TIMEOUT=16000 #读取数据超时,单位毫秒`
|
ALIYUN_CLIENT_READ_TIMEOUT=16000 #读取数据超时,单位毫秒`
|
||||||
);
|
);
|
||||||
|
const commonHeadersExample = ref(
|
||||||
|
`User-Agent=certd
|
||||||
|
X-Custom-Header=value`
|
||||||
|
);
|
||||||
|
|
||||||
const formState = reactive<Partial<SysSettings>>({
|
const formState = reactive<Partial<SysSettings>>({
|
||||||
public: {},
|
public: {},
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ const formState = reactive<Partial<SysSettings>>({
|
|||||||
const oauthProviders = ref([]);
|
const oauthProviders = ref([]);
|
||||||
async function loadOauthProviders() {
|
async function loadOauthProviders() {
|
||||||
oauthProviders.value = await api.GetOauthProviders();
|
oauthProviders.value = await api.GetOauthProviders();
|
||||||
|
mergeOauthProviderSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
const bindDomain = computed(() => {
|
const bindDomain = computed(() => {
|
||||||
@@ -164,6 +165,16 @@ const onFinish = async (form: any) => {
|
|||||||
function buildCallbackUrl(type: string) {
|
function buildCallbackUrl(type: string) {
|
||||||
return `${window.location.origin}/api/oauth/callback/${type}`;
|
return `${window.location.origin}/api/oauth/callback/${type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeOauthProviderSettings() {
|
||||||
|
const savedProviders = formState.public?.oauthProviders || {};
|
||||||
|
for (const item of oauthProviders.value) {
|
||||||
|
const saved = savedProviders[item.name];
|
||||||
|
if (saved) {
|
||||||
|
item.addonId = saved.addonId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.sys-settings-oauth {
|
.sys-settings-oauth {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
header: t("certd.packageContent"),
|
header: t("certd.packageContent"),
|
||||||
columns: ["content.maxDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.maxMonitorCount"],
|
columns: ["content.maxDomainCount", "content.maxWildcardDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.maxMonitorCount"],
|
||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
header: t("certd.price"),
|
header: t("certd.price"),
|
||||||
@@ -154,6 +154,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"content.maxWildcardDomainCount": {
|
||||||
|
title: t("certd.wildcardDomainCountPart"),
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
key: ["content", "maxWildcardDomainCount"],
|
||||||
|
helper: t("certd.wildcardDomainCountHelper"),
|
||||||
|
component: {
|
||||||
|
name: SuiteValueEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unitCount"),
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: t("certd.requiredField") }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: {
|
||||||
|
name: SuiteValue,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unitCount"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"content.maxPipelineCount": {
|
"content.maxPipelineCount": {
|
||||||
title: t("certd.pipelineCount"),
|
title: t("certd.pipelineCount"),
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<div style="height: 400px">
|
<div style="height: 400px">
|
||||||
<ProductManager @refreshed="onTableRefresh"></ProductManager>
|
<ProductManager @refreshed="onTableRefresh"></ProductManager>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="helper">泛域名数量受域名总数量限制:泛域名会同时占用域名总数量和泛域名数量额度。</div>
|
||||||
<div class="helper">不建议设置免费套餐,可以在下方配置注册赠送套餐,或者在用户套餐管理中手动赠送套餐</div>
|
<div class="helper">不建议设置免费套餐,可以在下方配置注册赠送套餐,或者在用户套餐管理中手动赠送套餐</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
align: "center",
|
align: "center",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"content.maxWildcardDomainCount": {
|
||||||
|
title: t("certd.wildcardDomainCountPart"),
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
key: ["content", "maxWildcardDomainCount"],
|
||||||
|
component: {
|
||||||
|
name: SuiteValueEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unit_count"),
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: t("certd.field_required") }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: {
|
||||||
|
name: SuiteValue,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unit_count"),
|
||||||
|
},
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
},
|
||||||
"content.maxPipelineCount": {
|
"content.maxPipelineCount": {
|
||||||
title: t("certd.pipeline_count"),
|
title: t("certd.pipeline_count"),
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -3,6 +3,37 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复clogin登录丢失state问题 ([22f5cfc](https://github.com/certd/certd/commit/22f5cfcfd8462ca74128329eefb3f48b3ee0b7ea))
|
||||||
|
* 修复clogin多选类型登录失败的bug ([9f878a3](https://github.com/certd/certd/commit/9f878a353cd49b7b10bb0a95610ad236bc920dd2))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 第三方登录自动注册的用户支持设置初始化密码 ([a815d02](https://github.com/certd/certd/commit/a815d0245b97efbb948b33d6fc9d49862ce06889))
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/ui-server
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录彩虹登录不上的bug ([bae4f8e](https://github.com/certd/certd/commit/bae4f8e3209d9f9869ecbd7c01655383bac2fe21))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
|
||||||
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE cd_cert_info ADD COLUMN wildcard_domain_count integer NOT NULL DEFAULT 0;
|
||||||
@@ -39,6 +39,8 @@ input:
|
|||||||
component:
|
component:
|
||||||
name: a-switch
|
name: a-switch
|
||||||
vModel: checked
|
vModel: checked
|
||||||
|
col:
|
||||||
|
span: 24
|
||||||
helper: 是否启用了双重认证
|
helper: 是否启用了双重认证
|
||||||
required: true
|
required: true
|
||||||
deviceId:
|
deviceId:
|
||||||
@@ -48,6 +50,8 @@ input:
|
|||||||
name: synology-device-id-getter
|
name: synology-device-id-getter
|
||||||
type: access
|
type: access
|
||||||
typeName: synology
|
typeName: synology
|
||||||
|
col:
|
||||||
|
span: 24
|
||||||
mergeScript: |2-
|
mergeScript: |2-
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,43 +14,54 @@ input:
|
|||||||
loginType:
|
loginType:
|
||||||
title: 登录类型
|
title: 登录类型
|
||||||
component:
|
component:
|
||||||
name: a-auto-complete
|
name: a-select
|
||||||
|
vModel: value
|
||||||
|
mode: tags
|
||||||
|
multiple: true
|
||||||
options:
|
options:
|
||||||
- label: QQ
|
- label: QQ
|
||||||
value: qq
|
value: qq
|
||||||
|
icon: cib:tencent-qq:#007AFF
|
||||||
- label: 微信
|
- label: 微信
|
||||||
value: wx
|
value: wx
|
||||||
|
icon: simple-icons:wechat:#34C759
|
||||||
- label: 支付宝
|
- label: 支付宝
|
||||||
value: alipay
|
value: alipay
|
||||||
|
icon: simple-icons:alipay:#0099ff
|
||||||
- label: 微博
|
- label: 微博
|
||||||
value: sina
|
value: sina
|
||||||
|
icon: uiw:weibo:#FF3B30
|
||||||
- label: 百度
|
- label: 百度
|
||||||
value: baidu
|
value: baidu
|
||||||
|
icon: simple-icons:baidu:#007AFF
|
||||||
- label: 华为
|
- label: 华为
|
||||||
value: huawei
|
value: huawei
|
||||||
|
icon: simple-icons:huawei:#ff0000
|
||||||
- label: 小米
|
- label: 小米
|
||||||
value: xiaomi
|
value: xiaomi
|
||||||
|
icon: simple-icons:xiaomi:#FF9500
|
||||||
- label: 谷歌
|
- label: 谷歌
|
||||||
value: google
|
value: google
|
||||||
|
icon: flat-color-icons:google
|
||||||
- label: 微软
|
- label: 微软
|
||||||
value: microsoft
|
value: microsoft
|
||||||
|
icon: logos:microsoft-icon
|
||||||
- label: Facebook
|
- label: Facebook
|
||||||
value: facebook
|
value: facebook
|
||||||
|
icon: logos:facebook
|
||||||
- label: Twitter
|
- label: Twitter
|
||||||
value: twitter
|
value: twitter
|
||||||
|
icon: logos:twitter
|
||||||
- label: 钉钉
|
- label: 钉钉
|
||||||
value: dingtalk
|
value: dingtalk
|
||||||
|
icon: ant-design:dingding-outlined:#007AFF
|
||||||
- label: Gitee
|
- label: Gitee
|
||||||
value: gitee
|
value: gitee
|
||||||
|
icon: simple-icons:gitee:#c71d23
|
||||||
- label: Github
|
- label: Github
|
||||||
value: github
|
value: github
|
||||||
|
icon: logos:github-icon
|
||||||
required: true
|
required: true
|
||||||
icon:
|
|
||||||
title: 自定义图标
|
|
||||||
component:
|
|
||||||
name: fs-icon-selector
|
|
||||||
vModel: modelValue
|
|
||||||
required: false
|
|
||||||
appId:
|
appId:
|
||||||
title: AppId
|
title: AppId
|
||||||
helper: 彩虹聚合登录->应用列表->创建应用 获取
|
helper: 彩虹聚合登录->应用列表->创建应用 获取
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ input:
|
|||||||
component:
|
component:
|
||||||
name: fs-icon-selector
|
name: fs-icon-selector
|
||||||
vModel: modelValue
|
vModel: modelValue
|
||||||
|
iconSets:
|
||||||
|
- streamline-logos
|
||||||
|
- logos
|
||||||
|
- fa-brands
|
||||||
|
- fa-solid
|
||||||
|
- fa-regular
|
||||||
|
- carbon
|
||||||
|
- ion
|
||||||
|
- ant-design
|
||||||
|
- mdi
|
||||||
|
- twemoji
|
||||||
|
- svg-spinners
|
||||||
required: false
|
required: false
|
||||||
clientId:
|
clientId:
|
||||||
title: ClientId
|
title: ClientId
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-server",
|
"name": "@certd/ui-server",
|
||||||
"version": "1.39.14",
|
"version": "1.40.0",
|
||||||
"description": "fast-server base midway",
|
"description": "fast-server base midway",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -53,26 +53,26 @@
|
|||||||
"@aws-sdk/client-sts": "^3.990.0",
|
"@aws-sdk/client-sts": "^3.990.0",
|
||||||
"@azure/arm-dns": "^5.1.0",
|
"@azure/arm-dns": "^5.1.0",
|
||||||
"@azure/identity": "^4.13.1",
|
"@azure/identity": "^4.13.1",
|
||||||
"@certd/acme-client": "^1.39.14",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.14",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/commercial-core": "^1.39.14",
|
"@certd/commercial-core": "^1.40.0",
|
||||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||||
"@certd/jdcloud": "^1.39.14",
|
"@certd/jdcloud": "^1.40.0",
|
||||||
"@certd/lib-huawei": "^1.39.14",
|
"@certd/lib-huawei": "^1.40.0",
|
||||||
"@certd/lib-k8s": "^1.39.14",
|
"@certd/lib-k8s": "^1.40.0",
|
||||||
"@certd/lib-server": "^1.39.14",
|
"@certd/lib-server": "^1.40.0",
|
||||||
"@certd/midway-flyway-js": "^1.39.14",
|
"@certd/midway-flyway-js": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.14",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plugin-cert": "^1.39.14",
|
"@certd/plugin-cert": "^1.40.0",
|
||||||
"@certd/plugin-lib": "^1.39.14",
|
"@certd/plugin-lib": "^1.40.0",
|
||||||
"@certd/plugin-plus": "^1.39.14",
|
"@certd/plugin-plus": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.14",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"@google-cloud/dns": "^5.3.1",
|
"@google-cloud/dns": "^5.3.1",
|
||||||
"@google-cloud/publicca": "^1.3.0",
|
"@google-cloud/publicca": "^1.3.0",
|
||||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185",
|
"@huaweicloud/huaweicloud-sdk-cdn": "3.1.185",
|
||||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.185",
|
"@huaweicloud/huaweicloud-sdk-core": "3.1.185",
|
||||||
"@huaweicloud/huaweicloud-sdk-elb": "^3.1.185",
|
"@huaweicloud/huaweicloud-sdk-elb": "3.1.185",
|
||||||
"@huaweicloud/huaweicloud-sdk-iam": "^3.1.185",
|
"@huaweicloud/huaweicloud-sdk-iam": "3.1.185",
|
||||||
"@koa/cors": "^5.0.0",
|
"@koa/cors": "^5.0.0",
|
||||||
"@midwayjs/bootstrap": "3.20.11",
|
"@midwayjs/bootstrap": "3.20.11",
|
||||||
"@midwayjs/cache": "3.14.0",
|
"@midwayjs/cache": "3.14.0",
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export function shouldSetDefaultNoCache(path: string, cacheControl?: string) {
|
||||||
|
if (cacheControl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return path === '/' || path === '/index.html' || path.startsWith('/api');
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { shouldSetDefaultNoCache } from "./configuration-cache.js";
|
||||||
|
|
||||||
|
describe("shouldSetDefaultNoCache", () => {
|
||||||
|
it("sets default no-cache for html and api responses without cache headers", () => {
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/"), true);
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/index.html"), true);
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/api/basic/file/download"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps explicit cache headers from file responses", () => {
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/api/basic/file/download", "public,max-age=259200"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores non-html and non-api paths", () => {
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/static/images/logo.svg"), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,6 +20,7 @@ import * as commercial from '@certd/commercial-core';
|
|||||||
import * as upload from '@midwayjs/upload';
|
import * as upload from '@midwayjs/upload';
|
||||||
import { setLogger } from '@certd/acme-client';
|
import { setLogger } from '@certd/acme-client';
|
||||||
import {HiddenMiddleware} from "./middleware/hidden.js";
|
import {HiddenMiddleware} from "./middleware/hidden.js";
|
||||||
|
import { shouldSetDefaultNoCache } from './configuration-cache.js';
|
||||||
// import * as swagger from '@midwayjs/swagger';
|
// import * as swagger from '@midwayjs/swagger';
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
// process.env.UV_THREADPOOL_SIZE = 2
|
// process.env.UV_THREADPOOL_SIZE = 2
|
||||||
@@ -123,7 +124,7 @@ export class MainConfiguration {
|
|||||||
|
|
||||||
this.app.getMiddleware().insertFirst(async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
this.app.getMiddleware().insertFirst(async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
||||||
await next();
|
await next();
|
||||||
if (ctx.path === '/' || ctx.path === '/index.html' || ctx.path.startsWith("/api")) {
|
if (shouldSetDefaultNoCache(ctx.path, ctx.response.get('Cache-Control'))) {
|
||||||
ctx.response.set('Cache-Control', 'public,max-age=0');
|
ctx.response.set('Cache-Control', 'public,max-age=0');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { getImageDownloadOptions, isImageFile } from "./file-controller.js";
|
||||||
|
|
||||||
|
describe("FileController.isImageFile", () => {
|
||||||
|
it("detects uploaded logo image files", () => {
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo.PNG"), true);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo.svg"), true);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo.webp"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not treat non-image downloads as logo images", () => {
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/archive.zip"), false);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/cert.pem"), false);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds koa-send options that keep image cache headers at 3 days", () => {
|
||||||
|
const options = getImageDownloadOptions("data/upload/public/user/logo.png");
|
||||||
|
|
||||||
|
assert.equal(options?.maxage, 259200000);
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
options?.setHeaders({
|
||||||
|
setHeader(key: string, value: string) {
|
||||||
|
headers[key] = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(headers["Cache-Control"], "public,max-age=259200");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not build cache options for non-image files", () => {
|
||||||
|
assert.equal(getImageDownloadOptions("data/upload/private/user/cert.pem"), undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,6 +5,25 @@ import { nanoid } from 'nanoid';
|
|||||||
import { cache } from '@certd/basic';
|
import { cache } from '@certd/basic';
|
||||||
import { UploadFileInfo } from '@midwayjs/upload';
|
import { UploadFileInfo } from '@midwayjs/upload';
|
||||||
|
|
||||||
|
const imageExtSet = new Set(['.apng', '.avif', '.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg', '.webp']);
|
||||||
|
const imageCacheSeconds = 3 * 24 * 60 * 60;
|
||||||
|
|
||||||
|
export function isImageFile(filePath: string) {
|
||||||
|
return imageExtSet.has(filePath.substring(filePath.lastIndexOf('.')).toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getImageDownloadOptions(filePath: string) {
|
||||||
|
if (!isImageFile(filePath)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
maxage: imageCacheSeconds * 1000,
|
||||||
|
setHeaders(res: any) {
|
||||||
|
res.setHeader('Cache-Control', `public,max-age=${imageCacheSeconds}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@@ -40,8 +59,10 @@ export class FileController extends BaseController {
|
|||||||
userId = this.getUserId();
|
userId = this.getUserId();
|
||||||
}
|
}
|
||||||
const filePath = this.fileService.getFile(key, userId);
|
const filePath = this.fileService.getFile(key, userId);
|
||||||
|
const sendOptions = getImageDownloadOptions(filePath);
|
||||||
|
if (!sendOptions) {
|
||||||
this.ctx.response.attachment(filePath);
|
this.ctx.response.attachment(filePath);
|
||||||
this.ctx.response.set('Cache-Control', 'public,max-age=2592000');
|
}
|
||||||
await send(this.ctx, filePath);
|
await send(this.ctx, filePath, sendOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,24 @@ import { UserEntity } from "../../../modules/sys/authority/entity/user.js";
|
|||||||
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
||||||
import { IOauthProvider } from "../../../plugins/plugin-oauth/api.js";
|
import { IOauthProvider } from "../../../plugins/plugin-oauth/api.js";
|
||||||
|
|
||||||
|
type OauthProviderSetting = {
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
addonId: number;
|
||||||
|
types?: OauthProviderType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type OauthProviderType = {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getOauthBoundType(type: string, subtype?: string) {
|
||||||
|
return subtype ? `${type}:${subtype}` : type;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@@ -41,7 +59,7 @@ export class ConnectController extends BaseController {
|
|||||||
if (!publicSettings?.oauthEnabled) {
|
if (!publicSettings?.oauthEnabled) {
|
||||||
throw new Error("OAuth功能未启用");
|
throw new Error("OAuth功能未启用");
|
||||||
}
|
}
|
||||||
const setting = publicSettings?.oauthProviders?.[type || ""]
|
const setting = publicSettings?.oauthProviders?.[type || ""] as OauthProviderSetting | undefined;
|
||||||
if (!setting) {
|
if (!setting) {
|
||||||
throw new Error(`未配置该OAuth类型:${type}`);
|
throw new Error(`未配置该OAuth类型:${type}`);
|
||||||
}
|
}
|
||||||
@@ -50,19 +68,38 @@ export class ConnectController extends BaseController {
|
|||||||
if (!addon) {
|
if (!addon) {
|
||||||
throw new Error("初始化OAuth插件失败");
|
throw new Error("初始化OAuth插件失败");
|
||||||
}
|
}
|
||||||
return addon as IOauthProvider;
|
return {
|
||||||
|
addon: addon as IOauthProvider,
|
||||||
|
setting,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/login', { description: Constants.per.guest })
|
@Post('/login', { description: Constants.per.guest })
|
||||||
public async login(@Body(ALL) body: { type: string, forType?:string ,from?:string }) {
|
public async login(@Body(ALL) body: { type: string, subtype?: string, forType?:string ,from?:string }) {
|
||||||
|
|
||||||
const addon = await this.getOauthProvider(body.type);
|
const oauthProvider = await this.getOauthProvider(body.type);
|
||||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||||
const bindUrl = installInfo?.bindUrl || "";
|
const bindUrl = installInfo?.bindUrl || "";
|
||||||
//构造登录url
|
//构造登录url
|
||||||
const redirectUrl = `${bindUrl}api/oauth/callback/${body.type}`;
|
const redirectUrl = `${bindUrl}api/oauth/callback/${body.type}`;
|
||||||
const { loginUrl, ticketValue } = await addon.buildLoginUrl({ redirectUri: redirectUrl, forType: body.forType ,from: body.from || "web" });
|
|
||||||
const ticket = this.codeService.setValidationValue(ticketValue)
|
let stateObj = {
|
||||||
|
forType: body.forType || 'login',
|
||||||
|
}
|
||||||
|
const state = utils.hash.base64(JSON.stringify(stateObj))
|
||||||
|
const { loginUrl, ticketValue } = await oauthProvider.addon.buildLoginUrl({
|
||||||
|
redirectUri: redirectUrl,
|
||||||
|
forType: body.forType,
|
||||||
|
from: body.from || "web",
|
||||||
|
subtype: body.subtype,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ticket = this.codeService.setValidationValue({
|
||||||
|
...ticketValue,
|
||||||
|
state,
|
||||||
|
subtype: body.subtype,
|
||||||
|
})
|
||||||
this.ctx.cookies.set("oauth_ticket", ticket, {
|
this.ctx.cookies.set("oauth_ticket", ticket, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
// secure: true,
|
// secure: true,
|
||||||
@@ -78,7 +115,7 @@ export class ConnectController extends BaseController {
|
|||||||
checkPlus()
|
checkPlus()
|
||||||
|
|
||||||
//处理登录回调
|
//处理登录回调
|
||||||
const addon = await this.getOauthProvider(type);
|
const oauthProvider = await this.getOauthProvider(type);
|
||||||
const request = this.ctx.request;
|
const request = this.ctx.request;
|
||||||
// const ticketValue = this.codeService.getValidationValue(ticket);
|
// const ticketValue = this.codeService.getValidationValue(ticket);
|
||||||
// if (!ticketValue) {
|
// if (!ticketValue) {
|
||||||
@@ -98,7 +135,7 @@ export class ConnectController extends BaseController {
|
|||||||
const bindUrl = installInfo?.bindUrl || "";
|
const bindUrl = installInfo?.bindUrl || "";
|
||||||
const currentUrl = `${bindUrl}api/oauth/callback/${type}?${request.querystring}`
|
const currentUrl = `${bindUrl}api/oauth/callback/${type}?${request.querystring}`
|
||||||
try {
|
try {
|
||||||
const tokenRes = await addon.onCallback({
|
const tokenRes = await oauthProvider.addon.onCallback({
|
||||||
code: query.code,
|
code: query.code,
|
||||||
state: query.state,
|
state: query.state,
|
||||||
ticketValue,
|
ticketValue,
|
||||||
@@ -108,11 +145,14 @@ export class ConnectController extends BaseController {
|
|||||||
const userInfo = tokenRes.userInfo;
|
const userInfo = tokenRes.userInfo;
|
||||||
|
|
||||||
const validationCode = await this.codeService.setValidationValue({
|
const validationCode = await this.codeService.setValidationValue({
|
||||||
type,
|
type: getOauthBoundType(type, ticketValue.subtype),
|
||||||
userInfo,
|
userInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = JSON.parse(utils.hash.base64Decode(query.state));
|
let state = {forType:""}
|
||||||
|
if (query.state) {
|
||||||
|
state = JSON.parse(utils.hash.base64Decode(query.state));
|
||||||
|
}
|
||||||
|
|
||||||
const redirectUrl = `${bindUrl}#/oauth/callback/${type}?validationCode=${validationCode}&forType=${state.forType}`;
|
const redirectUrl = `${bindUrl}#/oauth/callback/${type}?validationCode=${validationCode}&forType=${state.forType}`;
|
||||||
this.ctx.redirect(redirectUrl);
|
this.ctx.redirect(redirectUrl);
|
||||||
@@ -126,8 +166,10 @@ export class ConnectController extends BaseController {
|
|||||||
@Post('/getLogoutUrl', { description: Constants.per.guest })
|
@Post('/getLogoutUrl', { description: Constants.per.guest })
|
||||||
public async logout(@Body(ALL) body: any) {
|
public async logout(@Body(ALL) body: any) {
|
||||||
checkPlus()
|
checkPlus()
|
||||||
const addon = await this.getOauthProvider(body.type);
|
const oauthProvider = await this.getOauthProvider(body.type);
|
||||||
const { logoutUrl } = await addon.buildLogoutUrl(body);
|
const { logoutUrl } = await oauthProvider.addon.buildLogoutUrl({
|
||||||
|
...body,
|
||||||
|
});
|
||||||
return this.ok({ logoutUrl });
|
return this.ok({ logoutUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +183,7 @@ export class ConnectController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const type = validationValue.type;
|
const type = validationValue.type;
|
||||||
if (type !== body.type) {
|
if (type !== body.type && !type.startsWith(`${body.type}:`)) {
|
||||||
throw new Error("校验码错误");
|
throw new Error("校验码错误");
|
||||||
}
|
}
|
||||||
const userInfo = validationValue.userInfo;
|
const userInfo = validationValue.userInfo;
|
||||||
@@ -259,16 +301,32 @@ export class ConnectController extends BaseController {
|
|||||||
provider.addonId = conf.addonId;
|
provider.addonId = conf.addonId;
|
||||||
provider.addonTitle = addonEntity.name;
|
provider.addonTitle = addonEntity.name;
|
||||||
|
|
||||||
const addon = await this.addonGetterService.getAddonById(conf.addonId,true,0,null);
|
const addon = await this.addonGetterService.getAddonById(conf.addonId,true,0,null) as IOauthProvider & { icon?: string; types?: OauthProviderType[] };
|
||||||
const {logoutUrl} = await addon.buildLogoutUrl();
|
const {logoutUrl} = await addon.buildLogoutUrl({});
|
||||||
if (logoutUrl){
|
if (logoutUrl){
|
||||||
provider.logoutUrl = logoutUrl;
|
provider.logoutUrl = logoutUrl;
|
||||||
}
|
}
|
||||||
if(addon.icon){
|
if(addon.icon){
|
||||||
provider.icon = addon.icon;
|
provider.icon = addon.icon;
|
||||||
}
|
}
|
||||||
|
if(addon.types?.length){
|
||||||
|
provider.types = addon.types;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (provider.addonId && provider.types?.length) {
|
||||||
|
for (const subtype of provider.types) {
|
||||||
|
list.push({
|
||||||
|
...provider,
|
||||||
|
name: type,
|
||||||
|
subtype: subtype.type,
|
||||||
|
title: subtype.name,
|
||||||
|
icon: subtype.icon || provider.icon,
|
||||||
|
addonTitle: subtype.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
list.push(provider);
|
list.push(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
await this.service.savePrivateSettings(privateSettings);
|
await this.service.savePrivateSettings(privateSettings);
|
||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/stopOtherUserTimer', { description: 'sys:settings:edit' })
|
@Post('/stopOtherUserTimer', { description: 'sys:settings:edit' })
|
||||||
async stopOtherUserTimer(@Body(ALL) body) {
|
async stopOtherUserTimer(@Body(ALL) body) {
|
||||||
await this.pipelineService.stopOtherUserPipeline(1);
|
await this.pipelineService.stopOtherUserPipeline(1);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { BaseController, Constants, SysSettingsService } from '@certd/lib-server';
|
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server";
|
||||||
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
|
||||||
import { PasskeyService } from '../../../modules/login/service/passkey-service.js';
|
import { PasskeyService } from "../../../modules/login/service/passkey-service.js";
|
||||||
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
|
import { RoleService } from "../../../modules/sys/authority/service/role-service.js";
|
||||||
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
||||||
import { ApiTags } from '@midwayjs/swagger';
|
import { ApiTags } from "@midwayjs/swagger";
|
||||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@Controller('/api/mine')
|
@Controller("/api/mine")
|
||||||
@ApiTags(['mine'])
|
@ApiTags(["mine"])
|
||||||
export class MineController extends BaseController {
|
export class MineController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
@@ -27,28 +27,38 @@ export class MineController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
sysSettingsService: SysSettingsService;
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
@Post('/info', { description: Constants.per.authOnly, summary: '查询用户信息' })
|
@Post("/info", { description: Constants.per.authOnly, summary: "查询用户信息" })
|
||||||
public async info() {
|
public async info() {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
const user = await this.userService.info(userId);
|
const user = await this.userService.info(userId);
|
||||||
const isWeak = await this.userService.checkPassword('123456', user.password, user.passwordVersion);
|
const isWeak = await this.userService.checkPassword("123456", user.password, user.passwordVersion);
|
||||||
if (isWeak) {
|
if (isWeak) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
user.isWeak = true;
|
user.isWeak = true;
|
||||||
}
|
}
|
||||||
|
const needInitPassword = user.password === "changeme";
|
||||||
user.roleIds = await this.roleService.getRoleIdsByUserId(userId);
|
user.roleIds = await this.roleService.getRoleIdsByUserId(userId);
|
||||||
delete user.password;
|
delete user.password;
|
||||||
|
//@ts-ignore
|
||||||
|
user.needInitPassword = needInitPassword;
|
||||||
return this.ok(user);
|
return this.ok(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/changePassword', { description: Constants.per.authOnly, summary: '修改密码' })
|
@Post("/changePassword", { description: Constants.per.authOnly, summary: "修改密码" })
|
||||||
public async changePassword(@Body(ALL) body: any) {
|
public async changePassword(@Body(ALL) body: any) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
await this.userService.changePassword(userId, body);
|
await this.userService.changePassword(userId, body);
|
||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/updateProfile', { description: Constants.per.authOnly, summary: '更新用户资料' })
|
@Post("/initPassword", { description: Constants.per.authOnly, summary: "初始化密码" })
|
||||||
|
public async initPassword(@Body(ALL) body: any) {
|
||||||
|
const userId = this.getUserId();
|
||||||
|
await this.userService.initPassword(userId, body);
|
||||||
|
return this.ok({});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/updateProfile", { description: Constants.per.authOnly, summary: "更新用户资料" })
|
||||||
public async updateProfile(@Body(ALL) body: any) {
|
public async updateProfile(@Body(ALL) body: any) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
|
|
||||||
@@ -59,7 +69,7 @@ export class MineController extends BaseController {
|
|||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/capability', { description: Constants.per.authOnly, summary: '查询联系方式绑定能力' })
|
@Post("/contact/capability", { description: Constants.per.authOnly, summary: "查询联系方式绑定能力" })
|
||||||
public async contactCapability() {
|
public async contactCapability() {
|
||||||
const settings = await this.sysSettingsService.getPrivateSettings();
|
const settings = await this.sysSettingsService.getPrivateSettings();
|
||||||
return this.ok({
|
return this.ok({
|
||||||
@@ -67,27 +77,27 @@ export class MineController extends BaseController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/verifyIdentity', { description: Constants.per.authOnly, summary: '验证本人操作' })
|
@Post("/contact/verifyIdentity", { description: Constants.per.authOnly, summary: "验证本人操作" })
|
||||||
public async verifyContactIdentity(@Body(ALL) body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }) {
|
public async verifyContactIdentity(@Body(ALL) body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
await this.userService.verifyIdentity(userId, body, this.codeService);
|
await this.userService.verifyIdentity(userId, body, this.codeService);
|
||||||
const validationCode = this.codeService.setValidationValue({
|
const validationCode = this.codeService.setValidationValue({
|
||||||
type: 'contactIdentity',
|
type: "contactIdentity",
|
||||||
userId,
|
userId,
|
||||||
identityType: body.identityType,
|
identityType: body.identityType,
|
||||||
});
|
});
|
||||||
return this.ok({ validationCode });
|
return this.ok({ validationCode });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/mobile', { description: Constants.per.authOnly, summary: '绑定或修改手机号' })
|
@Post("/contact/mobile", { description: Constants.per.authOnly, summary: "绑定或修改手机号" })
|
||||||
public async updateMobile(@Body(ALL) body: { phoneCode?: string; mobile: string; validateCode: string; identityValidationCode: string }) {
|
public async updateMobile(@Body(ALL) body: { phoneCode?: string; mobile: string; validateCode: string; identityValidationCode: string }) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
||||||
await this.codeService.checkSmsCode({
|
await this.codeService.checkSmsCode({
|
||||||
mobile: body.mobile,
|
mobile: body.mobile,
|
||||||
phoneCode: body.phoneCode || '86',
|
phoneCode: body.phoneCode || "86",
|
||||||
smsCode: body.validateCode,
|
smsCode: body.validateCode,
|
||||||
verificationType: 'bindMobile',
|
verificationType: "bindMobile",
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
await this.userService.updateMobile(userId, {
|
await this.userService.updateMobile(userId, {
|
||||||
@@ -97,14 +107,14 @@ export class MineController extends BaseController {
|
|||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/email', { description: Constants.per.authOnly, summary: '绑定或修改邮箱' })
|
@Post("/contact/email", { description: Constants.per.authOnly, summary: "绑定或修改邮箱" })
|
||||||
public async updateEmail(@Body(ALL) body: { email: string; validateCode: string; identityValidationCode: string }) {
|
public async updateEmail(@Body(ALL) body: { email: string; validateCode: string; identityValidationCode: string }) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
||||||
this.codeService.checkEmailCode({
|
this.codeService.checkEmailCode({
|
||||||
email: body.email,
|
email: body.email,
|
||||||
validateCode: body.validateCode,
|
validateCode: body.validateCode,
|
||||||
verificationType: 'bindEmail',
|
verificationType: "bindEmail",
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
await this.userService.updateEmail(userId, {
|
await this.userService.updateEmail(userId, {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { AutoLoadPlugins } from "./auto-load-plugins.js";
|
|||||||
import { AutoCron } from "./auto-cron.js";
|
import { AutoCron } from "./auto-cron.js";
|
||||||
import { AutoMitterRegister } from "./auto-mitter-register.js";
|
import { AutoMitterRegister } from "./auto-mitter-register.js";
|
||||||
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
|
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
|
||||||
import { AutoFix } from "./auto-fix.js";
|
import { AutoFix } from "./fix/auto-fix.js";
|
||||||
import { AutoPrint } from "./auto-print.js";
|
import { AutoPrint } from "./auto-print.js";
|
||||||
|
|
||||||
@Autoload()
|
@Autoload()
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { AutoFix } from "./auto-fix.js";
|
||||||
|
|
||||||
|
describe("AutoFix", () => {
|
||||||
|
it("runs unfinished fix tasks in order and marks them fixed", async () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
let savedSetting: any;
|
||||||
|
const autoFix = new AutoFix();
|
||||||
|
autoFix.sysSettingsService = {
|
||||||
|
async getSetting() {
|
||||||
|
return {
|
||||||
|
fixed: {
|
||||||
|
"oauth-subtype-bound-type": true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async saveSetting(setting: any) {
|
||||||
|
savedSetting = {
|
||||||
|
fixed: { ...setting.fixed },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
autoFix.googleCommonEabAccountKeyFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("google");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
autoFix.oauthSubtypeBoundTypeFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("oauth");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
autoFix.certInfoWildcardDomainCountFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("cert");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
autoFix.suiteContentWildcardDomainCountFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("suite");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await autoFix.init();
|
||||||
|
|
||||||
|
assert.deepEqual(calls, ["google", "cert", "suite"]);
|
||||||
|
assert.equal(savedSetting.fixed["google-common-eab-account-key"], true);
|
||||||
|
assert.equal(savedSetting.fixed["oauth-subtype-bound-type"], true);
|
||||||
|
assert.equal(savedSetting.fixed["cert-info-wildcard-domain-count"], true);
|
||||||
|
assert.equal(savedSetting.fixed["suite-content-wildcard-domain-count"], true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initializes missing fixed map", async () => {
|
||||||
|
const autoFix = new AutoFix();
|
||||||
|
autoFix.sysSettingsService = {
|
||||||
|
async getSetting() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
async saveSetting() {},
|
||||||
|
} as any;
|
||||||
|
autoFix.googleCommonEabAccountKeyFix = { async init() {} } as any;
|
||||||
|
autoFix.oauthSubtypeBoundTypeFix = { async init() {} } as any;
|
||||||
|
autoFix.certInfoWildcardDomainCountFix = { async init() {} } as any;
|
||||||
|
autoFix.suiteContentWildcardDomainCountFix = { async init() {} } as any;
|
||||||
|
|
||||||
|
await autoFix.init();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { SysAutoFixSetting, SysSettingsService } from "@certd/lib-server";
|
||||||
|
import { GoogleCommonEabAccountKeyFix } from "./google-common-eab-account-key-fix.js";
|
||||||
|
import { OauthSubtypeBoundTypeFix } from "./oauth-subtype-bound-type-fix.js";
|
||||||
|
import { CertInfoWildcardDomainCountFix } from "./cert-info-wildcard-domain-count-fix.js";
|
||||||
|
import { SuiteContentWildcardDomainCountFix } from "./suite-content-wildcard-domain-count-fix.js";
|
||||||
|
|
||||||
|
type AutoFixTask = {
|
||||||
|
key: string;
|
||||||
|
fix: {
|
||||||
|
init(): Promise<void>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class AutoFix {
|
||||||
|
@Inject()
|
||||||
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
googleCommonEabAccountKeyFix: GoogleCommonEabAccountKeyFix;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
oauthSubtypeBoundTypeFix: OauthSubtypeBoundTypeFix;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
certInfoWildcardDomainCountFix: CertInfoWildcardDomainCountFix;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
suiteContentWildcardDomainCountFix: SuiteContentWildcardDomainCountFix;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const setting = await this.sysSettingsService.getSetting<SysAutoFixSetting>(SysAutoFixSetting);
|
||||||
|
setting.fixed = setting.fixed || {};
|
||||||
|
const tasks: AutoFixTask[] = [
|
||||||
|
{
|
||||||
|
key: "google-common-eab-account-key",
|
||||||
|
fix: this.googleCommonEabAccountKeyFix,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "oauth-subtype-bound-type",
|
||||||
|
fix: this.oauthSubtypeBoundTypeFix,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "cert-info-wildcard-domain-count",
|
||||||
|
fix: this.certInfoWildcardDomainCountFix,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "suite-content-wildcard-domain-count",
|
||||||
|
fix: this.suiteContentWildcardDomainCountFix,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const task of tasks) {
|
||||||
|
if (setting.fixed?.[task.key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await task.fix.init();
|
||||||
|
setting.fixed[task.key] = true;
|
||||||
|
await this.sysSettingsService.saveSetting(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { CertInfoWildcardDomainCountFix } from "./cert-info-wildcard-domain-count-fix.js";
|
||||||
|
|
||||||
|
describe("CertInfoWildcardDomainCountFix", () => {
|
||||||
|
it("fixes cert info wildcard domain count only when value changed", async () => {
|
||||||
|
const updated: any[] = [];
|
||||||
|
const rows = [
|
||||||
|
{ id: 1, domains: "*.a.com,a.com, *.b.com ", wildcardDomainCount: 0 },
|
||||||
|
{ id: 2, domains: "c.com", wildcardDomainCount: 0 },
|
||||||
|
{ id: 3, domains: "*.d.com", wildcardDomainCount: 1 },
|
||||||
|
];
|
||||||
|
const fix = new CertInfoWildcardDomainCountFix();
|
||||||
|
fix.certInfoService = {
|
||||||
|
countWildcardDomains(domains: string[]) {
|
||||||
|
return domains.filter(item => item.trim().toLowerCase().startsWith("*.")).length;
|
||||||
|
},
|
||||||
|
async find() {
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
async update(value: any) {
|
||||||
|
updated.push(value);
|
||||||
|
const row = rows.find(item => item.id === value.id);
|
||||||
|
Object.assign(row, value);
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.deepEqual(updated, [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
wildcardDomainCount: 2,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { logger } from "@certd/basic";
|
||||||
|
import { CertInfoService } from "../../monitor/service/cert-info-service.js";
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class CertInfoWildcardDomainCountFix {
|
||||||
|
@Inject()
|
||||||
|
certInfoService: CertInfoService;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.certInfoService) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const list = await this.certInfoService.find({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
domains: true,
|
||||||
|
wildcardDomainCount: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let fixedCount = 0;
|
||||||
|
for (const item of list) {
|
||||||
|
if (!item.domains) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const wildcardDomainCount = this.certInfoService.countWildcardDomains(item.domains.split(","));
|
||||||
|
if ((item.wildcardDomainCount ?? 0) === wildcardDomainCount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await this.certInfoService.update({
|
||||||
|
id: item.id,
|
||||||
|
wildcardDomainCount,
|
||||||
|
});
|
||||||
|
fixedCount++;
|
||||||
|
}
|
||||||
|
if (fixedCount > 0) {
|
||||||
|
logger.info(`已修复证书泛域名数量历史数据,数量=${fixedCount}`);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error("修复证书泛域名数量历史数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-38
@@ -1,16 +1,8 @@
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import esmock from "esmock";
|
import esmock from "esmock";
|
||||||
import { AutoFix, buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, parseStorageValue } from "./auto-fix.js";
|
import { buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, GoogleCommonEabAccountKeyFix, parseStorageValue } from "./google-common-eab-account-key-fix.js";
|
||||||
|
|
||||||
function createAutoFix(options: { pluginConfigService?: any; accessService?: any; storageService?: any }) {
|
describe("GoogleCommonEabAccountKeyFix", () => {
|
||||||
const autoFix = new AutoFix();
|
|
||||||
autoFix.pluginConfigService = options.pluginConfigService;
|
|
||||||
autoFix.accessService = options.accessService;
|
|
||||||
autoFix.storageService = options.storageService;
|
|
||||||
return autoFix;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("AutoFix", () => {
|
|
||||||
it("parses legacy storage values", () => {
|
it("parses legacy storage values", () => {
|
||||||
const config = parseStorageValue(
|
const config = parseStorageValue(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -45,10 +37,8 @@ describe("AutoFix", () => {
|
|||||||
it("finds legacy Google account config by exact email key only", async () => {
|
it("finds legacy Google account config by exact email key only", async () => {
|
||||||
let findOneWhere: any;
|
let findOneWhere: any;
|
||||||
let findCalled = false;
|
let findCalled = false;
|
||||||
const autoFix = createAutoFix({
|
const fix = new GoogleCommonEabAccountKeyFix();
|
||||||
pluginConfigService: null as any,
|
fix.storageService = {
|
||||||
accessService: null as any,
|
|
||||||
storageService: {
|
|
||||||
getRepository() {
|
getRepository() {
|
||||||
return {
|
return {
|
||||||
async findOne(options: any) {
|
async findOne(options: any) {
|
||||||
@@ -67,10 +57,9 @@ describe("AutoFix", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
} as any,
|
} as any;
|
||||||
});
|
|
||||||
|
|
||||||
const config = await autoFix.getLegacyGoogleAccountConfig("user@example.com");
|
const config = await fix.getLegacyGoogleAccountConfig("user@example.com");
|
||||||
|
|
||||||
assert.equal(config.privateKey, "legacy-private-key");
|
assert.equal(config.privateKey, "legacy-private-key");
|
||||||
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
||||||
@@ -79,43 +68,42 @@ describe("AutoFix", () => {
|
|||||||
|
|
||||||
it("does not query legacy Google account config without email", async () => {
|
it("does not query legacy Google account config without email", async () => {
|
||||||
let repositoryCalled = false;
|
let repositoryCalled = false;
|
||||||
const autoFix = createAutoFix({
|
const fix = new GoogleCommonEabAccountKeyFix();
|
||||||
pluginConfigService: null as any,
|
fix.storageService = {
|
||||||
accessService: null as any,
|
|
||||||
storageService: {
|
|
||||||
getRepository() {
|
getRepository() {
|
||||||
repositoryCalled = true;
|
repositoryCalled = true;
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
} as any,
|
} as any;
|
||||||
});
|
|
||||||
|
|
||||||
const config = await autoFix.getLegacyGoogleAccountConfig();
|
const config = await fix.getLegacyGoogleAccountConfig();
|
||||||
|
|
||||||
assert.equal(config, null);
|
assert.equal(config, null);
|
||||||
assert.equal(repositoryCalled, false);
|
assert.equal(repositoryCalled, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips Google common EAB account key fix outside commercial edition", async () => {
|
it("skips Google common EAB account key fix outside commercial edition", async () => {
|
||||||
|
const { GoogleCommonEabAccountKeyFix: MockedFix } = await esmock("./google-common-eab-account-key-fix.js", {
|
||||||
|
"@certd/plus-core": {
|
||||||
|
isComm: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
let pluginConfigCalled = false;
|
let pluginConfigCalled = false;
|
||||||
const autoFix = createAutoFix({
|
const fix = new MockedFix();
|
||||||
pluginConfigService: {
|
fix.pluginConfigService = {
|
||||||
async getPluginConfig() {
|
async getPluginConfig() {
|
||||||
pluginConfigCalled = true;
|
pluginConfigCalled = true;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
} as any,
|
};
|
||||||
accessService: null as any,
|
|
||||||
storageService: null as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
await autoFix.init();
|
await fix.init();
|
||||||
|
|
||||||
assert.equal(pluginConfigCalled, false);
|
assert.equal(pluginConfigCalled, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fixes Google common EAB account key in commercial edition", async () => {
|
it("fixes Google common EAB account key in commercial edition", async () => {
|
||||||
const { AutoFix: MockedAutoFix } = await esmock("./auto-fix.js", {
|
const { GoogleCommonEabAccountKeyFix: MockedFix } = await esmock("./google-common-eab-account-key-fix.js", {
|
||||||
"@certd/plus-core": {
|
"@certd/plus-core": {
|
||||||
isComm: () => true,
|
isComm: () => true,
|
||||||
},
|
},
|
||||||
@@ -123,8 +111,8 @@ describe("AutoFix", () => {
|
|||||||
let getAccessByIdArgs: any[] = [];
|
let getAccessByIdArgs: any[] = [];
|
||||||
let findOneWhere: any;
|
let findOneWhere: any;
|
||||||
let updateAccessParam: any;
|
let updateAccessParam: any;
|
||||||
const autoFix = new MockedAutoFix();
|
const fix = new MockedFix();
|
||||||
autoFix.pluginConfigService = {
|
fix.pluginConfigService = {
|
||||||
async getPluginConfig(options: any) {
|
async getPluginConfig(options: any) {
|
||||||
assert.deepEqual(options, {
|
assert.deepEqual(options, {
|
||||||
name: "CertApply",
|
name: "CertApply",
|
||||||
@@ -139,7 +127,7 @@ describe("AutoFix", () => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
autoFix.accessService = {
|
fix.accessService = {
|
||||||
async getAccessById(...args: any[]) {
|
async getAccessById(...args: any[]) {
|
||||||
getAccessByIdArgs = args;
|
getAccessByIdArgs = args;
|
||||||
return {
|
return {
|
||||||
@@ -151,7 +139,7 @@ describe("AutoFix", () => {
|
|||||||
updateAccessParam = param;
|
updateAccessParam = param;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
autoFix.storageService = {
|
fix.storageService = {
|
||||||
getRepository() {
|
getRepository() {
|
||||||
return {
|
return {
|
||||||
async findOne(options: any) {
|
async findOne(options: any) {
|
||||||
@@ -168,7 +156,7 @@ describe("AutoFix", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await autoFix.fixGoogleCommonEabAccountKey();
|
await fix.init();
|
||||||
|
|
||||||
assert.deepEqual(getAccessByIdArgs, [12, false]);
|
assert.deepEqual(getAccessByIdArgs, [12, false]);
|
||||||
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
||||||
@@ -178,5 +166,4 @@ describe("AutoFix", () => {
|
|||||||
accountKey: buildEabAccountKeyValue("kid-1", "legacy-private-key"),
|
accountKey: buildEabAccountKeyValue("kid-1", "legacy-private-key"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
+3
-6
@@ -2,8 +2,8 @@ import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
|||||||
import { logger } from "@certd/basic";
|
import { logger } from "@certd/basic";
|
||||||
import { AccessService } from "@certd/lib-server";
|
import { AccessService } from "@certd/lib-server";
|
||||||
import { isComm } from "@certd/plus-core";
|
import { isComm } from "@certd/plus-core";
|
||||||
import { PluginConfigService } from "../plugin/service/plugin-config-service.js";
|
import { PluginConfigService } from "../../plugin/service/plugin-config-service.js";
|
||||||
import { StorageService } from "../pipeline/service/storage-service.js";
|
import { StorageService } from "../../pipeline/service/storage-service.js";
|
||||||
|
|
||||||
export function parseStorageValue(value?: string) {
|
export function parseStorageValue(value?: string) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -35,7 +35,7 @@ export function buildLegacyGoogleAccountConfigWhere(email: string) {
|
|||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
export class AutoFix {
|
export class GoogleCommonEabAccountKeyFix {
|
||||||
@Inject()
|
@Inject()
|
||||||
pluginConfigService: PluginConfigService;
|
pluginConfigService: PluginConfigService;
|
||||||
|
|
||||||
@@ -46,9 +46,6 @@ export class AutoFix {
|
|||||||
storageService: StorageService;
|
storageService: StorageService;
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.fixGoogleCommonEabAccountKey();
|
|
||||||
}
|
|
||||||
async fixGoogleCommonEabAccountKey() {
|
|
||||||
if (!isComm()) {
|
if (!isComm()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { buildOauthBoundType, OauthSubtypeBoundTypeFix } from "./oauth-subtype-bound-type-fix.js";
|
||||||
|
|
||||||
|
describe("OauthSubtypeBoundTypeFix", () => {
|
||||||
|
it("builds OAuth subtype bound type", () => {
|
||||||
|
assert.equal(buildOauthBoundType("clogin", "alipay"), "clogin:alipay");
|
||||||
|
assert.equal(buildOauthBoundType("github"), "github");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fixes legacy OAuth bound type from string addon loginType and converts loginType to array", async () => {
|
||||||
|
const updates: any[] = [];
|
||||||
|
const fix = new OauthSubtypeBoundTypeFix();
|
||||||
|
fix.sysSettingsService = {
|
||||||
|
async getPublicSettings() {
|
||||||
|
return {
|
||||||
|
oauthProviders: {
|
||||||
|
clogin: {
|
||||||
|
addonId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
fix.oauthBoundService = {
|
||||||
|
async transaction(callback: any) {
|
||||||
|
return await callback({
|
||||||
|
async findOne(entity: any, options: any) {
|
||||||
|
assert.equal(entity.name, "AddonEntity");
|
||||||
|
assert.deepEqual(options, { where: { id: 1 } });
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
setting: JSON.stringify({
|
||||||
|
loginType: "alipay",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async update(entity: any, where: any, value: any) {
|
||||||
|
updates.push({ entity: entity.name, where, value });
|
||||||
|
return { affected: entity.name === "OauthBoundEntity" ? 1 : 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.deepEqual(updates[0], {
|
||||||
|
entity: "OauthBoundEntity",
|
||||||
|
where: { type: "clogin" },
|
||||||
|
value: { type: "clogin:alipay" },
|
||||||
|
});
|
||||||
|
assert.equal(updates[1].entity, "AddonEntity");
|
||||||
|
assert.deepEqual(updates[1].where, { id: 1 });
|
||||||
|
assert.deepEqual(JSON.parse(updates[1].value.setting).loginType, ["alipay"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips OAuth subtype fix when addon loginType is already not legacy string", async () => {
|
||||||
|
let updateCalled = false;
|
||||||
|
const fix = new OauthSubtypeBoundTypeFix();
|
||||||
|
fix.sysSettingsService = {
|
||||||
|
async getPublicSettings() {
|
||||||
|
return {
|
||||||
|
oauthProviders: {
|
||||||
|
clogin: {
|
||||||
|
addonId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
fix.oauthBoundService = {
|
||||||
|
async transaction(callback: any) {
|
||||||
|
return await callback({
|
||||||
|
async findOne() {
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
setting: JSON.stringify({
|
||||||
|
loginType: ["alipay", "github"],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
updateCalled = true;
|
||||||
|
return { affected: 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.equal(updateCalled, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { logger } from "@certd/basic";
|
||||||
|
import { AddonEntity, SysSettingsService } from "@certd/lib-server";
|
||||||
|
import { OauthBoundService } from "../../login/service/oauth-bound-service.js";
|
||||||
|
import { OauthBoundEntity } from "../../login/entity/oauth-bound.js";
|
||||||
|
|
||||||
|
export function buildOauthBoundType(type: string, subtype?: string) {
|
||||||
|
return subtype ? `${type}:${subtype}` : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class OauthSubtypeBoundTypeFix {
|
||||||
|
@Inject()
|
||||||
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
oauthBoundService: OauthBoundService;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const publicSettings = await this.sysSettingsService.getPublicSettings();
|
||||||
|
const oauthProviders = publicSettings.oauthProviders || {};
|
||||||
|
await this.oauthBoundService.transaction(async manager => {
|
||||||
|
for (const [type, provider] of Object.entries(oauthProviders)) {
|
||||||
|
if (!provider.addonId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addonEntity = await manager.findOne(AddonEntity, { where: { id: provider.addonId } });
|
||||||
|
const legacyLoginType = this.getLegacyAddonLoginType(addonEntity?.setting);
|
||||||
|
if (!legacyLoginType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newType = buildOauthBoundType(type, legacyLoginType);
|
||||||
|
const res = await manager.update(OauthBoundEntity, { type }, { type: newType });
|
||||||
|
if (res.affected) {
|
||||||
|
logger.info(`已修复OAuth绑定历史数据,${type} -> ${newType},数量=${res.affected}`);
|
||||||
|
}
|
||||||
|
await this.convertLegacyAddonLoginTypeToArray(addonEntity, legacyLoginType, manager);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error("修复OAuth subtype绑定历史数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLegacyAddonLoginType(settingValue?: string) {
|
||||||
|
if (!settingValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const setting = JSON.parse(settingValue);
|
||||||
|
return typeof setting.loginType === "string" && setting.loginType ? setting.loginType : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async convertLegacyAddonLoginTypeToArray(addonEntity: AddonEntity | null, loginType: string, manager: any) {
|
||||||
|
if (!addonEntity?.setting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const setting = JSON.parse(addonEntity.setting);
|
||||||
|
if (typeof setting.loginType !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setting.loginType = [loginType];
|
||||||
|
await manager.update(AddonEntity, { id: addonEntity.id }, { setting: JSON.stringify(setting) });
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { fixSuiteContentWildcardDomainCount, SuiteContentWildcardDomainCountFix } from "./suite-content-wildcard-domain-count-fix.js";
|
||||||
|
|
||||||
|
describe("SuiteContentWildcardDomainCountFix", () => {
|
||||||
|
it("fills missing suite wildcard domain count from total domain count", () => {
|
||||||
|
const fixed = fixSuiteContentWildcardDomainCount(
|
||||||
|
JSON.stringify({
|
||||||
|
maxDomainCount: 10,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(JSON.parse(fixed).maxWildcardDomainCount, 10);
|
||||||
|
assert.equal(
|
||||||
|
JSON.parse(
|
||||||
|
fixSuiteContentWildcardDomainCount(
|
||||||
|
JSON.stringify({
|
||||||
|
maxDomainCount: -1,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).maxWildcardDomainCount,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
fixSuiteContentWildcardDomainCount(
|
||||||
|
JSON.stringify({
|
||||||
|
maxDomainCount: 10,
|
||||||
|
maxWildcardDomainCount: 3,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fixes suite content wildcard domain count in product and user suite tables", async () => {
|
||||||
|
const rows = {
|
||||||
|
cd_product: [
|
||||||
|
{ id: 1, content: JSON.stringify({ maxDomainCount: 1 }) },
|
||||||
|
{ id: 2, content: JSON.stringify({ maxDomainCount: 1, maxWildcardDomainCount: 0 }) },
|
||||||
|
],
|
||||||
|
cd_user_suite: [{ id: 3, content: JSON.stringify({ maxDomainCount: 2 }) }],
|
||||||
|
};
|
||||||
|
const updates: any[] = [];
|
||||||
|
const entityManager = {
|
||||||
|
async query(sql: string) {
|
||||||
|
const table = sql.includes("cd_product") ? "cd_product" : "cd_user_suite";
|
||||||
|
return rows[table];
|
||||||
|
},
|
||||||
|
async update(tableName: string, where: any, value: any) {
|
||||||
|
updates.push({ tableName, where, value });
|
||||||
|
const row = rows[tableName].find((item: any) => item.id === where.id);
|
||||||
|
Object.assign(row, value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const fix = new SuiteContentWildcardDomainCountFix();
|
||||||
|
fix.dataSourceManager = {
|
||||||
|
getDataSource() {
|
||||||
|
return {
|
||||||
|
manager: entityManager,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.equal(updates.length, 2);
|
||||||
|
assert.equal(updates[0].tableName, "cd_product");
|
||||||
|
assert.equal(JSON.parse(updates[0].value.content).maxWildcardDomainCount, 1);
|
||||||
|
assert.equal(updates[1].tableName, "cd_user_suite");
|
||||||
|
assert.equal(JSON.parse(updates[1].value.content).maxWildcardDomainCount, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { logger } from "@certd/basic";
|
||||||
|
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
|
||||||
|
|
||||||
|
export function fixSuiteContentWildcardDomainCount(contentValue?: string) {
|
||||||
|
if (!contentValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const content = JSON.parse(contentValue);
|
||||||
|
if (content.maxWildcardDomainCount != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
content.maxWildcardDomainCount = content.maxDomainCount == null || content.maxDomainCount === -1 ? -1 : content.maxDomainCount;
|
||||||
|
return JSON.stringify(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class SuiteContentWildcardDomainCountFix {
|
||||||
|
@Inject()
|
||||||
|
dataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.dataSourceManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const entityManager = this.dataSourceManager.getDataSource("default").manager;
|
||||||
|
let fixedCount = 0;
|
||||||
|
fixedCount += await this.fixSuiteContentWildcardDomainCountByTable(entityManager, "cd_product");
|
||||||
|
fixedCount += await this.fixSuiteContentWildcardDomainCountByTable(entityManager, "cd_user_suite");
|
||||||
|
if (fixedCount > 0) {
|
||||||
|
logger.info(`已修复套餐最大泛域名数量历史数据,数量=${fixedCount}`);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error("修复套餐最大泛域名数量历史数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fixSuiteContentWildcardDomainCountByTable(entityManager: any, tableName: string) {
|
||||||
|
const list = await entityManager.query(`select id, content from ${tableName}`);
|
||||||
|
let fixedCount = 0;
|
||||||
|
for (const item of list) {
|
||||||
|
const content = fixSuiteContentWildcardDomainCount(item.content);
|
||||||
|
if (!content) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await entityManager.update(tableName, { id: item.id }, { content });
|
||||||
|
fixedCount++;
|
||||||
|
}
|
||||||
|
return fixedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import { isComm, isPlus } from '@certd/plus-core';
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import { SendMailOptions } from 'nodemailer';
|
import { SendMailOptions } from 'nodemailer';
|
||||||
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
||||||
import { AddonService, PlusService, SysEmailConf, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
import { AddonService, PlusService, SysEmailConf, SysInstallInfo, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
||||||
import { getEmailSettings } from '../../sys/settings/fix.js';
|
import { getEmailSettings } from '../../sys/settings/fix.js';
|
||||||
import { UserEmailSetting } from "../../mine/service/models.js";
|
import { UserEmailSetting } from "../../mine/service/models.js";
|
||||||
import { AddonGetterService } from '../../pipeline/service/addon-getter-service.js';
|
import { AddonGetterService } from '../../pipeline/service/addon-getter-service.js';
|
||||||
@@ -131,12 +131,21 @@ export class EmailService implements IEmailService {
|
|||||||
data: {
|
data: {
|
||||||
title: '测试邮件,from certd',
|
title: '测试邮件,from certd',
|
||||||
content: '测试邮件,from certd',
|
content: '测试邮件,from certd',
|
||||||
url: "https://certd.handfree.work",
|
url: await this.getTestEmailUrl(),
|
||||||
},
|
},
|
||||||
receivers: [receiver],
|
receivers: [receiver],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getTestEmailUrl() {
|
||||||
|
const defaultUrl = "https://certd.docmirror.cn";
|
||||||
|
if (!isComm()) {
|
||||||
|
return defaultUrl;
|
||||||
|
}
|
||||||
|
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||||
|
return installInfo?.bindUrl || installInfo?.bindUrl2 || defaultUrl;
|
||||||
|
}
|
||||||
|
|
||||||
async list(userId: any) {
|
async list(userId: any) {
|
||||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,null, UserEmailSetting)
|
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,null, UserEmailSetting)
|
||||||
return userEmailSetting.list;
|
return userEmailSetting.list;
|
||||||
@@ -157,7 +166,6 @@ export class EmailService implements IEmailService {
|
|||||||
await this.settingsService.saveSetting(userId, null, userEmailSetting)
|
await this.settingsService.saveSetting(userId, null, userEmailSetting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async sendByTemplate(req: EmailSendByTemplateReq) {
|
async sendByTemplate(req: EmailSendByTemplateReq) {
|
||||||
let content = null
|
let content = null
|
||||||
const emailConf = await this.sysSettingsService.getSetting<SysEmailConf>(SysEmailConf);
|
const emailConf = await this.sysSettingsService.getSetting<SysEmailConf>(SysEmailConf);
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export class CertInfoEntity {
|
|||||||
@Column({ name: 'domain_count', comment: '域名数量' })
|
@Column({ name: 'domain_count', comment: '域名数量' })
|
||||||
domainCount: number;
|
domainCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'wildcard_domain_count', comment: '泛域名数量', default: 0 })
|
||||||
|
wildcardDomainCount: number;
|
||||||
|
|
||||||
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
|
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
|
||||||
pipelineId: number;
|
pipelineId: number;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { CertInfoService } from "./cert-info-service.js";
|
||||||
|
|
||||||
|
describe("CertInfoService", () => {
|
||||||
|
it("counts wildcard domains by normalized prefix", () => {
|
||||||
|
const service = new CertInfoService();
|
||||||
|
|
||||||
|
assert.equal(service.countWildcardDomains(["*.a.com", "a.com", " *.B.com "]), 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("saves wildcard domain count when updating pipeline domains", async () => {
|
||||||
|
const service = new CertInfoService();
|
||||||
|
let saved: any;
|
||||||
|
service.repository = {
|
||||||
|
async findOne() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
service.addOrUpdate = async (bean: any) => {
|
||||||
|
saved = bean;
|
||||||
|
return bean;
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.updateDomains(1, 2, null, ["*.a.com", "a.com", "*.b.com"], "pipeline");
|
||||||
|
|
||||||
|
assert.equal(saved.domainCount, 3);
|
||||||
|
assert.equal(saved.wildcardDomainCount, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -40,6 +40,22 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserWildcardDomainCount(userId: number) {
|
||||||
|
if (userId == null) {
|
||||||
|
throw new Error('userId is required');
|
||||||
|
}
|
||||||
|
return await this.repository.sum('wildcardDomainCount', {
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
countWildcardDomains(domains?: string[]) {
|
||||||
|
if (!domains) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return domains.filter(domain => domain?.trim().toLowerCase().startsWith("*.")).length;
|
||||||
|
}
|
||||||
|
|
||||||
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[],fromType?:string) {
|
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[],fromType?:string) {
|
||||||
const found = await this.repository.findOne({
|
const found = await this.repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
@@ -67,10 +83,12 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
|||||||
bean.domain = '';
|
bean.domain = '';
|
||||||
bean.domains = '';
|
bean.domains = '';
|
||||||
bean.domainCount = 0;
|
bean.domainCount = 0;
|
||||||
|
bean.wildcardDomainCount = 0;
|
||||||
} else {
|
} else {
|
||||||
bean.domain = domains[0];
|
bean.domain = domains[0];
|
||||||
bean.domains = domains.join(',');
|
bean.domains = domains.join(',');
|
||||||
bean.domainCount = domains.length;
|
bean.domainCount = domains.length;
|
||||||
|
bean.wildcardDomainCount = this.countWildcardDomains(domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.addOrUpdate(bean);
|
await this.addOrUpdate(bean);
|
||||||
@@ -171,6 +189,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
|||||||
bean.domains = domains.join(',');
|
bean.domains = domains.join(',');
|
||||||
bean.domain = domains[0];
|
bean.domain = domains[0];
|
||||||
bean.domainCount = domains.length;
|
bean.domainCount = domains.length;
|
||||||
|
bean.wildcardDomainCount = this.countWildcardDomains(domains);
|
||||||
bean.effectiveTime = certReader.effective;
|
bean.effectiveTime = certReader.effective;
|
||||||
bean.expiresTime = certReader.expires;
|
bean.expiresTime = certReader.expires;
|
||||||
bean.certProvider = certReader.detail.issuer.commonName;
|
bean.certProvider = certReader.detail.issuer.commonName;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import { executorQueue } from "@certd/lib-server";
|
|||||||
import parser from "cron-parser";
|
import parser from "cron-parser";
|
||||||
import { ProjectService } from "../../sys/enterprise/service/project-service.js";
|
import { ProjectService } from "../../sys/enterprise/service/project-service.js";
|
||||||
import { CertApplyStepInputPatch, updateCertApplyStepInputs } from "./pipeline-batch-update.js";
|
import { CertApplyStepInputPatch, updateCertApplyStepInputs } from "./pipeline-batch-update.js";
|
||||||
|
import { calcNextSuiteCountUsed } from "./pipeline-suite-limit.js";
|
||||||
const runningTasks: Map<string | number, Executor> = new Map();
|
const runningTasks: Map<string | number, Executor> = new Map();
|
||||||
|
|
||||||
|
|
||||||
@@ -76,7 +77,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
historyService: HistoryService;
|
historyService: HistoryService;
|
||||||
@Inject()
|
@Inject()
|
||||||
historyLogService: HistoryLogService;
|
historyLogService: HistoryLogService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
pluginConfigGetter: PluginConfigGetter;
|
pluginConfigGetter: PluginConfigGetter;
|
||||||
|
|
||||||
@@ -287,10 +287,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUpdate) {
|
await this.checkMaxPipelineCount(bean, pipeline, domains, old);
|
||||||
//如果是添加,校验数量
|
|
||||||
await this.checkMaxPipelineCount(bean, pipeline, domains);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bean.status) {
|
if (!bean.status) {
|
||||||
bean.status = ResultType.none;
|
bean.status = ResultType.none;
|
||||||
@@ -345,7 +342,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
return bean
|
return bean
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[]) {
|
private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[], old?: PipelineEntity) {
|
||||||
// if (!isPlus()) {
|
// if (!isPlus()) {
|
||||||
// const count = await this.repository.count();
|
// const count = await this.repository.count();
|
||||||
// if (count >= freeCount) {
|
// if (count >= freeCount) {
|
||||||
@@ -363,15 +360,31 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
const suiteSetting = await this.userSuiteService.getSuiteSetting();
|
const suiteSetting = await this.userSuiteService.getSuiteSetting();
|
||||||
if (suiteSetting.enabled) {
|
if (suiteSetting.enabled) {
|
||||||
const userSuite = await this.userSuiteService.getMySuiteDetail(bean.userId);
|
const userSuite = await this.userSuiteService.getMySuiteDetail(bean.userId);
|
||||||
if (userSuite?.pipelineCount.max != -1 && userSuite?.pipelineCount.used + 1 > userSuite?.pipelineCount.max) {
|
if (!old && userSuite?.pipelineCount.max != -1 && userSuite?.pipelineCount.used + 1 > userSuite?.pipelineCount.max) {
|
||||||
throw new NeedSuiteException(`对不起,您最多只能创建${userSuite?.pipelineCount.max}条流水线,请购买或升级套餐`);
|
throw new NeedSuiteException(`对不起,您最多只能创建${userSuite?.pipelineCount.max}条流水线,请购买或升级套餐`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userSuite.domainCount.max != -1 && userSuite.domainCount.used + domains.length > userSuite.domainCount.max) {
|
let oldDomainCount = 0;
|
||||||
|
let oldWildcardDomainCount = 0;
|
||||||
|
if (old?.id) {
|
||||||
|
const oldCertInfo = await this.certInfoService.getByPipelineId(old.id);
|
||||||
|
oldDomainCount = oldCertInfo?.domainCount ?? 0;
|
||||||
|
oldWildcardDomainCount = oldCertInfo?.wildcardDomainCount ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextDomainCountUsed = calcNextSuiteCountUsed(userSuite.domainCount.used, oldDomainCount, domains.length);
|
||||||
|
if (userSuite.domainCount.max != -1 && nextDomainCountUsed > userSuite.domainCount.max) {
|
||||||
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
|
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const suiteWildcardDomainCount = userSuite.wildcardDomainCount;
|
||||||
|
const wildcardDomainCount = this.certInfoService.countWildcardDomains(domains);
|
||||||
|
const nextWildcardDomainCountUsed = calcNextSuiteCountUsed(suiteWildcardDomainCount.used, oldWildcardDomainCount, wildcardDomainCount);
|
||||||
|
if (suiteWildcardDomainCount.max != -1 && nextWildcardDomainCountUsed > suiteWildcardDomainCount.max) {
|
||||||
|
throw new NeedSuiteException(`对不起,您最多只能添加${suiteWildcardDomainCount.max}个泛域名,请购买或升级套餐`);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
} else if (!old) {
|
||||||
//非商业版校验用户最大流水线数量
|
//非商业版校验用户最大流水线数量
|
||||||
const userId = bean.userId;
|
const userId = bean.userId;
|
||||||
const userIsAdmin = await this.userService.isAdmin(userId);
|
const userIsAdmin = await this.userService.isAdmin(userId);
|
||||||
@@ -1332,7 +1345,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createAutoPipeline(req: { domains: string[]; email: string; userId: number, projectId?: number, from: string }) {
|
async createAutoPipeline(req: { domains: string[]; email: string; userId: number, projectId?: number, from: string }) {
|
||||||
|
|
||||||
const randomHour = Math.floor(Math.random() * 6);
|
const randomHour = Math.floor(Math.random() * 6);
|
||||||
const randomMin = Math.floor(Math.random() * 60);
|
const randomMin = Math.floor(Math.random() * 60);
|
||||||
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { calcNextSuiteCountUsed } from "./pipeline-suite-limit.js";
|
||||||
|
|
||||||
|
describe("Pipeline suite limits", () => {
|
||||||
|
it("calculates next usage by subtracting current pipeline usage on update", () => {
|
||||||
|
assert.equal(calcNextSuiteCountUsed(2, 1, 1), 2);
|
||||||
|
assert.equal(calcNextSuiteCountUsed(2, 1, 2), 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function calcNextSuiteCountUsed(used: number, oldCount: number, newCount: number) {
|
||||||
|
return (used ?? 0) - (oldCount ?? 0) + (newCount ?? 0);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user