Compare commits

...

59 Commits

Author SHA1 Message Date
xiaojunnuo 85c633fddf Merge branch 'v2-dev' into v2-invite 2026-05-19 22:59:18 +08:00
xiaojunnuo cb08e061d2 perf(controller): 更换版本获取源并添加版本标准化处理
将npm镜像源的版本查询替换为AtomGit仓库的最新release接口,新增normalizeReleaseVersion方法标准化版本号格式,移除v前缀
2026-05-19 22:58:03 +08:00
xiaojunnuo 9bf7ca400f build: release 2026-05-19 00:58:34 +08:00
xiaojunnuo 9f75e30af3 Merge branch 'v2' of https://github.com/certd/certd into v2 2026-05-19 00:55:41 +08:00
xiaojunnuo 3f0243ba9a build: publish 2026-05-19 00:38:56 +08:00
xiaojunnuo 1a9b367c9d build: trigger build image 2026-05-19 00:38:44 +08:00
xiaojunnuo 73996f055b v1.40.1 2026-05-19 00:37:16 +08:00
xiaojunnuo 57ea2f317a build: prepare to build 2026-05-19 00:34:11 +08:00
xiaojunnuo c66c05b442 chore: 1 2026-05-19 00:32:24 +08:00
xiaojunnuo 6450d2bd56 chore: 商业版也支持企业模式 2026-05-19 00:23:32 +08:00
xiaojunnuo b5368d120d chore: tsconfig.build 支持sourcemap控制 2026-05-19 00:22:48 +08:00
xiaojunnuo f9a310b6c3 feat: 商业版支持邀请返佣功能 2026-05-18 13:25:35 +08:00
xiaojunnuo 1bdcfe646f chore: tsconfig.build 支持sourcemap控制 2026-05-18 13:25:01 +08:00
Robert Silén a88d14a3bd dos: Document MariaDB support in README (#737)
Mention MariaDB in the database feature list. Install docs and TypeORM
already support type=mariadb; the app uses the mysql2 driver.
2026-05-17 00:40:41 +08:00
xiaojunnuo bdb3d09c09 perf(suite): 商业版用户已购套餐支持修改 2026-05-17 00:29:36 +08:00
xiaojunnuo 667e7b185b fix(email-service): 优化商业版测试邮件内容中带的url链接 2026-05-16 02:54:15 +08:00
xiaojunnuo 8483ee0d41 refactor(util.cache): 优化定时清理过期缓存的定时器行为
新增定时器变量保存intervalId,并调用unref方法避免阻塞进程退出
2026-05-16 02:46:28 +08:00
xiaojunnuo c3baaf3ac7 chore: auto fix 存储标记,确保fix只运行一次 2026-05-16 02:23:21 +08:00
xiaojunnuo c63745d1ba perf: 商业版支持限制泛域名数量 2026-05-16 02:18:06 +08:00
xiaojunnuo 59b9ffadd0 fix: 固化华为云sdk版本,避免华为云调用报错 2026-05-15 23:18:51 +08:00
xiaojunnuo d131ea3790 build: release 2026-05-15 01:14:12 +08:00
xiaojunnuo b849d34be5 build: publish 2026-05-15 00:50:09 +08:00
xiaojunnuo 58fc9a551c build: trigger build image 2026-05-15 00:49:57 +08:00
xiaojunnuo 5801f34b3a v1.40.0 2026-05-15 00:48:37 +08:00
xiaojunnuo 17cf16ca92 build: prepare to build 2026-05-15 00:44:39 +08:00
xiaojunnuo 7015b1b232 perf: 头像增加缓存时间 2026-05-15 00:39:35 +08:00
xiaojunnuo 3b72ca09c6 chore: 支持设置初始化密码 2026-05-15 00:26:24 +08:00
xiaojunnuo a815d0245b perf: 第三方登录自动注册的用户支持设置初始化密码 2026-05-15 00:25:28 +08:00
xiaojunnuo 229f22d5a9 docs: 自动升级docker-compose 2026-05-15 00:12:44 +08:00
xiaojunnuo 22f5cfcfd8 fix: 修复clogin登录丢失state问题 2026-05-14 23:44:04 +08:00
xiaojunnuo 90ba55c043 chore: 1 2026-05-14 19:05:49 +08:00
xiaojunnuo 9f878a353c fix: 修复clogin多选类型登录失败的bug 2026-05-14 19:04:20 +08:00
xiaojunnuo af7297d671 chore: 1 2026-05-14 18:47:28 +08:00
xiaojunnuo 2f172b56e9 fix: 修复群晖授权没有显示设备id输入框的bug 2026-05-14 18:42:35 +08:00
xiaojunnuo 9076c8b20e Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-05-14 18:31:34 +08:00
xiaojunnuo 639756dfcd chore: sdk 2026-05-14 18:31:25 +08:00
xiaojunnuo 7aa0c7e491 feat: 彩虹登录支持选择多种登录方式 2026-05-14 01:39:22 +08:00
xiaojunnuo 45dedf5bc7 fix: 修复第三方登录丢失state时无法在用户信息页面绑定第三方账号的bug 2026-05-13 23:47:31 +08:00
xiaojunnuo 4681ec9008 fix: 修复自动注册后没有跳转到控制台的bug 2026-05-13 22:47:46 +08:00
xiaojunnuo b91826c6e6 chore: 1 2026-05-13 17:29:49 +08:00
xiaojunnuo 686856d0ae build: release 2026-05-13 17:29:19 +08:00
xiaojunnuo 9b09d2578d chore: 1 2026-05-13 17:14:14 +08:00
xiaojunnuo f8f51adf88 build: publish 2026-05-13 14:22:46 +08:00
xiaojunnuo f8ce639717 build: trigger build image 2026-05-13 14:22:33 +08:00
xiaojunnuo 1c6dc169ac v1.39.16 2026-05-13 14:21:21 +08:00
xiaojunnuo 3e5366c74e build: prepare to build 2026-05-13 14:13:01 +08:00
xiaojunnuo b49ddbfef9 v1.39.15 2026-05-13 13:49:09 +08:00
xiaojunnuo b92fd73f53 build: prepare to build 2026-05-13 13:33:12 +08:00
xiaojunnuo 41b8f51a6a chore: 1 2026-05-13 13:32:01 +08:00
xiaojunnuo aad9045de5 perf(network): 新增全局公共http请求 headers设置
1. 新增公共请求头配置项,支持在系统设置中配置全局请求头
2. 实现请求头解析工具方法,支持多行KEY=VALUE格式配置
3. 在请求发起时自动附加全局公共请求头,且不会覆盖请求中已存在的同名Header
4. 添加多语言配置与前端表单组件,完善配置界面
5. 新增单元测试验证全局请求头合并逻辑
2026-05-13 12:09:01 +08:00
xiaojunnuo fdd5848df4 perf: icon选择器增加一套logo集 2026-05-13 11:20:55 +08:00
xiaojunnuo 118c15d046 perf(monitor/site): 新增站点监控页面禁用启用、检查状态两个筛选条件 2026-05-13 11:14:00 +08:00
xiaojunnuo bae4f8e320 fix: 修复第三方登录彩虹登录不上的bug 2026-05-13 11:03:10 +08:00
xiaojunnuo e0189a566e docs(donate): update privilege comparison table to add commercial edition details 2026-05-12 15:08:46 +08:00
xiaojunnuo 1cd8d73cdb Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-05-12 10:51:41 +08:00
xiaojunnuo d6e9e5987b perf: 优化申请时报错日志增加对应域名打印 2026-05-12 10:51:34 +08:00
xiaojunnuo 8c5aa37745 build: release 2026-05-12 00:51:59 +08:00
xiaojunnuo a18a871ac3 build: publish 2026-05-12 00:22:37 +08:00
xiaojunnuo 90cbff9cf9 build: trigger build image 2026-05-12 00:22:25 +08:00
190 changed files with 4625 additions and 640 deletions
+55
View File
@@ -0,0 +1,55 @@
---
name: fast-crud-page-dev
description: 用于开发或重构 Certd 前端列表管理、后台管理、记录查询、CRUD 表格页面,优先使用 Fast Crud(@fast-crud/fast-crud、fs-crud、useFs、createCrudOptions)实现。当用户要求列表页、管理页、审核页、记录页或表格 CRUD 页面时触发。
version: 1.0.0
---
# Fast Crud 页面开发技能
## 角色定义
你是一名 Certd 前端列表管理页面开发专家,熟悉 Vue 3、Ant Design Vue、Fast Crud 和本仓库现有页面拆分方式。你的目标是让管理页面保持统一的表格、搜索、分页、操作列和弹窗体验。
## 核心规则
- 列表管理、后台管理、记录查询、审核记录、CRUD 表格类页面,默认优先使用 Fast Crud 实现。
- 只有轻量只读展示、强交互自定义界面、复杂可视化或已有页面模式明确不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明原因。
- 设置表单、概览卡片、向导流程等非列表主体可以保留自定义 Vue;如果同一功能同时包含设置和列表,优先拆成独立页面,或把设置放入对话框。
## 推荐文件拆分
- `api.ts`:封装接口请求,保持页面和 CRUD 配置里不直接散落 URL。
- `crud.tsx` / `crud-*.tsx`:导出 `createCrudOptions`,集中定义请求映射、搜索项、列、表单、操作列、工具栏和字典。
- `index.vue`:承载 `fs-page``fs-crud`、页面头部、弹窗和生命周期,使用 `useFs({ createCrudOptions, context })` 创建绑定。
## 实现流程
1. 先在 `packages/ui/certd-client/src/views` 下找 1-2 个相近 Fast Crud 页面,沿用它们的导入、布局、命名和权限写法。
2.`index.vue` 中使用 `fs-crud ref="crudRef" v-bind="crudBinding"`,并在 `onMounted` / `onActivated` 时调用 `crudExpose.doRefresh()`
3.`crud.tsx` 中配置 `request.pageRequest``columns``search``form``rowHandle``actionbar``toolbar` 等,接口分页参数和返回值按现有页面适配。
4. 操作按钮优先放在 Fast Crud 的 `rowHandle.buttons``actionbar.buttons` 中;审核、保存设置、批量操作等复杂交互可通过 `context` 调用 `index.vue` 中的方法。
5. 金额、状态、时间、枚举等字段优先复用项目已有组件、字典和格式化工具;避免在模板里重复堆格式化逻辑。
6. 表格查询条件使用 Fast Crud 的 `search` 配置;新增/编辑表单使用 Fast Crud 的 `form` 配置,复杂设置项可以用 Ant Design Vue 对话框承载。
7. 删除、审核通过、拒绝等危险操作必须保留确认弹窗和错误提示,成功后刷新当前 CRUD 列表。
8. 对话框里只做纯确认时可以使用 `Modal.confirm`;只要需要字段输入、表单校验或提交字段,统一使用 `useFormDialog` / `openFormDialog`,不要在 `Modal.confirm``content` 里手写输入框。
## 布局高度
- Fast Crud 表格依赖外部容器高度计算。虽然表格本身有默认约 200px 高度,但页面内嵌 `fs-crud` 时,为了获得稳定可用的列表区域,必须让外层容器提供明确高度或剩余高度。
- 独立列表页通常可直接让 `fs-page` / 页面内容区撑满;如果表格嵌在 tabs、详情页、上下分区或弹窗里,要从页面根容器到 `fs-crud` 建立完整的 flex 高度链路:父容器 `display: flex; flex-direction: column; min-height: 0`,中间内容区和 tab pane 使用 `flex: 1; min-height: 0``fs-crud` 本身也使用 `flex: 1; min-height: 0`
- 有固定操作栏、统计区、说明区时,这些区域应 `flex: none`,把剩余空间交给表格区域。
- 修改嵌入式 Fast Crud 页面后,要检查空数据、少量数据和多页数据时表格高度、分页器和空状态是否仍在预期区域内。
## 代码习惯
- 页面命名、API 命名、权限标识和路由结构要贴近同目录已有页面。
- CRUD 配置中不要写大段业务流程;复杂逻辑放回 `index.vue` 方法或 `api.ts`
- 能用 `dict``compute``valueBuilder``valueResolve``component` 配置表达的表格/表单行为,不要改成手写模板。
- 保持列表页密度和操作入口克制,不要做营销式布局、嵌套卡片或大块说明文字。
- 如果页面有“设置 + 列表”,管理端优先拆成两个路由页面;用户端提现设置这类低频配置优先使用对话框保存。
## 验证方式
- 前端改动后,只对本次改动的 Vue / TS / TSX / locale 文件运行项目现有 Prettier / ESLint。
- 不运行 `vue-tsc` / `pnpm tsc`,因为当前依赖组合下 `vue-tsc` 已知会抛内部错误。
- 若只是新增或修改本 skill / 文档,不需要运行前端格式化和测试。
+15 -3
View File
@@ -35,6 +35,8 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
- `packages/ui/certd-server/`:后端服务
- `packages/ui/certd-client/`:前端 Web 管理台
`packages/pro/` 是独立 Git 工作区,使用 `packages/pro/.git` 管理。根仓库的 `git status` / `git diff` 默认看不到这里的实际改动;修改商业版代码后,要在 `packages/pro` 目录内单独执行 `git status` / `git diff` 检查。
## 后端
主要后端包:`packages/ui/certd-server`
@@ -105,9 +107,17 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
- 前端 `pnpm dev`:启动 Vite 开发服务
- 前端 `pnpm build`:生产构建
- 前端 `pnpm tsc`类型检查
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
前端列表管理页面约定:
- 列表管理、后台管理、记录查询、CRUD 表格类页面,默认优先使用 Fast Crud(`@fast-crud/fast-crud``fs-crud``useFs``createCrudOptions`)实现。
- 只有轻量只读展示、强交互自定义界面或已有页面模式明确不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明原因。
- 开发或重构这类页面前,先读取 `.trae/skills/fast-crud-page-dev/SKILL.md`,按仓库内 Fast Crud 页面拆分与验证方式实现。
- 前端对话框里只做纯确认时可以使用 `Modal.confirm`;只要对话框里有字段输入、表单校验或提交字段,统一使用 `useFormDialog` / `openFormDialog`,不要在 `Modal.confirm``content` 里手写输入框。
- 页面内嵌 Fast Crud 表格时,要显式给外层容器稳定高度或 `flex: 1; min-height: 0` 的撑满链路;Fast Crud 依赖外部元素高度,不能只依赖表格默认高度。
## 流水线与插件模型
项目最关键的架构概念是证书流水线。
@@ -168,6 +178,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
- `packages/ui/certd-server/data/``logs/`、生成的 metadata/dist 等通常视为运行时或构建产物,除非任务明确要求处理它们。
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
- 代码可读性优先于短写法。遇到包含业务分支的复杂三元表达式、内联对象、链式调用或条件组合时,优先拆成命名清晰的中间变量、独立分支或小函数,让读代码的人能一眼看出业务意图;不要为了少写几行把逻辑压成难读的一坨。
## 插件开发技能
@@ -182,6 +193,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
- `access-plugin-dev`:开发 Access 授权插件
- `dns-provider-dev`:开发 DNS Provider 插件
- `fast-crud-page-dev`:开发或重构前端 Fast Crud 列表管理页面
- `task-plugin-dev`:开发 Task 部署任务插件
- `plugin-converter`:将插件转换为 YAML 配置
@@ -216,5 +228,5 @@ Get-ChildItem packages\ui\certd-client\src\views\certd
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
- 单元测试需要 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` 暂时不跑单元测试。
- 前端 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。
- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。
- 前端 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。
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
+48
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
### Bug Fixes
* 固化华为云sdk版本,避免华为云调用报错 ([59b9ffa](https://github.com/certd/certd/commit/59b9ffadd05faf3982151c48f8d83cbd97419865))
* **email-service:** 优化商业版测试邮件内容中带的url链接 ([667e7b1](https://github.com/certd/certd/commit/667e7b185bf26558972be01720872f48352b5876))
### Performance Improvements
* 商业版支持限制泛域名数量 ([c63745d](https://github.com/certd/certd/commit/c63745d1ba30904428ba6b13ab0785298baa5cae))
* **suite:** 商业版用户已购套餐支持修改 ([bdb3d09](https://github.com/certd/certd/commit/bdb3d09c09fc73e7e5e3401f6ef5588bf8ad5088))
# [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
+18 -13
View File
@@ -34,10 +34,11 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永
* **多种通知方式**: 邮件通知、webhook通知、企微、钉钉、飞书、anpush等多种通知方式
* **私有化部署**: 数据保存本地,安装简单快捷,镜像由Github Actions构建,过程公开透明
* **多重安全保障**: 授权加密,站点隐藏,2FA,密码防爆破等多重安全保障
* **多数据库支持**:支持SQLite、PostgreSQL、MySQL
* **多数据库支持**:支持SQLite、PostgreSQL、MySQL、MariaDB
* **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
* **站点证书监控**: 定时监控网站证书的过期时间
* **多用户管理**: 用户可以管理自己的证书流水线
* **项目管理**: 企业级项目管理模式
* **多语言支持** 中英双语切换
* **无忧升级** 版本向下兼容
@@ -179,19 +180,23 @@ https://certd.handfree.work/
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
专业版特权对比
| 功能 | 免费版 | 专业版 |
|---------|---------------------------------------|--------------------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 证书域名数量 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
| 站点监控 | 限制1条 | 无限制 |
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
专业版、商业版特权对比
| 功能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 免费版 | 专业版 | 商业版 |
|---------|---------------------------------------|--------------------------------|---------------------------------|
| 证书申请 | 无限制 | 无限制 | 无限制 |
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
| 站点监控 | 限制1条 | 无限制 | 无限制 |
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
## 九、贡献代码
+1 -1
View File
@@ -31,7 +31,7 @@ This project not only supports automated certificate application but also automa
* Multiple notification methods, including email, webhook, WeChat Work, DingTalk, Lark, and anpush.
* On-premises deployment, local data storage, simple and quick installation. Images are built by Github Actions, with a transparent process.
* Multiple security measures, including authorization encryption, site hiding, 2FA, and password brute-force protection.
* Supports multiple databases such as SQLite, PostgreSQL, and MySQL.
* Supports multiple databases such as SQLite, PostgreSQL, MySQL, and MariaDB.
* Open API support.
* Site certificate monitoring.
* Multi-user management.
+95
View File
@@ -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_ + 配置项, 点号用_代替
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为truedocker 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
+55
View File
@@ -3,6 +3,61 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
### Bug Fixes
* 固化华为云sdk版本,避免华为云调用报错 ([59b9ffa](https://github.com/certd/certd/commit/59b9ffadd05faf3982151c48f8d83cbd97419865))
* **email-service:** 优化商业版测试邮件内容中带的url链接 ([667e7b1](https://github.com/certd/certd/commit/667e7b185bf26558972be01720872f48352b5876))
### Performance Improvements
* 商业版支持限制泛域名数量 ([c63745d](https://github.com/certd/certd/commit/c63745d1ba30904428ba6b13ab0785298baa5cae))
* **suite:** 商业版用户已购套餐支持修改 ([bdb3d09](https://github.com/certd/certd/commit/bdb3d09c09fc73e7e5e3401f6ef5588bf8ad5088))
# [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)
### Bug Fixes
+16 -10
View File
@@ -16,16 +16,22 @@
****------------------****
## 专业版特权对比
| 功能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 免费版 | 专业版 |
|---------|---------------------------------------|--------------------------------|
| 证书申请 | 无限制 | 无限制 |
| 证书域名数量 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
| 站点监控 | 限制1条 | 无限制 |
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
| 功能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 免费版 | 专业版 | 商业版 |
|---------|---------------------------------------|--------------------------------|---------------------------------|
| 证书申请 | 无限制 | 无限制 | 无限制 |
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
| 站点监控 | 限制1条 | 无限制 | 无限制 |
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
## 专业版激活方式
+60
View File
@@ -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版本插件
![](./images/github-release.png)
按如下图填写配置
![](./images/github-release-2.png)
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
```
-48
View File
@@ -22,51 +22,3 @@
可以查看最新版本号,以及所有版本的更新日志
[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版本插件
![](./images/github-release.png)
按如下图填写配置
![](./images/github-release-2.png)
检测到新版本后执行宿主机升级命令:
```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
View File
@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.39.14"
"version": "1.40.1"
}
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/publishlab/node-acme-client/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/acme-client
# [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)
**Note:** Version bump only for package @certd/acme-client
+4 -4
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.39.14",
"version": "1.40.1",
"type": "module",
"module": "./dist/index.js",
"main": "./dist/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.39.14",
"@certd/basic": "^1.40.1",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.9.0",
@@ -50,7 +50,7 @@
},
"scripts": {
"before-build": "node -e \"const fs=require('fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true});\"",
"build": "npm run before-build && tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"build-docs": "jsdoc2md dist/client.js > docs/client.md && jsdoc2md dist/crypto/index.js > docs/crypto.md && jsdoc2md dist/crypto/forge.js > docs/forge.md",
"lint": "eslint \"src/**/*.ts\" \"types/**/*.ts\"",
"lint-types": "tsd --files \"types/index.test-d.ts\"",
@@ -76,5 +76,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
+2
View File
@@ -167,6 +167,7 @@ export default async (client, userOpts) => {
await client.completeChallenge(challenge);
}catch (e) {
await deactivateAuth(e);
e.message = `[${d}] ${e.message || "completeChallenge error"}`;
throw e;
}
challengeCompleted = true;
@@ -178,6 +179,7 @@ export default async (client, userOpts) => {
} catch (e) {
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message || e}`);
await deactivateAuth(e);
e.message = `[${d}] ${e.message || "challengeCreateFn error"}`;
throw e;
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
"moduleResolution": "node",
"esModuleInterop": true,
"inlineSourceMap": false,
"sourceMap": false,
"sourceMap": true,
"noImplicitThis": false,
"noUnusedLocals": false,
"stripInternal": true,
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/basic
# [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)
**Note:** Version bump only for package @certd/basic
+1 -1
View File
@@ -1 +1 @@
00:06
00:34
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.39.14",
"version": "1.40.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -9,7 +9,7 @@
"scripts": {
"dev": "vite",
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build && tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"preview": "vite preview",
"test": "mocha --loader=ts-node/esm",
@@ -52,5 +52,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
+2 -1
View File
@@ -11,9 +11,10 @@ export class LocalCache<V = any> {
cache: Map<string, { value: V; expiresAt: number }>;
constructor(opts: { clearInterval?: number } = {}) {
this.cache = new Map();
setInterval(() => {
const intervalId = setInterval(() => {
this.clearExpires();
}, opts.clearInterval ?? 5 * 60 * 1000);
intervalId.unref?.();
}
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;
let defaultAgents = createAgent();
let defaultHeaders: Record<string, string> = {};
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
logger.info("setGlobalProxy:", opts);
@@ -92,6 +93,15 @@ export function getGlobalAgents() {
return defaultAgents;
}
export function setGlobalHeaders(headers: Record<string, string> = {}) {
logger.info("setGlobalHeaders:", Object.keys(headers));
defaultHeaders = { ...headers };
}
export function getGlobalHeaders() {
return defaultHeaders;
}
/**
* @description
*/
@@ -148,6 +158,12 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
config.httpsAgent = agents.httpsAgent;
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({
// rejectUnauthorized: false // 允许自签名证书
// });
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap": false,
"sourceMap": false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/pipeline
# [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)
**Note:** Version bump only for package @certd/pipeline
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.39.14",
"version": "1.40.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -9,7 +9,7 @@
"scripts": {
"dev": "vite",
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build && tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"build3": "rollup -c",
"preview": "vite preview",
@@ -19,8 +19,8 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.14",
"@certd/plus-core": "^1.39.14",
"@certd/basic": "^1.40.1",
"@certd/plus-core": "^1.40.1",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -49,5 +49,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap": false,
"sourceMap": false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"importHelpers": true,
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/lib-huawei
# [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)
**Note:** Version bump only for package @certd/lib-huawei
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.39.14",
"version": "1.40.1",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -27,5 +27,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -9,6 +9,7 @@ module.exports = {
output: {
file: "dist/bundle.js",
format: "cjs",
sourcemap: false,
},
plugins: [
// 解析第三方依赖
@@ -22,10 +23,13 @@ module.exports = {
// ],
}),
Typescript({
tsconfig: "./tsconfig.build.json",
target: "esnext",
rootDir: "src",
declaration: true,
declarationDir: "dist/d",
sourceMap: false,
inlineSourceMap: false,
exclude: ["./node_modules/**", "./src/**/*.vue", "./src/**/*.spec.ts"],
allowSyntheticDefaultImports: true,
}),
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
"composite": false,
"useDefineForClassFields": true,
"strict": false,
"sourceMap": false,
"sourceMap": true,
"inlineSourceMap": false,
"resolveJsonModule": true,
"isolatedModules": false,
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/lib-iframe
# [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)
**Note:** Version bump only for package @certd/lib-iframe
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.39.14",
"version": "1.40.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -9,7 +9,7 @@
"scripts": {
"dev": "vite",
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build && tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
@@ -34,5 +34,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -8,6 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
@@ -22,7 +23,6 @@
"composite": false,
"useDefineForClassFields": true,
"strict": false,
// "sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"lib": ["ESNext", "DOM"],
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/jdcloud
# [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)
**Note:** Version bump only for package @certd/jdcloud
+4 -3
View File
@@ -1,12 +1,13 @@
{
"name": "@certd/jdcloud",
"version": "1.39.14",
"version": "1.40.1",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
"scripts": {
"build": "rollup -c ",
"before-build": "node -e \"const fs=require('fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true});fs.rmSync('.rollup.cache',{recursive:true,force:true});\"",
"build": "npm run before-build && rollup -c ",
"dev-build": "npm run build",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish"
@@ -58,5 +59,5 @@
"fetch"
]
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -9,6 +9,7 @@ module.exports = {
output: {
file: "dist/bundle.js",
format: "cjs",
sourcemap: false,
},
plugins: [
// 解析第三方依赖
@@ -22,10 +23,13 @@ module.exports = {
// ],
}),
Typescript({
tsconfig: "./tsconfig.build.json",
target: "esnext",
rootDir: "src",
declaration: true,
declarationDir: "dist/d",
sourceMap: false,
inlineSourceMap: false,
exclude: ["./node_modules/**", "./src/**/*.vue", "./src/**/*.spec.ts"],
allowSyntheticDefaultImports: true,
}),
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
"composite": false,
"useDefineForClassFields": true,
"strict": false,
"sourceMap": false,
"sourceMap": true,
"inlineSourceMap": false,
"resolveJsonModule": true,
"isolatedModules": false,
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/lib-k8s
# [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)
**Note:** Version bump only for package @certd/lib-k8s
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.39.14",
"version": "1.40.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -9,7 +9,7 @@
"scripts": {
"dev": "vite",
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build && tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
@@ -19,7 +19,7 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.14",
"@certd/basic": "^1.40.1",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -36,5 +36,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -8,6 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
@@ -22,7 +23,6 @@
"composite": false,
"useDefineForClassFields": true,
"strict": false,
// "sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"lib": ["ESNext", "DOM"],
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/lib-server
# [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)
### Bug Fixes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.39.14",
"version": "1.40.1",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -9,7 +9,7 @@
"types": "./dist/index.d.ts",
"scripts": {
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build && tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"test": "midway-bin test --ts -V",
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
@@ -29,11 +29,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.39.14",
"@certd/basic": "^1.39.14",
"@certd/pipeline": "^1.39.14",
"@certd/plugin-lib": "^1.39.14",
"@certd/plus-core": "^1.39.14",
"@certd/acme-client": "^1.40.1",
"@certd/basic": "^1.40.1",
"@certd/pipeline": "^1.40.1",
"@certd/plugin-lib": "^1.40.1",
"@certd/plus-core": "^1.40.1",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",
@@ -69,5 +69,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -64,6 +64,7 @@ export class SysPublicSettings extends BaseSettings {
type: string;
title: string;
addonId: number;
icon?: string;
}> = {};
notice?: string;
@@ -80,6 +81,7 @@ export class SysPrivateSettings extends BaseSettings {
httpsProxy? = '';
httpProxy? = '';
commonHeaders?: string = '';
reverseProxies?: Record<string, string> = {};
@@ -251,6 +253,14 @@ export class SysSuiteSetting extends BaseSettings {
intro?: string;
}
export class SysAutoFixSetting extends BaseSettings {
static __title__ = '自动修复记录';
static __key__ = 'sys.auto.fix';
static __access__ = 'private';
fixed: Record<string, boolean> = {};
}
export type SiteHidden = {
enabled: boolean;
@@ -5,7 +5,7 @@ import { SysSettingsEntity } from '../entity/sys-settings.js';
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
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 * as dns from 'node:dns';
import { BaseService, setAdminMode } from '../../../basic/index.js';
@@ -167,6 +167,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
httpsProxy: privateSetting.httpsProxy,
};
setGlobalProxy(opts);
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
if (privateSetting.dnsResultOrder) {
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
@@ -185,12 +186,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
}
setEnvironmentVars(vars: string) {
const envVars = {}
if (typeof vars !== 'string') {
vars = ""
parseKeyValueText(text: string) {
const values = {};
if (typeof text !== 'string') {
text = "";
}
vars.split('\n').forEach(line => {
text.split('\n').forEach(line => {
line = line.trim();
if (!line || line.startsWith('#')) {
return
@@ -204,11 +205,18 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
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) {
envVars[key.trim()] = value.trim();
values[key] = value;
}
});
return values;
}
setEnvironmentVars(vars: string) {
const envVars = this.parseKeyValueText(vars);
//先删除旧环境变量
if (lastSaveEnvVars) {
for (const key in lastSaveEnvVars) {
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -9,7 +9,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap": false,
"sourceMap": false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/midway-flyway-js
# [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)
**Note:** Version bump only for package @certd/midway-flyway-js
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.39.14",
"version": "1.40.1",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -9,7 +9,7 @@
"types": "./dist/index.d.ts",
"scripts": {
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build && tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"test": "midway-bin test --ts -V",
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
@@ -49,5 +49,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -8,6 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
@@ -21,7 +22,6 @@
"composite": false,
"useDefineForClassFields": true,
"strict": false,
// "sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"lib": ["ESNext", "DOM"],
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/plugin-cert
# [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)
**Note:** Version bump only for package @certd/plugin-cert
+7 -7
View File
@@ -1,14 +1,14 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.39.14",
"version": "1.40.1",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"dev": "vite",
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "tsc --skipLibCheck",
"build": "tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
@@ -18,10 +18,10 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/acme-client": "^1.39.14",
"@certd/basic": "^1.39.14",
"@certd/pipeline": "^1.39.14",
"@certd/plugin-lib": "^1.39.14",
"@certd/acme-client": "^1.40.1",
"@certd/basic": "^1.40.1",
"@certd/pipeline": "^1.40.1",
"@certd/plugin-lib": "^1.40.1",
"psl": "^1.9.0",
"punycode.js": "^2.3.1"
},
@@ -41,5 +41,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":false,
"sourceMap": false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
**Note:** Version bump only for package @certd/plugin-lib
# [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)
**Note:** Version bump only for package @certd/plugin-lib
+7 -7
View File
@@ -1,14 +1,14 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.39.14",
"version": "1.40.1",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"dev": "vite",
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build &&tsc --skipLibCheck",
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
@@ -23,10 +23,10 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.11",
"@aws-sdk/client-s3": "^3.964.0",
"@certd/acme-client": "^1.39.14",
"@certd/basic": "^1.39.14",
"@certd/pipeline": "^1.39.14",
"@certd/plus-core": "^1.39.14",
"@certd/acme-client": "^1.40.1",
"@certd/basic": "^1.40.1",
"@certd/pipeline": "^1.40.1",
"@certd/plus-core": "^1.40.1",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -61,5 +61,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
"gitHead": "73996f055bbff996ee776e7788e5a5cb500fc197"
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"inlineSourceMap": false
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap": false,
"sourceMap": false,
"sourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
+35
View File
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
### Performance Improvements
* 商业版支持限制泛域名数量 ([c63745d](https://github.com/certd/certd/commit/c63745d1ba30904428ba6b13ab0785298baa5cae))
* **suite:** 商业版用户已购套餐支持修改 ([bdb3d09](https://github.com/certd/certd/commit/bdb3d09c09fc73e7e5e3401f6ef5588bf8ad5088))
# [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)
### Bug Fixes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.39.14",
"version": "1.40.1",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -106,8 +106,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.39.14",
"@certd/pipeline": "^1.39.14",
"@certd/lib-iframe": "^1.40.1",
"@certd/pipeline": "^1.40.1",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -78,8 +78,13 @@ export default {
passkeyRegisterFailed: "Passkey registration failed",
title: "Change Password",
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",
setNow: "Set Now",
notNow: "Not Now",
successMessage: "Changed successfully",
initPasswordSuccessMessage: "Set successfully",
oldPassword: "Old Password",
oldPasswordRequired: "Please enter the old password",
newPassword: "New Password",
@@ -40,6 +40,9 @@ export default {
basicInfo: "Basic Information",
disabled: "Disabled",
enabled: "Enabled",
enabledLabel: "Enabled",
normal: "Normal",
used_up: "Used up",
default: "Default",
success: "Success",
test: "Test",
@@ -6,6 +6,9 @@ export default {
specifications: "Specifications",
pipeline: "Pipeline",
domain: "Domain",
totalDomain: "Total Domain Count",
wildcardDomain: "Wildcard Domain",
includedWildcardDomain: "Included Wildcard Domain Count",
deployTimes: "Deployments",
monitorCount: "DomainMonitors",
duration: "Duration",
@@ -24,11 +27,13 @@ export default {
please_select_package: "Please select a package",
package: "Package",
addon_package: "Addon Package",
domain_count: "Domain Count",
domain_count: "Total Domain Count",
wildcard_domain_count: "Wildcard Domain Count",
unit_count: "pcs",
pipeline_count: "Pipeline Count",
unit_item: "items",
deploy_count: "Deploy Count",
deploy_count_used: "Used Deploy Count",
unit_times: "times",
monitor_count: "Certificate Monitor Count",
duration: "Duration",
@@ -42,6 +47,7 @@ export default {
intro: "Introduction",
packageContent: "Package Content",
maxDomainCount: "Max Domain Count",
maxWildcardDomainCount: "Max Wildcard Domain Count",
maxPipelineCount: "Max Pipeline Count",
maxDeployCount: "Max Deploy Count",
maxMonitorCount: "Max Monitor Count",
@@ -51,6 +57,10 @@ export default {
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",
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",
deployCount: "Deployment Count",
unitDeploy: "times",
@@ -91,6 +91,8 @@ export default {
reverseProxyEmpty: "No reverse proxy list configured",
environmentVars: "Environment Variables",
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",
bindUrlHelper: "Bind URL, used as your site URL in notifications",
@@ -79,8 +79,13 @@ export default {
title: "修改密码",
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
initPasswordWarning: "当前账号还未设置登录密码,请先设置密码",
initPasswordTitle: "设置密码",
changeNow: "立即修改",
setNow: "立即设置",
notNow: "暂不设置",
successMessage: "修改成功",
initPasswordSuccessMessage: "设置成功",
oldPassword: "旧密码",
oldPasswordRequired: "请输入旧密码",
newPassword: "新密码",
@@ -44,6 +44,9 @@ export default {
basicInfo: "基础信息",
disabled: "禁用",
enabled: "启用",
enabledLabel: "启用",
normal: "正常",
used_up: "已用完",
default: "默认",
success: "成功",
test: "测试",
@@ -22,6 +22,8 @@ export default {
mySuite: "我的套餐",
suiteBuy: "套餐购买",
myTrade: "我的订单",
myWallet: "我的钱包",
inviteCommission: "邀请返佣",
paymentReturn: "支付返回",
source: "源码",
github: "github",
@@ -46,6 +48,8 @@ export default {
suiteSetting: "套餐设置",
orderManager: "订单管理",
userSuites: "用户套餐",
inviteCommissionSetting: "邀请返佣设置",
inviteWithdraw: "提现申请记录",
netTest: "网络测试",
enterpriseManager: "企业管理设置",
projectManager: "项目管理",
@@ -6,6 +6,9 @@ export default {
specifications: "规格",
pipeline: "流水线",
domain: "域名",
totalDomain: "域名总数量",
wildcardDomain: "泛域名",
includedWildcardDomain: "其中泛域名数量",
deployTimes: "部署次数",
monitorCount: "域名监控数",
duration: "时长",
@@ -24,11 +27,13 @@ export default {
please_select_package: "请选择套餐",
package: "套餐",
addon_package: "加量包",
domain_count: "域名数量",
domain_count: "域名数量",
wildcard_domain_count: "泛域名数量",
unit_count: "个",
pipeline_count: "流水线数量",
unit_item: "条",
deploy_count: "部署次数",
deploy_count_used: "已用部署次数",
unit_times: "次",
monitor_count: "证书监控数量",
duration: "时长",
@@ -42,6 +47,7 @@ export default {
intro: "介绍",
packageContent: "套餐内容",
maxDomainCount: "最大域名数",
maxWildcardDomainCount: "最大泛域名数",
maxPipelineCount: "最大流水线数",
maxDeployCount: "最大部署数",
maxMonitorCount: "最大监控数",
@@ -51,6 +57,10 @@ export default {
addon: "加量包",
typeHelper: "套餐:同一时间只有最新购买的一个生效\n加量包:可购买多个,购买后立即生效,不影响套餐\n套餐和加量包数量可叠加",
pipelineCount: "流水线数量",
wildcardDomainCount: "泛域名数量",
wildcardDomainCountPart: "其中泛域名数量",
wildcardDomainCountSub: "- 泛域名数量",
wildcardDomainCountHelper: "泛域名数量受域名总数量限制:泛域名会同时占用域名总数量和泛域名数量额度;注意:如果域名总数有限制,泛域名数量不要设置为无限制。",
unitPipeline: "条",
deployCount: "部署次数",
unitDeploy: "次",
@@ -89,6 +89,8 @@ export default {
reverseProxyEmpty: "未配置反向代理",
environmentVars: "环境变量",
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
commonHeaders: "公共请求头",
commonHeadersHelper: "服务端发起 HTTP 请求时自动附加的公共请求头,每行一个,格式:KEY=VALUE;请求中已设置同名 Header 时不会覆盖\n注意: 不要将token等敏感内容放在此处,仅限个人和公司内部使用,商业版不要设置",
bindUrl: "绑定URL",
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
},
+2
View File
@@ -12,9 +12,11 @@ import plugin from "./plugin/";
import { setupVben } from "./vben";
import { util } from "/@/utils";
import { initPreferences } from "/@/vben/preferences";
import { inviteUtils } from "/@/utils/util.invite";
// import "./components/code-editor/import-works";
// @ts-ignore
async function bootstrap() {
inviteUtils.captureFromLocation();
const app = createApp(App);
// app.use(Antd);
app.use(Antd);
@@ -324,7 +324,7 @@ export const certdResources = [
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
return settingStore.isInviteCommissionEnabled;
},
icon: "ion:gift-outline",
auth: true,
@@ -359,6 +359,36 @@ export const certdResources = [
keepAlive: true,
},
},
{
title: "certd.myWallet",
name: "MyWallet",
path: "/certd/wallet",
component: "/certd/wallet/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:wallet-outline",
auth: true,
keepAlive: true,
},
},
{
title: "certd.inviteCommission",
name: "InviteCommission",
path: "/certd/invite",
component: "/certd/invite/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true,
keepAlive: true,
},
},
{
title: "certd.paymentReturn",
name: "PaymentReturn",
@@ -286,6 +286,38 @@ export const sysResources = [
keepAlive: true,
},
},
{
title: "certd.sysResources.inviteCommissionSetting",
name: "SysInviteCommissionSetting",
path: "/sys/suite/invite/setting",
component: "/sys/suite/invite/setting.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
permission: "sys:settings:edit",
auth: true,
keepAlive: true,
},
},
{
title: "certd.sysResources.inviteWithdraw",
name: "SysInviteWithdraw",
path: "/sys/suite/invite/withdraw",
component: "/sys/suite/invite/withdraw.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:cash-outline",
permission: "sys:settings:edit",
auth: true,
keepAlive: true,
},
},
],
},
{
@@ -85,6 +85,7 @@ export type SysPublicSetting = {
type: string;
title: string;
addonId: number;
icon?: string;
}
>;
// 系统通知
@@ -96,9 +97,13 @@ export type SysPublicSetting = {
export type SuiteSetting = {
enabled?: boolean;
};
export type InviteSetting = {
enabled?: boolean;
};
export type SysPrivateSetting = {
httpProxy?: string;
httpsProxy?: string;
commonHeaders?: string;
reverseProxies?: any;
dnsResultOrder?: string;
commonCnameEnabled?: boolean;
@@ -140,6 +145,7 @@ export type AllSettings = {
siteEnv: SiteEnv;
headerMenus: HeaderMenus;
suiteSetting: SuiteSetting;
inviteSetting: InviteSetting;
app: AppInfo;
};
@@ -30,6 +30,9 @@ export interface SettingState {
headerMenus?: HeaderMenus;
inited?: boolean;
suiteSetting?: SuiteSetting;
inviteSetting?: {
enabled?: boolean;
};
app: {
version?: string;
time?: number;
@@ -102,6 +105,7 @@ export const useSettingStore = defineStore({
menus: [],
},
suiteSetting: { enabled: false },
inviteSetting: { enabled: false },
inited: false,
app: {
version: "",
@@ -196,6 +200,9 @@ export const useSettingStore = defineStore({
// @ts-ignore
return this.suiteSetting?.enabled === true;
},
isInviteCommissionEnabled(): boolean {
return this.isComm && this.inviteSetting?.enabled === true;
},
},
actions: {
checkPlus() {
@@ -215,6 +222,7 @@ export const useSettingStore = defineStore({
merge(this.plusInfo, allSettings.plusInfo || {});
merge(this.headerMenus, allSettings.headerMenus || {});
merge(this.suiteSetting, allSettings.suiteSetting || {});
merge(this.inviteSetting, allSettings.inviteSetting || {});
//@ts-ignore
this.initSiteInfo(allSettings.siteInfo || {});
this.initAppInfo(allSettings.app || {});
@@ -4,6 +4,7 @@ export interface RegisterReq {
username: string;
password: string;
confirmPassword: string;
inviteCode?: string;
}
/**
* @description: Login interface parameters
@@ -18,6 +19,7 @@ export interface SmsLoginReq {
phoneCode: string;
smsCode: string;
randomStr: string;
inviteCode?: string;
}
export interface ForgotPasswordReq {
@@ -38,6 +40,7 @@ export interface UserInfoRes {
avatar?: string;
roleIds: number[];
isWeak?: boolean;
needInitPassword?: boolean;
validTime?: number;
status?: number;
}
@@ -38,6 +38,9 @@ export const useUserStore = defineStore({
getToken(): string {
return this.token || LocalStorage.get(TOKEN_KEY);
},
isLogined(): boolean {
return !!this.getToken;
},
isAdmin(): boolean {
return this.getUserInfo.roleIds?.includes(1) || this.getUserInfo.id === 1;
},
@@ -14,7 +14,7 @@ export function useReference(formItem: any) {
compute: (opts: any) => {
const func = (context: any) => {
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({
...context,
form,
@@ -0,0 +1,54 @@
const INVITE_STORAGE_KEY = "certd_invite_code";
const INVITE_TTL = 3 * 24 * 60 * 60 * 1000;
export type InviteCache = {
code: string;
expiresAt: number;
};
function normalizeInviteCode(code?: string | null) {
return code?.trim().toUpperCase();
}
export const inviteUtils = {
save(code?: string | null) {
const normalized = normalizeInviteCode(code);
if (!normalized) {
return;
}
const cache: InviteCache = {
code: normalized,
expiresAt: Date.now() + INVITE_TTL,
};
localStorage.setItem(INVITE_STORAGE_KEY, JSON.stringify(cache));
},
get() {
const text = localStorage.getItem(INVITE_STORAGE_KEY);
if (!text) {
return "";
}
try {
const cache = JSON.parse(text) as InviteCache;
if (!cache.code || !cache.expiresAt || cache.expiresAt < Date.now()) {
localStorage.removeItem(INVITE_STORAGE_KEY);
return "";
}
return cache.code;
} catch (e) {
localStorage.removeItem(INVITE_STORAGE_KEY);
return "";
}
},
captureFromLocation() {
const hashQuery = window.location.hash?.split("?")[1] || "";
const search = window.location.search?.replace(/^\?/, "") || "";
const hashParams = new URLSearchParams(hashQuery);
const searchParams = new URLSearchParams(search);
const code = hashParams.get("inviteCode") || searchParams.get("inviteCode");
if (code) {
this.save(code);
}
},
};
@@ -0,0 +1,13 @@
import { request } from "/@/api/service";
export async function GetMyInvite() {
return await request({ url: "/invite/my", method: "post" });
}
export async function GetInvitees(query: any) {
return await request({ url: "/invite/invitees/page", method: "post", data: query });
}
export async function GetCommissionLogs(query: any) {
return await request({ url: "/invite/commission/page", method: "post", data: query });
}
@@ -0,0 +1,34 @@
import { CreateCrudOptionsProps, CreateCrudOptionsRet, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import * as api from "./api";
export default function (): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetInvitees(query);
};
return {
crudOptions: {
request: { pageRequest },
actionbar: { show: false },
toolbar: { show: false },
rowHandle: { show: false },
columns: {
inviteeUserId: {
title: "被邀请人ID",
type: "number",
column: { width: 140 },
},
inviteCode: {
title: "邀请码",
type: "text",
column: { width: 160 },
},
createTime: {
title: "邀请时间",
type: "datetime",
column: { width: 180 },
},
},
},
};
}
@@ -0,0 +1,59 @@
import { CreateCrudOptionsRet, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import * as api from "./api";
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
export default function (): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetCommissionLogs(query);
};
return {
crudOptions: {
request: { pageRequest },
actionbar: { show: false },
toolbar: { show: false },
rowHandle: { show: false },
columns: {
type: {
title: "类型",
type: "dict-select",
dict: dict({
data: [{ label: "佣金入账", value: "commission", color: "success" }],
}),
column: { width: 130 },
},
amount: {
title: "金额",
type: "number",
column: {
width: 120,
component: { name: PriceInput, vModel: "modelValue", edit: false },
},
},
inviteeUserDisplay: {
title: "被邀请用户",
type: "text",
column: { width: 150 },
},
consumeAmount: {
title: "消费金额",
type: "number",
column: {
width: 120,
component: { name: PriceInput, vModel: "modelValue", edit: false },
},
},
remark: {
title: "备注",
type: "text",
column: { minWidth: 220 },
},
createTime: {
title: "时间",
type: "datetime",
column: { width: 180 },
},
},
},
};
}
@@ -0,0 +1,137 @@
<template>
<fs-page class="page-invite">
<template #header>
<div class="title">邀请返佣</div>
</template>
<div v-if="loaded && enabled" class="invite-body">
<div class="invite-link-row flex-o">
<span class="label">邀请码</span>
<fs-copyable v-model="inviteInfo.inviteCode" />
</div>
<div class="invite-link-row flex-o mt-10">
<span class="label">邀请链接</span>
<fs-copyable v-model="inviteInfo.inviteLink" />
</div>
<a-tabs v-model:active-key="activeTab" class="invite-tabs mt-6">
<a-tab-pane key="invitees" tab="邀请成功">
<fs-crud v-if="activeTab === 'invitees'" ref="inviteesCrudRef" class="invite-crud" v-bind="inviteesCrudBinding" />
</a-tab-pane>
<a-tab-pane key="logs" tab="佣金记录">
<fs-crud v-if="activeTab === 'logs'" ref="logsCrudRef" class="invite-crud" v-bind="logsCrudBinding" />
</a-tab-pane>
</a-tabs>
</div>
<a-empty v-else-if="loaded" description="邀请返佣未开启" />
</fs-page>
</template>
<script lang="ts" setup>
import { computed, nextTick, onActivated, onMounted, reactive, ref } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import * as api from "./api";
import createInviteesCrudOptions from "./crud-invitees";
import createLogsCrudOptions from "./crud-logs";
import { useSettingStore } from "/@/store/settings";
defineOptions({ name: "InviteCommission" });
const settingStore = useSettingStore();
const enabled = computed(() => settingStore.isInviteCommissionEnabled);
const activeTab = ref("invitees");
const inviteInfo = reactive<any>({ inviteCode: "", inviteLink: "" });
const loaded = ref(false);
const { crudBinding: inviteesCrudBinding, crudExpose: inviteesCrudExpose, crudRef: inviteesCrudRef } = useFs({ createCrudOptions: createInviteesCrudOptions });
const { crudBinding: logsCrudBinding, crudExpose: logsCrudExpose, crudRef: logsCrudRef } = useFs({ createCrudOptions: createLogsCrudOptions });
async function loadMyInvite() {
const res: any = await api.GetMyInvite();
inviteInfo.inviteCode = res.inviteCode;
inviteInfo.inviteLink = res.inviteLink;
}
async function refreshActiveList() {
if (activeTab.value === "invitees") {
await inviteesCrudExpose.doRefresh();
} else if (activeTab.value === "logs") {
await logsCrudExpose.doRefresh();
}
}
async function refreshInvitePage(refreshAll = false) {
await settingStore.initOnce();
loaded.value = true;
if (!enabled.value) {
return;
}
await loadMyInvite();
await nextTick();
if (refreshAll) {
await Promise.all([inviteesCrudExpose.doRefresh(), logsCrudExpose.doRefresh()]);
return;
}
await refreshActiveList();
}
onMounted(async () => {
await refreshInvitePage(true);
});
onActivated(async () => {
if (!loaded.value) {
return;
}
await refreshInvitePage();
});
</script>
<style lang="less">
.page-invite {
display: flex;
min-height: 0;
.fs-page-content {
display: flex;
min-height: 0;
}
.invite-body {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
padding: 20px;
}
.invite-tabs {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
.ant-tabs-content-holder,
.ant-tabs-content,
.ant-tabs-tabpane {
display: flex;
flex: 1;
min-height: 0;
}
.ant-tabs-tabpane {
flex-direction: column;
}
.invite-crud {
flex: 1;
min-height: 0;
}
.label {
width: 80px;
flex: none;
text-align: right;
margin-right: 8px;
}
}
</style>
@@ -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) {
return await request({
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({
url: "/oauth/unbind",
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({
url: "/oauth/login",
method: "POST",
data: {
type,
subtype,
forType: "bind",
},
});
@@ -9,7 +9,7 @@ import { ref } from "vue";
import { useI18n } from "/src/locales";
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 { notification } from "ant-design-vue";
import { useUserStore } from "/@/store/user";
@@ -20,6 +20,11 @@ defineProps<{
let passwordFormRef = ref();
type OpenOptions = {
password?: string;
init?: boolean;
};
const validatePass1 = async (rule: any, value: any) => {
if (value === "") {
throw new Error(t("authentication.enterPassword"));
@@ -53,19 +58,33 @@ const passwordFormOptions: CrudOptions = {
width: "500px",
},
async doSubmit({ form }) {
await api.changePassword(form);
if (form.init) {
await api.initPassword(form);
} else {
await api.changePassword(form);
}
//
await userStore.loadUserInfo();
},
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: {
init: {
title: "init",
type: "text",
form: {
show: false,
},
},
password: {
title: t("authentication.oldPassword"),
type: "password",
form: {
show: compute(({ form }) => form.init !== true),
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);
formOptions.newInstance = true; //
if (opts.init) {
formOptions.wrapper.title = t("authentication.initPasswordTitle");
}
passwordFormRef.value = await openDialog(formOptions);
passwordFormRef.value.setFormData({
password: opts.password,
init: opts.init === true,
password: opts.password || "",
});
console.log(passwordFormRef.value);
}
@@ -48,7 +48,7 @@
</div>
</div>
<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">
{{ t("authentication.securitySettingTip") }}
@@ -78,11 +78,11 @@
<a-tag v-else color="red" class="bound-tag1">未绑定</a-tag>
</span>
</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>
解绑
</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>
绑定
</a-button>
@@ -214,7 +214,7 @@ async function loadOauthProviders() {
const computedOauthBounds = computed(() => {
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 {
...item,
bound,
@@ -223,20 +223,24 @@ const computedOauthBounds = computed(() => {
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({
title: "确认解绑吗?",
okText: "确认",
okType: "danger",
onOk: async () => {
await api.UnbindOauth(type);
await api.UnbindOauth(item.name, item.subtype);
await loadOauthBounds();
},
});
}
async function bind(type: string) {
const res = await api.OauthBoundUrl(type);
async function bind(item: any) {
const res = await api.OauthBoundUrl(item.name, item.subtype);
const loginUrl = res.loginUrl;
window.location.href = loginUrl;
}
@@ -383,6 +387,7 @@ const checkPasskeySupport = () => {
}
};
const userStore = useUserStore();
const changePasswordButtonRef = ref();
const userAvatar = computed(() => {
if (isEmpty(userInfo.value.avatar)) {
return "";
@@ -391,11 +396,26 @@ const userAvatar = computed(() => {
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 () => {
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 loadOauthBounds();
await loadOauthProviders();
@@ -320,6 +320,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
title: t("monitor.siteName"),
search: {
show: true,
col: {
span: 3,
},
},
type: "text",
form: {
@@ -333,6 +336,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
title: t("monitor.domain"),
search: {
show: true,
col: {
span: 3,
},
},
type: "text",
form: {
@@ -411,6 +417,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
title: t("monitor.certDomains"),
search: {
show: true,
col: {
span: 3,
},
},
type: "text",
form: {
@@ -451,6 +460,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
title: t("monitor.certStatus"),
search: {
show: true,
col: {
span: 2,
},
},
type: "dict-select",
dict: dict({
@@ -472,7 +484,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
checkStatus: {
title: t("monitor.checkStatus"),
search: {
show: false,
show: true,
col: {
span: 2,
},
},
type: "dict-select",
dict: checkStatusDict,
@@ -578,6 +593,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "dict-select",
search: {
show: true,
col: {
span: 3,
},
},
dict: groupDictRef,
form: {
@@ -631,7 +649,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
disabled: {
title: t("monitor.disabled"),
search: {
show: false,
show: true,
col: {
span: 2,
},
},
type: "dict-switch",
dict: dict({
@@ -56,6 +56,10 @@ export function findStep(pipeline: any, id: string) {
export async function checkPipelineLimit() {
const settingsStore = useSettingStore();
if (settingsStore.isEnterprise) {
//企业模式不限制
return;
}
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
//检查数量是否超限
@@ -46,6 +46,7 @@ export type TradeCreateReq = {
duration: number;
num: number;
payType: string;
useRebateBalance?: boolean;
};
export async function TradeCreate(form: TradeCreateReq) {
@@ -14,6 +14,7 @@ export type SuiteDetail = {
expiresTime?: number;
pipelineCount?: SuiteValue;
domainCount?: SuiteValue;
wildcardDomainCount?: SuiteValue;
deployCount?: SuiteValue;
monitorCount?: SuiteValue;
};
@@ -147,7 +147,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
"content.maxDomainCount": {
title: "域名数量",
title: "域名数量",
type: "text",
form: {
key: ["content", "maxDomainCount"],
@@ -168,6 +168,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
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": {
title: "流水线数量",
type: "text",
@@ -11,7 +11,10 @@
<span class="label">{{ $t("certd.order.specifications") }}</span>
<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.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.monitorCount") }}<suite-value class="ml-5" :model-value="product.content.maxMonitorCount" :unit="$t('certd.order.unit.times')" /> </span>
</span>
@@ -25,10 +28,19 @@
<span class="label">{{ $t("certd.order.price") }}</span>
<price-input :edit="false" :model-value="durationSelected.price"></price-input>
</div>
<div v-if="durationSelected.price > 0 && wallet.availableAmount > 0" class="flex-o mt-5">
<span class="label">返利抵扣</span>
<a-switch v-model:checked="formRef.useRebateBalance" />
<span class="ml-10">可用 {{ amountToYuan(wallet.availableAmount) }} 预计抵扣 {{ amountToYuan(expectedRebateAmount) }} </span>
</div>
<div v-if="durationSelected.price > 0 && formRef.useRebateBalance" class="flex-o mt-5">
<span class="label">还需支付</span>
<span class="color-red">{{ amountToYuan(expectedThirdPartyAmount) }} </span>
</div>
<div class="flex-o mt-5">
<span class="label">{{ $t("certd.order.paymentMethod") }}</span>
<div v-if="durationSelected.price === 0">{{ $t("certd.order.free") }}</div>
<div v-if="durationSelected.price === 0 || expectedThirdPartyAmount === 0">{{ $t("certd.order.free") }}</div>
<fs-dict-select v-else v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select>
</div>
</div>
@@ -36,7 +48,7 @@
</template>
<script setup lang="tsx">
import { ref } from "vue";
import { computed, ref } from "vue";
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
@@ -46,11 +58,14 @@ import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
import { useRouter } from "vue-router";
import qrcode from "qrcode";
import * as api from "/@/views/certd/suite/api";
import { GetMyInvite } from "/@/views/certd/invite/api";
import { util } from "/@/utils";
const openRef = ref(false);
const product = ref<any>(null);
const formRef = ref<any>({});
const durationSelected = ref<any>(null);
const wallet = ref<any>({ availableAmount: 0 });
async function open(opts: OrderModalOpenReq) {
openRef.value = true;
@@ -60,6 +75,13 @@ async function open(opts: OrderModalOpenReq) {
formRef.value.productId = opts.product.id;
formRef.value.duration = opts.duration;
formRef.value.num = opts.num ?? 1;
formRef.value.useRebateBalance = false;
try {
const inviteInfo: any = await GetMyInvite();
wallet.value = inviteInfo.wallet || { availableAmount: 0 };
} catch (e) {
wallet.value = { availableAmount: 0 };
}
}
const paymentsDictRef = dict({
async getData() {
@@ -74,6 +96,21 @@ const paymentsDictRef = dict({
const router = useRouter();
const expectedRebateAmount = computed(() => {
if (!formRef.value.useRebateBalance) {
return 0;
}
return Math.min(wallet.value.availableAmount || 0, durationSelected.value?.price || 0);
});
const expectedThirdPartyAmount = computed(() => {
return Math.max(0, (durationSelected.value?.price || 0) - expectedRebateAmount.value);
});
function amountToYuan(amount: number) {
return util.amount.toYuan(amount || 0);
}
async function orderCreate() {
if (durationSelected.value.price === 0) {
//0
@@ -90,7 +127,7 @@ async function orderCreate() {
return;
}
if (!formRef.value.payType) {
if (expectedThirdPartyAmount.value > 0 && !formRef.value.payType) {
notification.error({
message: "请选择支付方式",
});
@@ -101,8 +138,17 @@ async function orderCreate() {
duration: formRef.value.duration,
num: formRef.value.num ?? 1,
payType: formRef.value.payType,
useRebateBalance: formRef.value.useRebateBalance,
});
if (paymentReq.paid) {
notification.success({
message: "套餐购买成功",
});
openRef.value = false;
return;
}
async function onPaid() {
openRef.value = false;
router.push({
@@ -1,7 +1,7 @@
<template>
<a-card :title="product.title" class="product-card">
<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>
<div class="product-intro">{{ product.intro || "暂无介绍" }}</div>
@@ -12,9 +12,13 @@
<suite-value :model-value="product.content.maxPipelineCount" unit="条" />
</div>
<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="个" />
</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-o">
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
@@ -151,6 +151,30 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
rebateAmount: {
title: "返利抵扣",
type: "number",
column: {
width: 110,
component: {
name: PriceInput,
vModel: "modelValue",
edit: false,
},
},
},
thirdPartyPayAmount: {
title: "实付金额",
type: "number",
column: {
width: 110,
component: {
name: PriceInput,
vModel: "modelValue",
edit: false,
},
},
},
status: {
title: "状态",
search: { show: true },
@@ -177,6 +201,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
{ label: "支付宝", value: "alipay" },
{ label: "微信", value: "wxpay" },
{ label: "免费", value: "free" },
{ label: "返利余额", value: "rebate" },
],
}),
column: {
@@ -0,0 +1,21 @@
import { request } from "/@/api/service";
export async function GetWalletSummary() {
return await request({ url: "/wallet/summary", method: "post" });
}
export async function GetWithdrawSetting() {
return await request({ url: "/wallet/withdraw/setting/get", method: "post" });
}
export async function SaveWithdrawSetting(data: any) {
return await request({ url: "/wallet/withdraw/setting/save", method: "post", data });
}
export async function ApplyWithdraw(amount: number) {
return await request({ url: "/wallet/withdraw/apply", method: "post", data: { amount } });
}
export async function GetWithdraws(query: any) {
return await request({ url: "/wallet/withdraw/page", method: "post", data: query });
}

Some files were not shown because too many files have changed in this diff Show More