Compare commits

...
93 Commits
Author SHA1 Message Date
xiaojunnuo 9b1cf353b9 v1.45.3 2026-10-19 10:34:11 +08:00
xiaojunnuo 2b66faef90 build: prepare to build 2026-10-19 10:19:13 +08:00
xiaojunnuo 28e4f70e49 fix: 修复修改密码时校验表单字段的错误
修复了修改密码功能中,访问表单实例的属性路径错误,导致无法正确校验确认新密码字段的问题。
2026-10-19 10:13:10 +08:00
xiaojunnuo 93031e7f53 build: release 2026-10-18 21:42:30 +08:00
xiaojunnuo d60635f766 build: publish 2026-10-18 21:20:24 +08:00
xiaojunnuo f5df69fbc9 build: trigger build image 2026-10-18 21:20:13 +08:00
xiaojunnuo 9f15bb8a21 v1.45.2 2026-10-18 21:19:10 +08:00
xiaojunnuo 6bbaee0d99 build: prepare to build 2026-10-18 21:07:23 +08:00
xiaojunnuo 554a20e1fc style(plugin): 调整插件卡片简单模式的卡片体最小高度
将插件卡片简单模式下的卡片体最小高度从 128px 调整为 122px,以适配布局显示
2026-10-18 21:05:47 +08:00
xiaojunnuo 60e00f8f20 refactor(ksyun): 重构金山云客户端实现并调整请求配置
新增金山云 V2 客户端类,优化请求参数处理和 Content-Type 设置,调整 access 配置中 service 和 endpoint 的顺序,移除无用的空行代码
2026-10-18 19:39:19 +08:00
xiaojunnuo 62751bee7a perf: 新增更新金山云KCM SLB证书插件 2026-10-18 17:15:16 +08:00
xiaojunnuo 1b012c791c chore(plugin): 新增插件来源切换标签页并优化相关文本说明 2026-10-18 12:57:46 +08:00
xiaojunnuo 5f83693423 perf: 支持ikuai部署证书 2026-10-18 03:34:57 +08:00
xiaojunnuo cfd384485f perf: 为PEM输入组件添加文件拖拽上传功能 2026-10-17 17:35:32 +08:00
xiaojunnuo 61ef971518 perf(baota): 宝塔站点全自动部署插件支持部署docker站点 2026-10-17 17:34:52 +08:00
xiaojunnuo 60d0872ba1 chore: 1 2026-10-16 14:57:36 +08:00
xiaojunnuo a67d5bca2d fix: 修复腾讯云CLB部署证书逻辑 2026-10-16 14:52:13 +08:00
xiaojunnuo 1e1e76b4a8 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-10-16 14:03:48 +08:00
xiaojunnuo 9d02cd84f0 build: release 2026-10-14 23:23:47 +08:00
xiaojunnuo a9806315f1 build: publish 2026-10-14 22:55:47 +08:00
xiaojunnuo 42b743d5bb build: trigger build image 2026-10-14 22:55:36 +08:00
xiaojunnuo e9b9839d79 v1.45.1 2026-10-14 22:54:35 +08:00
xiaojunnuo 12ef05c528 build: prepare to build 2026-10-14 22:40:20 +08:00
xiaojunnuo 21b88ff017 build: prepare to build 2026-10-14 00:04:42 +08:00
xiaojunnuo 8e169be562 docs: 为所有插件添加英文国际化配置 2026-10-14 00:04:33 +08:00
xiaojunnuo 6b6e684c88 build: prepare to build 2026-10-13 23:50:23 +08:00
xiaojunnuo 3c98b974f7 build: 更新 @fast-crud 相关依赖到 1.29.0 版本
style: 修复代码格式、添加缺失逗号和分号以符合规范

refactor: 调整 flex 布局属性和 Tailwind 配置的缩进格式

docs: 移除重复的英文翻译项 "no" 和 "required"
2026-10-13 23:50:03 +08:00
xiaojunnuo e2aff952f9 chore: 添加证书密码字段sql并更新路由标题 2026-10-13 22:31:01 +08:00
xiaojunnuo 9d9ae64aff fix: 修复批量修改证书申请参数,无法选择证书颁发机构的bug 2026-10-13 22:18:56 +08:00
xiaojunnuo 22715a430e perf: 证书申请成功通知支持批量修改
- 新增英文和中文的操作按钮国际化文案(编辑、删除、启用、禁用)
- 优化状态国际化处理逻辑,支持动态加载对应语言的状态文本
- 新增证书申请成功通知批量修改组件和API逻辑
- 修复状态文本显示逻辑,确保正确使用国际化文案
- 优化通知更新逻辑,支持替换指定触发条件的通知规则
2026-10-13 21:37:06 +08:00
xiaojunnuo 949a69f0dc fix(aliyun-cert): 阿里云订阅证书增加申请请求接口 2026-10-13 20:28:16 +08:00
xiaojunnuo 0874cb2223 chore(email-service): 注释plus邮件发送逻辑并更新错误提示
注释plus邮件发送的自动使用逻辑,同时更新错误提示信息,明确指引用户配置邮件服务器
2026-10-12 10:38:02 +08:00
xiaojunnuo 53db97b6f2 chore: i18n 优化 2026-10-12 00:18:36 +08:00
xiaojunnuo cd2dcaa859 chore: 国际化持续完善 2026-10-11 23:28:45 +08:00
xiaojunnuo d0463abc26 perf: 完善插件翻译 2026-10-11 18:24:42 +08:00
xiaojunnuo 8f1b38ad7d perf: 多语言国际化优化,插件支持国际化配置
- 新增钱包模块,包含余额、提现、收益明细等功能
- 为插件、访问授权、任务等添加英文国际化支持
- 重构页面标题为多语言文本,统一国际化展示
- 完善导航、设置等页面的多语言支持
- 修复部分代码格式问题,优化插件国际化处理逻辑
2026-10-11 15:09:52 +08:00
xiaojunnuo e11349b8cc refactor: 重构国际化文案结构,按模块拆分命名空间
调整所有国际化文案的命名空间,将零散的文案归类到对应业务模块下,优化语言文件的组织结构,便于后续维护和扩展。
2026-10-11 13:39:34 +08:00
xiaojunnuo 2394cc6dd9 chore(certd-client): 修复开关选项的字典数据顺序
调整了字典数据的顺序,确保“否”选项在“是”选项之后,符合常规交互逻辑
2026-10-11 13:23:27 +08:00
xiaojunnuo 8209c3d6e1 chore(open-api): 添加返回证书密码开关及多语言支持
在开放接口密钥配置中新增返回证书密码的开关选项,添加中英文多语言提示文本和开关选项文案,明确开关开启时响应会包含证书密码,提醒妥善保管。
2026-10-11 13:22:58 +08:00
xiaojunnuo 3e001b8ba4 perf: 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署
新增证书密码字段到证书信息和开放密钥表,添加返回开关,开放接口可根据开关决定是否返回证书密码
2026-10-11 12:24:09 +08:00
xiaojunnuo 41ca198116 perf: 插件市场新增同步按钮并调整搜索区域布局 2026-10-04 14:23:51 +08:00
xiaojunnuo 6eca5dad9b chore(wangsu): 修复网宿access getCertList 不支持普通CDN的bug 2026-10-04 00:35:31 +08:00
xiaojunnuo c285522500 chore(wangsu): 新增网宿CDN Pro证书列表获取方法,更新插件说明
新增getCdnProCertList方法用于获取网宿CDN Pro的证书列表,同时更新WangsuRefreshCert插件的描述,明确仅支持普通CDN证书更新,CDN Pro证书需使用对应在线插件。
2026-10-04 00:08:10 +08:00
xiaojunnuo b12f7c54d4 build: release 2026-10-03 02:47:15 +08:00
xiaojunnuo b055bbecfc build: publish 2026-10-03 01:01:10 +08:00
xiaojunnuo a52a6ce8f6 build: trigger build image 2026-10-03 01:01:00 +08:00
xiaojunnuo fcbf1045fe v1.45.0 2026-10-03 01:00:08 +08:00
xiaojunnuo f5cfe054b7 build: prepare to build 2026-10-03 00:51:15 +08:00
xiaojunnuo 5ded5ff2b6 test: 更新测试用例的断言数据和测试描述 2026-10-03 00:51:09 +08:00
xiaojunnuo 8d31346cd3 build: prepare to build 2026-10-03 00:22:38 +08:00
xiaojunnuo 44b41b6a08 chore(certd-server): 同步迁移脚本 2026-10-02 03:17:46 +08:00
xiaojunnuo 4420472f8f perf: 宝塔站点列表支持搜索和排序 2026-10-01 21:49:39 +08:00
xiaojunnuo c4497a7018 chore: 更新DNS持久验证的支持证书类型为Google证书
更新了多个位置的提示文本,将原Let's Encrypt测试环境的说明替换为Google证书,同时调整了验证方式的说明顺序和内容,还修改了插件加载模式为metadata,优化了代码中的格式细节。
2026-10-01 10:47:28 +08:00
xiaojunnuo 03f1a7bd0e chore: 修复获取证书颁发者时未处理空directoryUrl的问题
修改两处代码,在获取DNS持久化颁发者时,当directoryUrl为空时使用accountUri作为备用,避免因缺少必要参数导致的错误
2026-10-01 10:01:41 +08:00
xiaojunnuo 97d2b50074 fix: 支持google persist校验方式 2026-09-30 18:19:23 +08:00
xiaojunnuo 2d0b85b305 fix: 缺失运行时嵌套依赖时添加清除缓存提示
当加载运行时依赖时出现嵌套模块未找到错误,添加明确的错误提示,指导用户前往插件管理页面清除缓存依赖后重试。同时新增单元测试验证该提示逻辑。
2026-09-30 16:34:11 +08:00
xiaojunnuo 28052c9387 chore: clb tencent 2026-09-30 16:22:59 +08:00
xiaojunnuo c4277350bc perf(tencent clb): 修改所有CLB地域的endpoint为就近接入 2026-09-30 15:58:13 +08:00
xiaojunnuo 97160373d6 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-09-30 15:32:57 +08:00
xiaojunnuo 0ce24618a1 docs: 修正HTTPS环境变量命名为小写格式 2026-09-29 15:06:06 +08:00
xiaojunnuo f60e63fbaf perf(monitor): 站点证书监控支持手动选中触发检查 2026-09-20 23:04:49 +08:00
xiaojunnuo 2735feb330 fix(webhook): 修复模版转义换行符,避免手动上传的证书报错
1.  将webhook插件内的replaceTemplate方法迁移到stringUtils公共工具
2.  优化模板替换处理,支持转义更多控制字符和兼容非字符串变量
3.  添加对应的单元测试用例
2026-09-20 23:04:13 +08:00
xiaojunnuo b6381d79fc chore: 批量优化插件开发相关配置与文档
1.  更新gitignore,新增.tmp_chunks忽略项
2.  切换certd插件加载模式为dev
3.  修正插件示例yaml的title注释
2026-09-20 01:26:02 +08:00
xiaojunnuo 241b2e5584 perf: 网宿插件双鉴权支持 2026-09-20 01:25:52 +08:00
xiaojunnuo bb1f232f41 fix: [BUG]使用天翼云CDN部署证书时无法匹配域名
https://github.com/certd/certd/issues/805
2026-09-19 18:05:22 +08:00
xiaojunnuo f82d3a5fcc chore(monitor): 补全站点新增/导入时的IP检查相关布尔默认值
解决批量导入等场景未传ipCheck等字段导致数据库存NULL,编辑弹窗开关过不了校验的issue #803,同时新增对应单测验证默认值和显式传参的正确性
2026-09-19 18:00:15 +08:00
xiaojunnuo bbeaf617d1 perf(open-api): openkey增加用途备注
1. 新增数据库字段存储开放接口密钥备注信息
2. 多语言添加密钥备注相关文案
3. 开放密钥接口支持备注参数传递与CRUD
4. 前端页面支持备注的展示、搜索与编辑
5. 调整插件加载模式为metadata并修复代码格式
2026-09-19 17:41:02 +08:00
xiaojunnuo ea7d14c289 fix(ctyun-cdn): 修复天翼云CDN部署插件的域名选择问题
1.  添加产品类型监听,切换产品类型时重新拉取域名列表
2.  未选择产品类型时抛出明确错误提示
3.  补全域名选项的domain字段以支持证书域名匹配分组
4.  新增单元测试覆盖上述场景
2026-09-18 01:12:36 +08:00
xiaojunnuo 3272a68ff6 style(certd client page): 调整头部布局和VIP按钮展示逻辑
优化页面头部子标题区域的对齐样式,移除非Plus版本的提示文本和条件渲染逻辑,统一展示VIP按钮
2026-09-18 00:18:13 +08:00
xiaojunnuo b5715d99ff Merge branch 'v2-dev' into v2_client 2026-09-17 23:52:49 +08:00
xiaojunnuo 3f5ba14fde ci(workflow): 修正stable-release.yml里版本号示例格式
将版本号示例的v前缀移除,和输入要求保持一致
2026-09-17 23:26:32 +08:00
xiaojunnuo 07f0d3b4d6 fix(config): 修复环境变量加载逻辑并更新配置文件
1.  重构loadDotEnv方法,支持指定加载的env文件路径,增加文件存在校验
2.  跳过注释和空行的环境变量加载,添加加载日志
3.  在默认配置中加载对应环境的env文件
4.  更新开发和生产环境的env配置项
2026-09-17 23:23:35 +08:00
xiaojunnuo 3dbbe72fc1 feat(client): 新增客户端管理功能
实现了客户端管理的完整流程:
1. 新增数据库表与迁移脚本,存储客户端信息与心跳快照
2. 实现服务端心跳上报接口与管理API
3. 新增前端页面与国际化多语言支持
4. 完善客户端在线状态判定逻辑与分页查询功能
2026-09-17 22:46:03 +08:00
xiaojunnuo 7f006d1359 refactor(plugin): 迁移同步插件逻辑到页面组件并优化上下文传递
1. 将plugin crud组件中的syncOnlinePlugins方法迁移到index.vue中
2. 把syncLoading和syncOnlinePlugins加入页面上下文传递给crud组件
3. 移除crud组件中冗余的syncLoading声明
4. 调整页面初始化调用顺序
2026-09-16 00:15:16 +08:00
xiaojunnuo d30f1c4785 refactor(plugin): 优化在线插件同步逻辑与时间格式化
1.  将自动同步相关逻辑抽离到页面组件统一管理
2.  使用dayjs替代原生Date进行时间格式化
3.  移除crud.tsx中冗余的同步配置加载代码
4.  统一维护自动同步的间隔时间常量
2026-09-15 23:52:29 +08:00
xiaojunnuo 96e0122b93 build: release 2026-09-12 20:26:56 +08:00
xiaojunnuo 7786a87b21 refactor(certd-server): 移除getReleaseMode函数,改用配置注入获取发布模式
1.  从环境配置文件中注释移除certd_release_mode的注释,启用稳定发布模式
2.  在AppController和SettingsController中注入release.mode配置替代原有的全局函数获取方式
3.  移除冗余的getReleaseMode工具函数
4.  在SettingsController中添加发布模式的日志打印
2026-09-12 20:22:18 +08:00
xiaojunnuo 34476ba14f build: publish 2026-09-12 19:24:56 +08:00
xiaojunnuo 4b942385a8 build: trigger build image 2026-09-12 19:24:46 +08:00
xiaojunnuo 14809127d6 v1.44.4 2026-09-12 19:23:35 +08:00
xiaojunnuo bd8b251d6e build: prepare to build 2026-09-12 19:17:14 +08:00
xiaojunnuo 5563abb13a fix: 修复证书插件配置无法打开的bug 2026-09-12 19:13:23 +08:00
xiaojunnuo 51e83e4c38 fix: 修复添加步骤会修改所有步骤标题的bug 2026-09-12 18:51:53 +08:00
xiaojunnuo 2b1a5296fe chore(certd-client): fix step form save data overwritten issue and add no payment tip
1. 修复步骤表单保存时因复用reactive对象导致已保存步骤被覆盖的问题,深拷贝currentStep后再传出
2. 新增无支付方式时显示暂不支持在线支付的提示
2026-09-12 18:49:24 +08:00
xiaojunnuo bded368cbb perf: 新增余额管理,管理员充值功能
1. 新增余额管理页面,支持用户余额查询与充值
2. 新增余额变更记录页面,查看用户余额明细
3. 补充多语言国际化文案
4. 修复免费订单显示文案为0元而非免费
5. 隐藏审核页面的操作按钮
6. 注释掉无用的堆日志启动代码
2026-09-12 16:06:53 +08:00
xiaojunnuo 6a649aaca0 perf(acme-account-access): AcmeAccount授权允许修改eab,重新生成account
修改了三处ACME账号表单的禁用判断条件,新增判断当editAccount为true时不禁用控件,允许在编辑已有账号时修改相关配置
2026-09-12 14:44:43 +08:00
xiaojunnuo e971145686 perf(ui): 商业版套餐购买不要求设置支付方式
1. 将套餐设置页面的"需要开启支付方式"提示改为"建议"
2. 新增支付方式为空且余额不足时的红色警告提示
3. 默认开启返利余额使用并修复支付字典加载异常处理
2026-09-12 14:28:52 +08:00
xiaojunnuo bbca449f94 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-09-12 09:32:36 +08:00
xiaojunnuo e6852c2715 docs: 更新AGENTS.md编码规范说明
refactor(plugin-fastly): 简化多行API调用代码
refactor(plugin-serv
2026-09-11 19:01:29 +08:00
xiaojunnuo 47cb523307 chore(cloudflare): 优化权限说明的换行展示
修复Cloudflare插件的权限说明文本排版,将权限项用换行分开展示,提升可读性
2026-09-10 15:13:38 +08:00
xiaojunnuo cbf46b5c48 build: release 2026-09-07 00:07:49 +08:00
xiaojunnuo 7ed3105d97 build: publish 2026-09-06 23:51:23 +08:00
xiaojunnuo 61b945c08f build: trigger build image 2026-09-06 23:51:13 +08:00
946 changed files with 12540 additions and 3252 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "版本号(如 v1.42.5)"
description: "版本号(如 1.42.5)"
required: true
type: string
+2 -1
View File
@@ -42,4 +42,5 @@ output/
.uploads/
.certd-plugin-history/
.tmp
.plugin-dev/
.plugin-dev/
.tmp_chunks/
+62
View File
@@ -23,6 +23,8 @@ version: 1.0.0
- 配置插件的唯一标识、标题、图标和描述
- 继承 `BaseAccess` 类
- 插件默认文案使用中文;需要英文时,在注解参数对象中添加同级 `i18n: { "en-US": { ... } }`,不要写成 `metadata.i18n`。`define` 本身就是插件 metadata。
- `i18n["en-US"]` 仅配置要翻译的展示字段,如 `title`、`desc`、`input`;输入字段可翻译 `title`、`helper`、组件展示文案和 `optionsMapping`。缺少英文翻译时回退默认中文。
3. **定义授权属性**
@@ -392,6 +394,66 @@ export class OAuthDemoAccess extends BaseAccess {
}
```
## 字段动态显隐(mergeScript)
多种鉴权方式共用一个授权时,用 `@AccessInput` 上的 `mergeScript` 按所选方式显隐字段:
```typescript
@AccessInput({
title: '鉴权方式',
value: 'apikey',
component: {
name: 'a-select',
vModel: 'value',
options: [
{ label: 'AccessKey签名', value: 'aksk' },
{ label: 'ApiKey', value: 'apikey' },
],
},
required: true,
})
authType = 'apikey';
@AccessInput({
title: 'accessKeyId',
component: { name: 'a-input', vModel: 'value' },
required: true,
mergeScript: `
return {
show: ctx.compute(({form})=>{
return !form.access.authType || form.access.authType === 'aksk';
})
}
`,
})
accessKeyId = '';
```
要点:
- `mergeScript` 返回的对象会深合并进本字段定义,可动态化的属性有 `show`、`required`、`helper`、
`component` 下的属性(`options`、`placeholder` 等)以及 `component.on` 事件。
- **动态判断必须写在 `ctx.compute(({form})=>{ ... })` 回调里**。`mergeScript` 本身在插件类
定义阶段就被拼成字符串,那时表单还没打开,用参数在服务端判断字段值没有意义。
- 授权表单字段值在 `form.access.xxx`(任务表单才是 `form.xxx`)。
- 历史授权没有新字段,条件里要用 `!form.access.authType || ...` 兜底走旧逻辑,保证老授权还能编辑保存。
- 联动其他字段用 `component.on` 事件回调,回调参数是 `{ form }`:
```typescript
mergeScript: `
return {
component: {
on: {
selectedChange: (scope) => {
const form = scope.form;
form.acmeAccountAccessId = null;
},
},
},
}
`,
```
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
+38 -7
View File
@@ -9,7 +9,7 @@ description: 用于通过 Certd API 开发、修改、读取、保存和恢复
本 Skill 使用 Certd HTTP API,不使用 WebSocket,也不依赖浏览器传递代码草稿。
- 前端只生成包含需求、API 地址和认证 Token 的启动提示词。
- 前端只生成包含需求、插件作者、API 地址和认证 Token 的启动提示词。
- Agent 直接调用 Certd API 读取和保存插件 YAML。
- Agent 的进度、日志和代码修改在 Codex/Trae 中查看。
- 插件开发临时文件和修改历史统一保存在 Agent 工作区的 `.plugin-dev/` 下,不保存到浏览器或 Certd 后端。
@@ -43,15 +43,13 @@ Certd 插件按来源分为三类:
## API 认证
提示词会提供 Certd API 地址和仅限 AI 插件开发接口的受限 Token。调用 API 时使用:
提示词会提供 Certd API 地址和仅限 AI 插件开发接口的受限的短时效的 Token。调用 API 时使用:
```http
Authorization: <token>
Content-Type: application/json
```
不要把 Token 写入代码、历史摘要、日志、提交信息或插件 YAML。
所有 Certd API 请求统一使用 Node.js 的 `fetch`。不要使用 PowerShell 的 `Invoke-RestMethod`、`Invoke-WebRequest` 或 .NET HTTP 客户端发送插件 YAML/JSON;它们在 Windows 上可能造成中文乱码或使完整 YAML 导入请求长时间无响应。
Node 请求须直接读取 UTF-8 文件或在 Node 内构造 JSON,并使用 `JSON.stringify`:
@@ -118,23 +116,56 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
- 修改后保存修改摘要。
- 恢复前再次备份当前版本。
- 不上传历史文件,不保存 Token、证书、私钥或真实授权值。
- 修改完一版后立即提交更新到certd,但不要发布到市场。
详细格式见 `references/local-history.md`。
## YAML 和脚本规范
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。`content` 必须使用 YAML 字面块 `|` 保留 JavaScript 换行,禁止使用折叠块 `>`,避免语句拼接导致 `Unexpected token 'const'` 等解析错误。
- 新插件的 `author` 必须使用启动提示词中给出的插件作者,不要自行编造作者名;修改已有插件时保留原 `author`,不要改成其他作者。
- 统一使用 `await _ctx.import(...)` 引用模块。
- `"/@/..."` 表示以绝对路径引用 `server/src/` 下的模块。
- 最后返回继承目标基类的 class。
- 不使用 `import`、`export`、装饰器或独立源码文件语法。
- 使用 `this.logger` 打印插件执行日志。
- 使用 `this.ctx.http` 访问 HTTP 能力。
- 使用目标基类提供的 logger 打印插件执行日志;在线 Access 使用 `this.ctx.logger`。
- 使用 `this.ctx.http` 访问 HTTP 能力;默认返回响应体 `response.data`,需要响应头、状态码或 Cookie 时必须设置 `returnOriginRes: true`,再从 `response.data` 读取响应体。详见 `references/context-utils.md`。
- 优先复用 `this.ctx.utils` 中的现成工具,尤其是 `this.ctx.utils.hash` 的摘要和编码方法;完整分类见 `references/context-utils.md`。
- 失败时抛出 `Error`。
- 日志打印对象时必须使用 `JSON.stringify(value)`,禁止直接字符串拼接对象导致输出 `[object Object]`;需要可读日志时先构造明确的 JSON 字符串。
### 证书域名匹配与列表组件
- 需要从前置证书获取域名时,优先在 `input` 前置配置 `certDomains` 字段,使用 `cert-domains-getter`,并通过 `inputKey` 指向证书字段。
- 远程证书或资源列表如果需要按证书域名匹配,必须为每个选项提供 `domain` 字段;`domain` 可以是单个域名字符串,也可以是域名数组。只有满足此前提,才能使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)` 返回分组后的列表。
- 如果服务商接口不支持分页或服务端搜索,列表组件不要设置 `pager: true` 或 `search: true`;确需搜索时在插件中获取完整列表后执行本地过滤。
需要字段格式时读取 `references/online-yaml-format.md`。
需要组件示例时读取 `references/component-examples.md`。
需要按其他字段动态显隐/必填/联动时读取 `references/merge-script.md`。
需要查找 `this.ctx.utils` 工具或确认 HTTP 返回结构时读取 `references/context-utils.md`。
### 字段动态显隐(mergeScript)
字段定义支持 `mergeScript`:脚本 `return` 的对象会合并进本字段,用来做「选了 A 才显示 B」这类联动。
```yaml
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
return form.access.authType === 'apikey';
})
}
```
- 动态逻辑必须写在 `ctx.compute(({form})=>{ ... })` 回调里;`mergeScript` 本身在插件类定义阶段就
被拼成字符串,那时表单还没打开,用参数在 node 侧判断字段值没有任何意义。
- 授权表单字段值在 `form.access.xxx`,任务表单字段值在 `form.xxx`。
- 新增可选字段时要给历史数据兜底,例如 `!form.access.authType || form.access.authType === 'aksk'`。
完整用法(能力、可动态化的属性、事件联动、常见错误、验证方式)见 `references/merge-script.md`。
## 示例插件
@@ -1,6 +1,6 @@
name: DemoAccess
icon: logos:airflow-icon
title: Demo-授权插件示例 # 模块-插件名
title: Demo-授权插件示例 # 格式:平台-功能 ,例如: 网宿-授权 ; 阿里云-授权 ; 天翼云-授权
group: null
desc: 这只是一个示例
version: 1.0.0
@@ -1,6 +1,6 @@
name: DemoDeploy
icon: logos:amp-icon
title: Demo-部署插件示例 # 模块-插件名
title: Demo-部署插件示例 # 格式:平台-功能 ,例如: 网宿-更新CDNpro证书 ; 阿里云-上传到CAS证书管理服务 ; 天翼云-更新CDN证书
group: cdn
desc: 这仅仅是一个示例
version: 1.0.0
@@ -1,6 +1,6 @@
name: DemoDnsProvider
icon: fa-solid:frog
title: Demo-Dns提供商插件示例 # 模块-插件名
title: Demo-Dns提供商插件示例 # 格式:平台-功能 ,例如: 阿里云-DNS提供商 ;腾讯云-DNS提供商
desc: 这只是一个示例
type: custom
version: 1.0.0
@@ -17,3 +17,61 @@ Access 插件负责保存授权配置,也负责封装平台 API/SDK,供 Task
- 业务插件通过 `dependPlugins` 依赖 Access。
- `editable: false` 时不要尝试修改 Access,在业务插件内部实现必要的调用。
- 不要在日志中打印完整授权配置。
## 地址与请求复用
- `BaseAccess` 已提供通用的 `normalizeEndpoint(endpoint)`,用于校验地址并清理 URL 末尾的 `/`。Access 插件直接调用 `this.normalizeEndpoint(this.endpoint)`,不要在插件中重复实现同名方法。
- Cookie 解析优先使用 `this.ctx.http.getCookies(response)` 或 `this.ctx.http.getCookie(response, name)`;需要原始响应时为请求设置 `returnOriginRes: true`。
## TLS 证书校验
需要兼容自签名或过期 HTTPS 证书时,在 Access 表单中使用 `skipSslVerify` 开关,并将其显式传给 `this.ctx.http.request`。默认关闭校验跳过,仅用户主动开启后才跳过:
```yaml
input:
skipSslVerify:
title: 忽略 HTTPS 证书校验
required: false
component:
name: a-switch
vModel: checked
```
```js
const response = await this.ctx.http.request({
url: `${baseUrl}/api`,
method: "POST",
skipSslVerify: this.skipSslVerify === true,
});
```
## 测试按钮
通过 `api-test` 输入项绑定 `onTestRequest`,用户点击按钮后 Certd 会调用该方法。测试方法应执行轻量、只读的连通性请求,并在失败时抛出有意义的错误:
```yaml
input:
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口是否正常
```
```js
async onTestRequest() {
const baseUrl = this.normalizeEndpoint(this.endpoint);
const response = await this.ctx.http.request({
url: `${baseUrl}/api/status`,
method: "GET",
skipSslVerify: this.skipSslVerify === true,
});
if (!response || response.code !== 0) {
throw new Error(`连接测试失败:${response?.message || "接口返回异常"}`);
}
return "连接成功";
}
```
@@ -70,29 +70,8 @@ name: "a-auto-complete",
options: [
{ value: "cn-qingdao", label: "华北1(青岛)" },
{ value: "cn-beijing", label: "华北2(北京)" },
{ value: "cn-zhangjiakou", label: "华北3(张家口)" },
{ value: "cn-huhehaote", label: "华北5(呼和浩特)" },
{ value: "cn-wulanchabu", label: "华北6(乌兰察布)" },
{ value: "cn-hangzhou", label: "华东1(杭州)" },
{ value: "cn-shanghai", label: "华东2(上海)" },
{ value: "cn-shenzhen", label: "华南1(深圳)" },
{ value: "cn-guangzhou", label: "华南3(广州)" },
{ value: "ap-southeast-2", label: "澳大利亚(悉尼)" },
{ value: "ap-southeast-3", label: "马来西亚(吉隆坡)" },
{ value: "ap-northeast-1", label: "日本(东京)" },
{ value: "cn-chengdu", label: "西南1(成都)" },
{ value: "ap-southeast-1", label: "新加坡" },
{ value: "ap-southeast-5", label: "印度尼西亚(雅加达)" },
{ value: "cn-hongkong", label: "中国香港" },
{ value: "eu-central-1", label: "德国(法兰克福)" },
{ value: "us-east-1", label: "美国(弗吉尼亚)" },
{ value: "us-west-1", label: "美国(硅谷)" },
{ value: "eu-west-1", label: "英国(伦敦)" },
{ value: "me-east-1", label: "阿联酋(迪拜)" },
//金融云
...
{ value: "cn-beijing-finance-1", label: "华北2 金融云(邀测)" },
{ value: "cn-hangzhou-finance", label: "华东1 金融云" },
{ value: "cn-shanghai-finance-1", label: "华东2 金融云" },
{ value: "cn-shenzhen-finance-1", label: "华南1 金融云" },
],
placeholder: "集群所属大区",
@@ -0,0 +1,103 @@
# 在线插件运行时工具
在线插件实例可以通过 `this.ctx.utils` 复用 Certd 已有工具。开发前按分类查找现成方法,避免重复实现。
## 工具分类
| 分类 | 调用入口 | 常用方法 | 用途 |
| --- | --- | --- | --- |
| 哈希与编码 | `this.ctx.utils.hash` | `md5(data, digest?)`、`sha256(data, digest?)`、`base64(data)`、`base64Decode(data)`、`hmacSha256(data, digest?)`、`hmacSha256WithKey(key, data, digest?)`、`toHex(data)`、`hexToStr(data)`、`hexToNumber(data)` | 摘要、签名、Base64/Hex 编解码 |
| 字符串 | `this.ctx.utils.string` | 读取 `packages/core/basic/src/utils/util.string.ts` | 转换、截断、格式化 |
| 域名 | `this.ctx.utils.domain` | 读取 `packages/core/basic/src/utils/util.domain.ts` | 主域名和子域名处理,域名选项列表,域名匹配分组 |
| 请求 | `this.ctx.utils.request` / `this.ctx.http` | `getCookies(response)`、`getCookie(response, name)`、`download(config)` | Cookie、下载和请求辅助 |
| 文件 | `this.ctx.utils.file` | 读取 `packages/core/basic/src/utils/util.file.ts` | 文件读写 |
| Promise/延迟 | `this.ctx.utils.promises`、`this.ctx.utils.sleep` | 读取对应实现 | 重试、等待、并发 |
| 时间 | `this.ctx.utils.dayjs` | Day.js API | 时间处理 |
| ID/缓存/合并 | `this.ctx.utils.id`、`nanoid`、`cache`、`mergeUtils` | 读取对应 `util.*.ts` | ID、缓存和配置合并 |
示例:
```javascript
const passwd = this.ctx.utils.hash.md5(password);
const pass = this.ctx.utils.hash.base64(`salt_11${password}`);
```
方法签名以仓库中 `packages/core/basic/src/utils/util.*.ts` 为准,不要凭名称猜测参数顺序。
## `this.ctx.http.request` 返回结构
`this.ctx.http.request` 默认直接返回响应体,也就是 Axios 的 `response.data`:
```javascript
const body = await this.ctx.http.request({ url: "https://api.example.com/status", method: "GET" });
```
此时 `body` 不能再读取 `body.data`。如果需要响应头、状态码或 `Set-Cookie`,必须设置 `returnOriginRes: true`:
```javascript
const response = await this.ctx.http.request({
url: "https://api.example.com/login",
method: "POST",
returnOriginRes: true,
data: { username, password },
});
const body = response.data;
const setCookie = response.headers?.["set-cookie"];
const cookies = this.ctx.http.getCookies(response);
const sessionId = this.ctx.http.getCookie(response, "session_id");
```
规则:普通 API 直接使用 `response` 作为 body;需要 headers/status/Cookie 时使用 `returnOriginRes: true`,再从 `response.data` 读取 body。首页预请求和登录请求都需要 Cookie 时,两次请求都要设置该选项。`skipSslVerify: true` 仅在用户明确配置忽略证书校验时传入。
## `HttpRequestConfig` 请求参数
`this.ctx.http.request` 接受以下 Certd 扩展参数,同时兼容 Axios 的标准请求配置(如 `url`、`method`、`headers`、`params`、`data`、`timeout`、`responseType` 等):
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `skipSslVerify` | `boolean` | 为 `true` 时关闭 HTTPS 证书校验。仅在授权表单提供了明确开关且用户主动启用时传入。 |
| `skipCheckRes` | `boolean` | Certd 内部兼容参数,在线插件通常不需要设置。 |
| `logReq` | `boolean` | 是否打印请求地址和方法。默认开启;敏感请求可设为 `false`。 |
| `logParams` | `boolean` | 是否打印 URL 查询参数。默认关闭。参数中含密钥、Token 或隐私数据时保持关闭。 |
| `logRes` | `boolean` | 是否打印响应状态和响应体摘要。默认关闭。 |
| `logData` | `boolean` | 是否打印请求体。默认关闭;禁止对包含密码、Token、Cookie 或证书私钥的请求开启。 |
| `httpProxy` | `string` | 本次请求使用的 HTTP 代理地址。没有明确代理需求时不要设置。 |
| `returnOriginRes` | `boolean` | 默认返回响应体 `response.data`;设为 `true` 时返回 Axios 原始响应对象,可读取 `response.data`、`response.headers` 和 `response.status`。获取 `Set-Cookie` 时必须开启。 |
示例:
```javascript
const response = await this.ctx.http.request({
url: `${baseUrl}/Action/login`,
method: "POST",
returnOriginRes: true,
skipSslVerify: this.ignoreTls === true,
logData: false,
headers: { "Content-Type": "application/json" },
data: requestData,
});
const body = response.data;
const cookies = response.headers?.["set-cookie"];
```
## Cookie 和文件下载辅助
原始响应可传给 HTTP 客户端提供的 Cookie 辅助方法:
```javascript
const cookies = this.ctx.http.getCookies(response);
const sessionId = this.ctx.http.getCookie(response, "session_id");
```
`getCookies(response)` 返回 `{ name, value }[]`;没有 `set-cookie` 时返回空数组。`getCookie(response, name)` 找不到目标 Cookie 时返回 `null`。
下载文件可使用 `this.ctx.http.download`,参数为 `{ http, config, savePath, logger }`,`config` 遵循 `HttpRequestConfig`:
```javascript
await this.ctx.http.download({
http: this.ctx.http,
config: { url: fileUrl, method: "GET", logRes: false },
savePath,
logger: this.ctx.logger,
});
```
@@ -0,0 +1,181 @@
# mergeScript 用法
来源:前端 `packages/ui/certd-client/src/use/use-refrence.tsx`,类型定义来自 `@fast-crud/fast-crud`。
`mergeScript` 用来让输入字段的定义**依赖当前表单里的其他字段值动态变化**,例如:
选了某个鉴权方式才显示对应字段、切换类型后清空已选值、下拉选项跟着另一个字段变。
它写成字符串放在字段定义上(内置插件放在 `@AccessInput` / `@TaskInput` 里,在线插件放在 `input.<字段>` 下):
```ts
@AccessInput({
title: "API Key",
component: { name: "a-input", vModel: "value" },
mergeScript: `
return {
show: ctx.compute(({form})=>{
//access里面 要用 form.access.[字段名]
return form.access.authType === 'apikey';
//deploy插件里面要用form.[字段名]
return form.authType === 'apikey';
})
}
`,
})
apiKey = "";
```
## 执行时机
字段定义被前端加载时,对**每一个字段**执行一次:
```js
const func = new Function('ctx', formItem.mergeScript);
const merged = func(ctx); // 字段脚本只执行这一次
merge(formItem, merged); // 返回值深合并进该字段的定义
delete formItem.mergeScript; // 执行完就删掉,最终定义里不再有 mergeScript
```
要点:
- 脚本体内**必须 `return` 一个对象**,返回的对象会深合并到本字段的定义上。
- 脚本本身只在加载字段时跑一次,**拿不到、也不应该判断用户填了什么值**。
- 真正的动态逻辑写在返回对象里的 `ctx.compute(...)` 回调中,由 fast-crud 在渲染时求值。
## ctx 提供的能力
| 能力 | 说明 |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `ctx.compute(fn)` | 最常用。返回一个响应式的计算值:`fn` 里读到的表单字段会被自动追踪,字段变化时重新计算,视图跟着刷新 |
`ctx.compute` 的回调参数是一个 context 对象,常用字段:
- `form`:当前表单数据。**这是唯一常用的一个**。
- `row`:行数据。
- `mode`:表单模式 `add` / `edit` / `view`。
- `getComponentRef(key)`:取其他字段组件的 ref。
注意:Certd 在把 `form` 传进回调前做过一次兼容处理(`form.input || form.body || form`),
所以两种表单取值路径不同:
- **授权(Access)表单**:字段值在 `form.access.xxx`(字段 key 会被加上 `access.` 前缀)。
- **任务(Task)表单**:字段值直接是 `form.xxx`。
## 可以动态化的属性
返回的对象里,任何字段定义属性都可以用 `ctx.compute`,常用的有:
| 属性 | 用途 |
| -------------------- | ------------------------------------------------------------------------------------------------- |
| `show` | 显隐(最常用) |
| `required` | 动态必填,例如 `required: ctx.compute(({form}) => form.targetType === 'ingress')` |
| `disabled` | 动态禁用,例如账号已创建后不允许再改:`disabled: ctx.compute(({form}) => !!form.access?.account)` |
| `helper` | 动态帮助文案,回调里按条件 `return` 不同字符串 |
| `value` | 动态默认值 |
| `component` 下的属性 | 动态 `options`、`placeholder`、`inputKey`、`type`、`disabled` 等 |
| `component.on` | 事件处理,见下面「联动」 |
| `col` / `order` | 布局与排序 |
只写了一层 `show` 时最简形式就是本文开头的例子。
## 联动(监听事件并改动其他字段)
`component.on` 的事件回调**不**用 `ctx.compute` 包,它本身就是回调,参数是
`{ form, $event, ... }`;`form` 同样做过 `form.input || form.body || form` 的兼容处理。
事件名按组件而定,例如 `selectedChange`。
```ts
mergeScript: `
return {
component: {
on: {
selectedChange: (scope) => {
const form = scope.form;
// 切换颁发机构后清空 ACME 账号,避免账号与机构不匹配
form.acmeAccountAccessId = null;
},
},
},
}
`,
```
另一种写法是把回调也做成计算值(需要用到组件上配置的 `onSelectedChange` / `onChange` 属性):
```ts
mergeScript: `
return {
component: {
onSelectedChange: ctx.compute(({form}) => {
return ($event) => {
form.dnsProviderAccessType = $event.accessType;
};
}),
},
}
`,
```
两种写法都能用,优先参考同目录已有插件的写法,保持一致。
## 常见错误
- **不要用 node 侧变量决定条件**。`mergeScript` 是在插件类定义阶段(node 侧)拼出来的字符串,
此时表单还没打开,拿不到任何用户输入;用参数拼条件等于把逻辑写死在服务端。
条件必须原样落到 `ctx.compute` 回调里。
- **不要用 `if` 直接判断字段值**。回调必须 `return` 一个值,写成
`show: ctx.compute(({form}) => { if (...) { return true } })` 也可以,但不要漏掉 `return`。
- **不要漏掉历史数据兼容**。新增可选字段时,历史数据里没有这个字段(值为 `undefined`),
条件要按旧行为兜底,例如 `!form.access.authType || form.access.authType === 'aksk'`。
- **不要访问 `form.access` 以外的路径**去读授权字段,也不要在授权表单里按任务表单的路径取值。
- **不要依赖脚本执行时的副作用**(打印日志、改全局状态),它在每次打开表单时都会重新执行。
## YAML 写法(在线插件)
`mergeScript` 是含换行的脚本字符串,必须用 YAML 块标量:
```yaml
input:
apiKey:
title: API Key
component:
name: a-input
vModel: value
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
return form.access.authType === 'apikey';
})
}
```
导出生成的缩进比较特殊(块标量内容统一缩进),**不要手工调整缩进**:
用脚本生成 YAML,并保证导出后再导入内容一致。转换成 block scalar 时注意:
- `|` / `|2-` 等块标量不需要转义换行,不要把脚本写成带 `\n` 的单行。
- 脚本里不要出现 `??` 这类容易被误判为乱码的写法,用显式 `if (x == null)` 代替。
## 验证方式
前端表单没法在插件开发流程里直接断言,可以在后端加一个 `src/**/*.test.ts`,
按前端 `useReference` 的路径执行字段上的 `mergeScript`:
```ts
const computedGetters = [];
const ctx = {
compute: getter => {
computedGetters.push(getter);
return getter;
},
asyncCompute: () => undefined,
computed: () => undefined,
};
new Function('ctx', mergeScript)(ctx);
// 用假的 form 求值,断言显隐结果
const show = computedGetters[0]({ form: { access: { authType: 'apikey' } } });
```
参考实现:`packages/ui/certd-server/src/plugins/plugin-wangsu/access-input.test.ts`。
要点是断言「同一个 show 函数换 `authType` 结果会变」,证明显隐确实是运行时算的,
而不是在 node 侧定死的。
@@ -7,7 +7,7 @@
```yaml
name: DemoTask
author: demo
title: Demo 任务
title: Demo-插件名称 # 格式:平台-功能 ,例如: 网宿-更新CDNpro证书 ; 阿里云-上传到CAS证书管理服务 ; 天翼云-更新CDN证书
desc: 插件说明
icon: clarity:plugin-line
pluginType: task
@@ -47,20 +47,32 @@ content: |
### `content` 规则
- `content` 必须使用 YAML 字面块 `|` 保留脚本换行,禁止使用折叠块 `>`;折叠换行可能导致多个 JavaScript 语句拼接,出现 `Unexpected token 'const'` 等解析错误。
- 统一使用 `await _ctx.import(...)` 加载模块。
- 使用 `_ctx.import("/@/...")` 以绝对路径加载 `certd-server/src/` 下的模块,`/@` 代表 `certd-server/src` 根路径。
- 需要确认模块时使用 `_ctx.logger`,插件执行过程使用 `this.logger`。
- 最后返回继承目标基类的 class。
- 不使用 `import`、`export`、装饰器或独立源码文件语法。
- 输入字段在 class 中声明为同名属性,并与 YAML 的 `input` 配置保持一致。
- HTTP 使用 `this.ctx.http`;
- HTTP 使用 `this.ctx.http`。默认 `request()` 返回响应体 `response.data`;需要 headers、status 或 Set-Cookie 时设置 `returnOriginRes: true`,此时返回原始响应对象,响应体从 `response.data` 读取。请求扩展参数 `skipSslVerify`、`skipCheckRes`、`logReq`、`logParams`、`logRes`、`logData`、`httpProxy`、`returnOriginRes` 的说明见 `context-utils.md`。
- 优先使用 `this.ctx.utils` 中已有的哈希、编码、字符串、域名和请求工具,详见 `context-utils.md`。
- 不要使用 `console.log`。
- 日志打印对象时使用 `JSON.stringify(value)`,不要直接拼接对象输出 `[object Object]`。
- 读取授权使用 `await this.getAccess(accessId)` 或目标基类规定的授权方式。
- 失败时抛出 `Error`,不要吞掉错误。
### 证书域名与列表选项
- 需要使用前置证书域名时,在列表字段之前配置 `certDomains`,组件使用 `cert-domains-getter`,并通过 `inputKey` 绑定证书字段。
- 列表选项可按证书域名匹配时,必须为每个选项设置 `domain`;该字段支持单个域名字符串或域名数组。只有配置该字段后,才使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)`。
- 服务商接口不支持翻页或服务端搜索时,远程选择组件不要配置 `pager: true`、`search: true`;本地搜索可在获取完整列表后过滤。
### 编辑规则
- 修改已有插件时保留 `name`、`author`、`pluginType` 和已有兼容字段。
- 新插件的 `author` 使用启动提示词中给出的插件作者,不要自行编造作者名;启动提示词未给出作者时先向用户确认。
- `fullName` 由 `author/name` 组成,作者变化会导致插件身份变化,不要随意修改。
- 只修改需求涉及的字段,避免删除未知的 YAML 字段。
- 脚本过长时仍放在同一个 `content` block scalar 中。
- 提交前检查 YAML 可解析、`content` 非空、版本和插件类型没有被意外修改。
@@ -14,6 +14,42 @@ description: 用于开发 Certd 在线 Access 插件。输出完整 YAML,conte
- 敏感字段在 input 中设置加密或密码类组件。
- `content` 中实现授权 class 和 API 方法。
## Metadata 示例
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
```yaml
name: DemoAccess
author: certd
fullName: certd/DemoAccess
title: Demo-授权
desc: Demo 平台授权配置
icon: clarity:plugin-line
group: other
version: 1.0.0
pluginType: access
input:
demoKeyId:
title: Key ID
required: true
component:
name: a-input
vModel: value
demoKeySecret:
title: Key Secret
required: true
encrypt: true
component:
name: a-input-password
vModel: value
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口是否正常
```
## `content` 模板
```javascript
@@ -29,7 +65,7 @@ return class DemoAccess extends BaseAccess {
}
async getDomainList(req) {
this.logger.info("获取域名列表", { searchKey: req.searchKey })
this.ctx.logger.info("获取域名列表", { searchKey: req.searchKey })
const res = await this.ctx.http.request({
url: "https://api.example.com/domains",
method: "GET",
@@ -56,5 +92,42 @@ return class DemoAccess extends BaseAccess {
- 所有敏感授权值只通过 `this` 和 Certd 授权上下文使用,不打印真实值。
- `onTestRequest` 应调用实际 API 方法并在失败时抛出异常。
- 对外 API 方法应统一处理分页、错误和返回字段。
- 使用 `this.logger` 或框架提供的 logger,禁止 `console.log`。
- 一般将API接口方法封装到Access中,其他plugin调用access的方法当做client sdk使用
- 在线 Access 使用 `this.ctx.logger` 打印日志,禁止使用未注入的 `this.logger` 和 `console.log`。
- 优先复用 `this.ctx.utils` 中的现成工具,例如 `this.ctx.utils.hash.md5` 和 `this.ctx.utils.hash.base64`;HTTP 返回值和 Cookie 处理规则见父 Skill 的 `references/context-utils.md`。
- 一般将API接口方法封装到Access中,其他plugin调用access的方法当做client sdk使用
## 字段动态显隐
多种鉴权方式(例如「ApiKey」和「AccessKey签名」)共用一个授权时,
用字段上的 `mergeScript` 按所选方式显隐对应字段,只让用户看到当前方式需要的字段:
```yaml
input:
authType:
title: 鉴权方式
value: apikey
component:
name: a-select
vModel: value
options:
- { label: AccessKey签名, value: aksk }
- { label: ApiKey, value: apikey }
accessKeyId:
title: accessKeyId
component:
name: a-input
vModel: value
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
return !form.access.authType || form.access.authType === 'aksk';
})
}
```
- 判断必须写在 `ctx.compute(({form})=>{ ... })` 回调里;脚本本身在插件定义阶段就拼好了,
那时表单还没打开,用参数在服务端判断字段值没有意义。
- 授权表单的字段值在 `form.access.xxx`(任务表单才是 `form.xxx`)。
- 历史授权没有 `authType` 字段,条件里要用 `!form.access.authType || ...` 兜底走旧逻辑。
完整用法见父 Skill 的 `references/merge-script.md`。
@@ -13,6 +13,22 @@ description: 用于开发 Certd 在线 DNS Provider 插件。输出完整 YAML
- `input` 中配置授权选择、域名或平台所需的参数。
- `content` 中实现创建和删除 DNS 记录的 class。
## Metadata 示例
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
```yaml
name: DemoDnsProvider
author: certd
fullName: certd/DemoDnsProvider
title: Demo-DNS 解析
desc: Demo 平台 DNS-01 验证
icon: clarity:plugin-line
group: dns
version: 1.0.0
pluginType: dnsProvider
```
## `content` 模板
```javascript
@@ -21,10 +37,10 @@ const { DemoAccess } = await _ctx.import("/@/plugins/plugin-lib/demo/access/inde
_ctx.logger.info("DemoAccess:", DemoAccess)
return class DemoDnsProvider extends AbstractDnsProvider {
accessId
access!: DemoAccess;
async onInstance() {
this.access = await this.getAccess(this.accessId)
this.access = this.ctx.access as DemoAccess;
}
async createRecord(options) {
@@ -14,6 +14,38 @@ description: 用于开发 Certd 在线 Task 插件。输出完整 YAML,脚本
- 输入配置放在 YAML 的 `input` 字段。
- 执行脚本放在 YAML 顶层 `content` 字段。
## Metadata 示例
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
```yaml
name: DemoTask
author: certd
fullName: certd/DemoTask
title: Demo-执行任务
desc: 调用 Demo 平台 API 执行操作
icon: clarity:plugin-line
group: other
version: 1.0.0
pluginType: task
input:
cert:
title: 前置任务证书
required: true
component:
name: cert-select
certDomains:
title: 当前证书域名
required: true
component:
name: cert-domains-getter
accessId:
title: Demo 授权
required: true
component:
name: access-selector
type: demo/DemoAccess # 如果是在线Access插件,要写全称, author/name, 如果是本地插件,只需要写插件名即可
```
## runStrategy
@@ -26,10 +58,11 @@ description: 用于开发 Certd 在线 Task 插件。输出完整 YAML,脚本
- 0 表示成功后跳过,1 表示正常运行。
- 设置任务的默认运行策略runStrategy。
当证书申请任务,申请证书成功后,会清空后续任务状态。此时后续`runStrategy = 0` 的任务,会重新执行。
这样可以保证相同的证书不会重复部署。
在其他场景的部署任务,可能希望每次运行都检查是否需要部署,如果即将过期就部署,没有过期就跳过。
有如下三种场景:
1、证书申请任务,设置为 `default.strategy.runStrategy = 1`,表示正常运行,此时executor每次都会执行plugin的execute方法,当无需更新证书时,返回 `"skip"`。
当申请证书成功后,调用`this.clearLastStatus();`清空后续任务的成功状态,后续任务就都会重新执行。
2、直接部署任务,建议设置为 `default.strategy.runStrategy = 0`,表示成功后跳过,executor参数与状态,如果参数没有变化,且上次的状态是成功或跳过,则直接跳过该任务,这样可以保证相同的证书不会重复部署。
3、检查后再部署的任务,可能希望每次运行都检查是否需要部署,如果即将过期就部署,没有过期就跳过。
此时可以设置 `default.strategy.runStrategy = 1`,然后在`execute`方法中,如果没有过期,就返回 `"skip"`。
## `content` 模板
+2
View File
@@ -30,6 +30,8 @@ version: 1.0.0
- 指定对应的云平台的 access 类型名称
- 设置排序值(可选)
- 继承 `AbstractDnsProvider` 类
- 插件默认文案使用中文;需要英文时,在注解参数对象中添加同级 `i18n: { "en-US": { ... } }`,不要写成 `metadata.i18n`。`define` 本身就是插件 metadata。
- `i18n["en-US"]` 仅配置要翻译的展示字段,如 `title`、`desc`、`input`;输入字段可翻译 `title`、`helper`、组件展示文案和 `optionsMapping`。缺少英文翻译时回退默认中文。
4. **实现 onInstance 方法**
+2
View File
@@ -28,6 +28,8 @@ version: 1.0.0
- 设置插件分组
- 配置默认策略(如 `SkipWhenSucceed`)
- 确保类名与插件名称一致
- 插件默认文案使用中文;需要英文时,在注解参数对象中添加同级 `i18n: { "en-US": { ... } }`,不要写成 `metadata.i18n`。`define` 本身就是插件 metadata。
- `i18n["en-US"]` 仅配置要翻译的展示字段,如 `title`、`desc`、`group`、`input`、`output`;未配置的字段自动回退默认中文。不要翻译插件标识、组件类型、默认值或执行参数。
3. **定义任务输入参数**
+1 -1
View File
@@ -75,7 +75,7 @@ Certd 是可私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web
- 只有真正机械化的大批量替换、格式化或生成任务才考虑脚本/工具。若必须使用临时脚本,应放在临时目录并在同一个受控步骤内完成创建、执行、删除;不要把临时脚本落在仓库里跨多步工具调用执行。
- 不要主动运行 `pnpm install`;缺依赖、TTY、网络导致安装或测试失败时,停止尝试并说明环境问题。
- 优先沿用现有模块、插件、service、页面模式;不要为形式上的复用制造过度抽象。
- 代码可读性优先于短写法。尽量少用三元表达式,优先使用完整的 `if/else`;复杂条件、链式调用、内联对象和多层 helper 调用要拆成命名清晰的中间变量或小方法。
- 【重要重要重要】代码可读性优先于短写法。尽量少用三元表达式,优先使用完整的 `if/else`【其他技能与此条要求冲突的,以此条要求为准】;复杂条件、链式调用、内联对象和多层 helper 调用要拆成命名清晰的中间变量或小方法。
- 方法调用链不要直接塞进另一个方法参数;先用有意义的局部变量承接返回值,再传入下一步。
- 不要在单一表达式内嵌套分支、对象构造与方法调用。优先使用清晰的 `if/else` 分支;仅在确实能降低复杂度时才提取有意义的中间变量,避免为拆分而增加阅读跳转。
- 注释优先使用中文,尤其是业务规则、兼容逻辑、协议细节和隐藏风险;文件已有英文风格或引用外部术语时可保持一致。
+70
View File
@@ -3,6 +3,76 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
### Bug Fixes
* 修复修改密码时校验表单字段的错误 ([28e4f70](https://github.com/certd/certd/commit/28e4f70e494f7c56202a8ca4708718c836fff702))
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Bug Fixes
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
### Performance Improvements
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
* **baota:** 宝塔站点全自动部署插件支持部署docker站点 ([61ef971](https://github.com/certd/certd/commit/61ef971518eeccbc31b09c06ec1b48589929d6bb))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Bug Fixes
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
* **aliyun-cert:** 阿里云订阅证书增加申请请求接口 ([949a69f](https://github.com/certd/certd/commit/949a69f0dcf929980363fc2b3d772edae696dcaf))
### Performance Improvements
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
* [BUG]使用天翼云CDN部署证书时无法匹配域名 ([bb1f232](https://github.com/certd/certd/commit/bb1f232f415c522851dede979f5d609dda844fd3))
* 缺失运行时嵌套依赖时添加清除缓存提示 ([2d0b85b](https://github.com/certd/certd/commit/2d0b85b3054fc1aa82b3ab5db34a2a56308f71f2))
* 支持google persist校验方式 ([97d2b50](https://github.com/certd/certd/commit/97d2b5007409ec79ea864d437c473a2f9b5dfae4))
* **config:** 修复环境变量加载逻辑并更新配置文件 ([07f0d3b](https://github.com/certd/certd/commit/07f0d3b4d626628a0e3dfc7e5c52e8c4a98bad47))
* **ctyun-cdn:** 修复天翼云CDN部署插件的域名选择问题 ([ea7d14c](https://github.com/certd/certd/commit/ea7d14c289b8720781bf101bfef59eac7bb03765))
* **webhook:** 修复模版转义换行符,避免手动上传的证书报错 ([2735feb](https://github.com/certd/certd/commit/2735feb3302f8308708dafc8b295adcd4b91d7f0))
### Features
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
### Performance Improvements
* 宝塔站点列表支持搜索和排序 ([4420472](https://github.com/certd/certd/commit/4420472f8fac272d30fb8ccb1cc2ee9763989001))
* 网宿插件双鉴权支持 ([241b2e5](https://github.com/certd/certd/commit/241b2e55848471e61b5d2be0c87a7dc9a0d29557))
* **monitor:** 站点证书监控支持手动选中触发检查 ([f60e63f](https://github.com/certd/certd/commit/f60e63fbaf00460d5050cd7705bd75d57dec202d))
* **open-api:** openkey增加用途备注 ([bbeaf61](https://github.com/certd/certd/commit/bbeaf617d19750515f6eca51863e74e23bc1de50))
* **tencent clb:** 修改所有CLB地域的endpoint为就近接入 ([c427735](https://github.com/certd/certd/commit/c4277350bcc8cf034cf4e76db2a6daa1842bc5d7))
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
### Bug Fixes
* 修复添加步骤会修改所有步骤标题的bug ([51e83e4](https://github.com/certd/certd/commit/51e83e4c38f7f319314fdc38ef33cf5720b2dde0))
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
### Performance Improvements
* 新增余额管理,管理员充值功能 ([bded368](https://github.com/certd/certd/commit/bded368cbb8974b6937ae260e448c25fae5c33ad))
* **acme-account-access:** AcmeAccount授权允许修改eab,重新生成account ([6a649aa](https://github.com/certd/certd/commit/6a649aaca083ccd3b517f7d676a58220aa33b37d))
* **ui:** 商业版套餐购买不要求设置支付方式 ([e971145](https://github.com/certd/certd/commit/e9711456860d7db40914a0c295534f7bca56f83c))
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
### Bug Fixes
+75
View File
@@ -3,6 +3,81 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Bug Fixes
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
### Performance Improvements
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
* **baota:** 宝塔站点全自动部署插件支持部署docker站点 ([61ef971](https://github.com/certd/certd/commit/61ef971518eeccbc31b09c06ec1b48589929d6bb))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Bug Fixes
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
* **aliyun-cert:** 阿里云订阅证书增加申请请求接口 ([949a69f](https://github.com/certd/certd/commit/949a69f0dcf929980363fc2b3d772edae696dcaf))
### Performance Improvements
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
* [BUG]使用天翼云CDN部署证书时无法匹配域名 ([bb1f232](https://github.com/certd/certd/commit/bb1f232f415c522851dede979f5d609dda844fd3))
* 缺失运行时嵌套依赖时添加清除缓存提示 ([2d0b85b](https://github.com/certd/certd/commit/2d0b85b3054fc1aa82b3ab5db34a2a56308f71f2))
* 支持google persist校验方式 ([97d2b50](https://github.com/certd/certd/commit/97d2b5007409ec79ea864d437c473a2f9b5dfae4))
* **config:** 修复环境变量加载逻辑并更新配置文件 ([07f0d3b](https://github.com/certd/certd/commit/07f0d3b4d626628a0e3dfc7e5c52e8c4a98bad47))
* **ctyun-cdn:** 修复天翼云CDN部署插件的域名选择问题 ([ea7d14c](https://github.com/certd/certd/commit/ea7d14c289b8720781bf101bfef59eac7bb03765))
* **webhook:** 修复模版转义换行符,避免手动上传的证书报错 ([2735feb](https://github.com/certd/certd/commit/2735feb3302f8308708dafc8b295adcd4b91d7f0))
### Features
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
### Performance Improvements
* 宝塔站点列表支持搜索和排序 ([4420472](https://github.com/certd/certd/commit/4420472f8fac272d30fb8ccb1cc2ee9763989001))
* 网宿插件双鉴权支持 ([241b2e5](https://github.com/certd/certd/commit/241b2e55848471e61b5d2be0c87a7dc9a0d29557))
* **monitor:** 站点证书监控支持手动选中触发检查 ([f60e63f](https://github.com/certd/certd/commit/f60e63fbaf00460d5050cd7705bd75d57dec202d))
* **open-api:** openkey增加用途备注 ([bbeaf61](https://github.com/certd/certd/commit/bbeaf617d19750515f6eca51863e74e23bc1de50))
* **tencent clb:** 修改所有CLB地域的endpoint为就近接入 ([c427735](https://github.com/certd/certd/commit/c4277350bcc8cf034cf4e76db2a6daa1842bc5d7))
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
### Bug Fixes
* 修复添加步骤会修改所有步骤标题的bug ([51e83e4](https://github.com/certd/certd/commit/51e83e4c38f7f319314fdc38ef33cf5720b2dde0))
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
### Performance Improvements
* 新增余额管理,管理员充值功能 ([bded368](https://github.com/certd/certd/commit/bded368cbb8974b6937ae260e448c25fae5c33ad))
* **acme-account-access:** AcmeAccount授权允许修改eab,重新生成account ([6a649aa](https://github.com/certd/certd/commit/6a649aaca083ccd3b517f7d676a58220aa33b37d))
* **ui:** 商业版套餐购买不要求设置支付方式 ([e971145](https://github.com/certd/certd/commit/e9711456860d7db40914a0c295534f7bca56f83c))
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
### Bug Fixes
* **plugin-service:** 修复同步自己开发的云端插件不显示已安装状态问题 ([dac94b6](https://github.com/certd/certd/commit/dac94b64f0ceb7176e2cf07ce220be0aa5ed98d2))
* **plugin:** 修复插件配置保存与加载的相关问题 ([bd94334](https://github.com/certd/certd/commit/bd9433478a43d5d64ce6370386d0fe0aaff07d2e))
### Performance Improvements
* **plugin:** add Fastly custom TLS certificates integration ([#797](https://github.com/certd/certd/issues/797)) [@szhu25](https://github.com/szhu25) ([2a2e963](https://github.com/certd/certd/commit/2a2e963914c4dfc192cbe0a4bbb9c115e626da01))
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
### Bug Fixes
+2 -2
View File
@@ -13,7 +13,7 @@
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **主机-复制到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 |
| 1.| **主机-复制证书到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 |
| 2.| **主机-执行远程主机脚本命令** | 可以执行重启nginx等操作让证书生效 |
| 3.| **IIS-部署到IIS站点** | |
| 4.| **上传证书到对象存储OSS** | 支持阿里云OSS、腾讯云COS、七牛云KODO、S3、MinIO、FTP、SFTP |
@@ -51,7 +51,7 @@
| 24.| **易盾-部署到易盾RCDN** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 25.| **雨云-更新证书** | app.rainyun.com |
| 26.| **又拍云-部署证书到CDN/USS** | 支持又拍云CDN,又拍云云存储USS |
| 27.| **网宿-更新证书** | 网宿证书自动更新 |
| 27.| **网宿-更新证书** | 网宿证书自动更新,注意:仅支持普通CDN证书更新,CDN pro的证书请安装在线插件“网宿-CDNPro更新证书” |
| 28.| **西数-部署到虚拟主机** | 西部数码部署证书到虚拟主机 |
| 29.| **我爱云-部署证书到我爱云CDN** | 部署证书到我爱云CDN |
| 30.| **Zenlayer-刷新证书** | 刷新Zenlayer CDN证书 |
+2 -2
View File
@@ -5,8 +5,8 @@
`Certd`默认启用https,监听7002端口
如果你想关闭https,或者修改端口,可以在环境变量中配置
```shell
CERTD_HTTPS_ENABLE=true
CERTD_HTTPS_port=7002
certd_https_enabled=true
certd_https_port=7002
```
+1 -1
View File
@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.44.3"
"version": "1.45.3"
}
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/publishlab/node-acme-client/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/acme-client
## [1.45.2](https://github.com/publishlab/node-acme-client/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/acme-client
## [1.45.1](https://github.com/publishlab/node-acme-client/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/acme-client
# [1.45.0](https://github.com/publishlab/node-acme-client/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
* 支持google persist校验方式 ([97d2b50](https://github.com/publishlab/node-acme-client/commit/97d2b5007409ec79ea864d437c473a2f9b5dfae4))
## [1.44.4](https://github.com/publishlab/node-acme-client/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/acme-client
## [1.44.3](https://github.com/publishlab/node-acme-client/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/acme-client
+4 -3
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.44.3",
"version": "1.45.3",
"type": "module",
"module": "./dist/index.js",
"main": "./dist/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.44.3",
"@certd/basic": "^1.45.3",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.9.0",
@@ -27,6 +27,7 @@
"https-proxy-agent": "^7.0.5",
"lodash-es": "^4.17.21",
"node-forge": "^1.3.1",
"psl": "^1.15.0",
"punycode.js": "^2.3.1"
},
"devDependencies": {
@@ -76,5 +77,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
@@ -0,0 +1,21 @@
// @ts-ignore
import psl from "psl";
export function getDnsPersistIssuer(directoryUrl?: string) {
if (!directoryUrl) {
return "letsencrypt.org";
}
let directory: URL;
try {
directory = new URL(directoryUrl);
} catch {
throw new Error(`ACME Directory URL无效,无法生成DNS持久验证Issuer:${directoryUrl}`);
}
const issuer = psl.get(directory.hostname);
if (!issuer) {
throw new Error(`无法从ACME Directory URL获取可注册主域名:${directory.hostname}`);
}
return issuer;
}
+1
View File
@@ -79,3 +79,4 @@ export * from './verify.js'
export * from './error.js'
export * from './util.js'
export * from './dns-persist.js'
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/basic
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Performance Improvements
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/basic
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
* **webhook:** 修复模版转义换行符,避免手动上传的证书报错 ([2735feb](https://github.com/certd/certd/commit/2735feb3302f8308708dafc8b295adcd4b91d7f0))
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/basic
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/basic
+1 -1
View File
@@ -1 +1 @@
23:46
10:19
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.44.3",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -54,5 +54,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+1
View File
@@ -59,5 +59,6 @@ export const utils = {
locker,
mitter,
amount: amountUtils,
//废弃
request,
};
@@ -1,5 +1,5 @@
import { expect } from "chai";
import { createAgent, createAxiosService, getGlobalAgents, HttpClient, isNoProxyMatched, setGlobalHeaders, setGlobalProxy } from "./util.request.js";
import { createAgent, createAxiosService, getCookie, getCookies, getGlobalAgents, HttpClient, isNoProxyMatched, setGlobalHeaders, setGlobalProxy } from "./util.request.js";
import { ILogger } from "./util.log.js";
const testLogger = {
@@ -167,9 +167,40 @@ describe("util.request", () => {
expect(isNoProxyMatched("192.168.*", { hostname: "192.169.1.10", port: "" })).to.equal(false);
expect(isNoProxyMatched("[::1]", { hostname: "::1", port: "" })).to.equal(true);
expect(isNoProxyMatched("[::1]:8443", { hostname: "::1", port: "8443" })).to.equal(true);
expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "8443" })).to.equal(true);
expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "443" })).to.equal(false);
expect(isNoProxyMatched("127.0.0.1", { hostname: "127.0.0.2", port: "" })).to.equal(false);
expect(isNoProxyMatched(".example.com", { hostname: "example.org", port: "" })).to.equal(false);
});
it("should parse cookies from array set-cookie header", () => {
const response = {
headers: {
"set-cookie": ["JSESSIONID=abc123; Path=/; HttpOnly", "token=xyz=789; Expires=Wed, 21 Oct 2026 07:28:00 GMT"],
},
};
expect(getCookies(response)).to.deep.equal([
{ name: "JSESSIONID", value: "abc123" },
{ name: "token", value: "xyz=789" },
]);
expect(getCookie(response, "JSESSIONID")).to.equal("abc123");
expect(getCookie(response, "token")).to.equal("xyz=789");
expect(getCookie(response, "not-exists")).to.equal(null);
});
it("should parse cookies from string set-cookie header", () => {
const response = {
headers: {
"set-cookie": "JSESSIONID=abc123; Path=/",
},
};
expect(getCookies(response)).to.deep.equal([{ name: "JSESSIONID", value: "abc123" }]);
});
it("should return empty cookies when set-cookie header is missing", () => {
expect(getCookies({ headers: {} })).to.deep.equal([]);
expect(getCookies({})).to.deep.equal([]);
expect(getCookies(undefined)).to.deep.equal([]);
});
});
+61 -10
View File
@@ -1,4 +1,4 @@
import axios, { AxiosHeaders, AxiosRequestConfig } from "axios";
import axios, { AxiosHeaders, AxiosRequestConfig, AxiosResponse } from "axios";
import { ILogger, logger } from "./util.log.js";
import { HttpProxyAgent } from "http-proxy-agent";
import { HttpsProxyAgent } from "https-proxy-agent";
@@ -118,7 +118,7 @@ export function getGlobalHeaders() {
/**
* @description 创建请求实例
*/
export function createAxiosService({ logger }: { logger: ILogger }) {
export function createAxiosService({ logger }: { logger: ILogger }): HttpClient {
// 创建一个 axios 实例
const service = axios.create();
@@ -344,14 +344,24 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
return Promise.reject(err);
}
);
return service;
return {
...service,
request: service.request,
getCookies,
getCookie,
download,
};
}
//@ts-ignore
export const http = createAxiosService({ logger }) as HttpClient;
//默认返回 response.data 。 可以通过 returnOriginRes 来返回原始响应。
export type HttpClientResponse<R> = any;
export type HttpRequestConfig<D = any> = {
skipSslVerify?: boolean;
skipCheckRes?: boolean;
skipCheckRes?: boolean; //非标参数,不需要让用户知道
logReq?: boolean;
logParams?: boolean;
logRes?: boolean;
@@ -359,8 +369,18 @@ export type HttpRequestConfig<D = any> = {
httpProxy?: string;
returnOriginRes?: boolean;
} & AxiosRequestConfig<D>;
export type DownloadReq = {
http: HttpClient;
config: HttpRequestConfig;
savePath: string;
logger: ILogger;
};
export type HttpClient = {
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;
getCookies: (response: AxiosResponse) => { name: string; value: string }[];
getCookie: (response: AxiosResponse, name: string) => string | null;
download: (config: DownloadReq) => Promise<boolean>;
};
// const http_proxy_backup = process.env.HTTP_PROXY || process.env.http_proxy;
@@ -540,7 +560,7 @@ function splitNoProxyRules(noProxy?: string) {
.filter(Boolean);
}
export async function download(req: { http: HttpClient; config: HttpRequestConfig; savePath: string; logger: ILogger }) {
export async function download(req: DownloadReq): Promise<boolean> {
const { http, config, savePath, logger } = req;
return safePromise((resolve, reject) => {
http
@@ -581,13 +601,44 @@ export async function download(req: { http: HttpClient; config: HttpRequestConfi
});
}
/**
* 解析响应头中的 set-cookie
*
* Node 环境下 set-cookie 是字符串数组,每个元素是一个完整 Cookie:
* `name=value; Path=/; HttpOnly; Expires=Wed, 21 Oct 2026 07:28:00 GMT`。
* 只取分号前的 name=value,避免把 Path、Expires 等属性误解析成 Cookie;
* value 本身可能包含 "="(如 base64),所以按第一个 "=" 切分。
*/
export function getCookies(response: any): { name: string; value: string }[] {
const setCookie = response?.headers?.["set-cookie"];
if (!setCookie) {
return [];
}
const cookieList: string[] = Array.isArray(setCookie) ? setCookie : [setCookie];
const cookies: { name: string; value: string }[] = [];
for (const cookieItem of cookieList) {
const cookiePair = String(cookieItem).split(";")[0].trim();
if (!cookiePair) {
continue;
}
const equalIndex = cookiePair.indexOf("=");
if (equalIndex <= 0) {
continue;
}
const name = cookiePair.substring(0, equalIndex).trim();
const value = cookiePair.substring(equalIndex + 1).trim();
cookies.push({ name, value });
}
return cookies;
}
export function getCookie(response: any, name: string) {
const cookies = response.headers["set-cookie"];
//根据name 返回对应的cookie
const found = cookies.find((cookie: any) => cookie.includes(name));
const cookies = getCookies(response);
const found = cookies.find((cookie: any) => cookie.name === name);
if (!found) {
return null;
}
const cookie = found.split(";")[0];
return cookie.substring(cookie.indexOf("=") + 1);
return found.value;
}
@@ -1,6 +1,21 @@
import dayjs from "dayjs";
export const stringUtils = {
replaceTemplate(target: string, body: Record<string, unknown>, urlEncode = false) {
let bodyStr = target;
for (const key of Object.keys(body)) {
const rawValue = body[key] == null ? "" : String(body[key]);
let value = urlEncode ? encodeURIComponent(rawValue) : rawValue;
value = value.replace(/[\u0000-\u001f]/g, char => {
const escapes: Record<string, string> = { "\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r", "\t": "\\t" };
return escapes[char] ?? `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
});
bodyStr = bodyStr.replaceAll(`\${${key}}`, value);
bodyStr = bodyStr.replaceAll(`{${key}}`, value);
}
return bodyStr;
},
maxLength(str?: string, length = 100) {
if (str) {
return str.length > length ? str.slice(0, length) + "..." : str;
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/pipeline
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Performance Improvements
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Performance Improvements
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
* 缺失运行时嵌套依赖时添加清除缓存提示 ([2d0b85b](https://github.com/certd/certd/commit/2d0b85b3054fc1aa82b3ab5db34a2a56308f71f2))
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/pipeline
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/pipeline
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.44.3",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -21,8 +21,8 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/basic": "^1.44.3",
"@certd/plus-core": "^1.44.3",
"@certd/basic": "^1.45.3",
"@certd/plus-core": "^1.45.3",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.2.2"
@@ -51,5 +51,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+3
View File
@@ -19,6 +19,7 @@ export type AccessInputDefine = FormItemProps & {
encrypt?: boolean;
};
export type AccessDefine = Registrable & {
i18n?: Record<string, any>;
icon?: string;
subtype?: string;
dependPlugins?: Record<string, string>;
@@ -48,6 +49,7 @@ export type AccessContext = {
export abstract class BaseAccess implements IAccess {
ctx!: AccessContext;
logger!: ILogger;
async importRuntime(specifier: string) {
return await getRuntimeDepsService().importRuntime(specifier, this.ctx.logger);
@@ -55,6 +57,7 @@ export abstract class BaseAccess implements IAccess {
async setCtx(ctx: AccessContext) {
this.ctx = ctx;
this.logger = ctx.logger;
}
async onRequest(req: AccessRequestHandleReq) {
@@ -44,6 +44,7 @@ export type NotificationDefine = Registrable & {
input?: {
[key: string]: NotificationInputDefine;
};
i18n?: Record<string, any>;
};
export type NotificationInstanceConfig = {
+2
View File
@@ -43,6 +43,7 @@ export type TaskInputDefine = {
} & FormItemProps;
export type PluginDefine = Registrable & {
i18n?: Record<string, any>;
default?: any;
group?: string;
icon?: string;
@@ -97,6 +98,7 @@ export type CertTargetItem = {
export type PipelineEvent<T = any> = {
cert: T;
certPassword?: string;
runnableId: string;
pipelineId: number;
};
@@ -64,6 +64,28 @@ describe("RuntimeDepsService", () => {
assert.equal(mod.value, 42);
});
it("logs a cache clearing hint when a nested runtime dependency is missing", async () => {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-missing-nested-"));
const packageDir = path.join(rootDir, "node_modules", "runtime-only");
fs.mkdirSync(packageDir, { recursive: true });
fs.writeFileSync(path.join(rootDir, "package.json"), JSON.stringify({ name: "runtime-root", type: "module" }), "utf8");
fs.writeFileSync(path.join(packageDir, "package.json"), JSON.stringify({ name: "runtime-only", main: "index.js" }), "utf8");
fs.writeFileSync(path.join(packageDir, "index.js"), "module.exports = require('missing-nested');\n", "utf8");
const logs: string[] = [];
const logger = {
info() {},
warn() {},
error(message: string, ...args: any[]) {
logs.push([message, ...args.map(item => item?.message || String(item))].join(" "));
},
};
const service = new RuntimeDepsService({ rootDir }, null);
await assert.rejects(() => service.importRuntime("runtime-only", logger));
assert.ok(logs.some(message => message.includes("Cannot find module 'missing-nested'")));
assert.ok(logs.some(message => message.includes("插件管理页面->清除缓存依赖->重启certd")));
});
it("installs configured lazy dependency when import target is missing", async () => {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-"));
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "lazy-pkg": "^1.2.3" } }, null);
+11 -1
View File
@@ -251,6 +251,7 @@ export class RuntimeDepsService {
}
let resolved: string;
let fromRuntimeDeps = false;
try {
//尝试加载本项目依赖
resolved = this.resolveProjectSpecifier(specifier).resolved;
@@ -258,13 +259,22 @@ export class RuntimeDepsService {
if (!this.isModuleNotFoundError(error)) {
throw error;
}
fromRuntimeDeps = true;
resolved = await this.resolveLazyOrInstallSpecifier(specifier, logger);
}
if (!resolved) {
throw new Error(`依赖未安装成功: ${specifier}`);
}
return await import(pathToFileURL(resolved).href);
try {
return await import(pathToFileURL(resolved).href);
} catch (error: any) {
if (fromRuntimeDeps && this.isModuleNotFoundError(error)) {
logger.error?.("动态依赖加载失败:", error);
logger.error?.("动态依赖加载失败,请前往插件管理页面->清除缓存依赖->重启certd后重试");
}
throw error;
}
}
//动态获取模块解析
+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.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-huawei
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-huawei
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/lib-huawei
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/lib-huawei
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/lib-huawei
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**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.44.3",
"version": "1.45.3",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -31,5 +31,5 @@
"prettier": "3.3.3",
"tslib": "^2.8.1"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+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.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-iframe
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-iframe
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/lib-iframe
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/lib-iframe
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/lib-iframe
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/lib-iframe
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.44.3",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -37,5 +37,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+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.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/jdcloud
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/jdcloud
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/jdcloud
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/jdcloud
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/jdcloud
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/jdcloud
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.44.3",
"version": "1.45.3",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -63,5 +63,5 @@
"fetch"
]
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+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.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-k8s
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-k8s
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/lib-k8s
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/lib-k8s
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/lib-k8s
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/lib-k8s
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.44.3",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -21,7 +21,7 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/basic": "^1.44.3",
"@certd/basic": "^1.45.3",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+26
View File
@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-server
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-server
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Performance Improvements
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Features
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
### Bug Fixes
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/lib-server
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.44.3",
"version": "1.45.3",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -29,11 +29,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.44.3",
"@certd/basic": "^1.44.3",
"@certd/pipeline": "^1.44.3",
"@certd/plugin-lib": "^1.44.3",
"@certd/plus-core": "^1.44.3",
"@certd/acme-client": "^1.45.3",
"@certd/basic": "^1.45.3",
"@certd/pipeline": "^1.45.3",
"@certd/plugin-lib": "^1.45.3",
"@certd/plus-core": "^1.45.3",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",
@@ -69,5 +69,5 @@
"typeorm": "^0.3.20",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
@@ -133,12 +133,12 @@ export class SysPrivateSettings extends BaseSettings {
// 内置项不在此配置 Directory URL:其端点由运行时按加密算法(pkType)通过 acme.getDirectoryUrl 获取,
// 例如 ZeroSSL、SSL.com 的 RSA 与 EC 证书使用不同的端点,写死单一 URL 会导致 EC 申请走错端点。
customAcmeProviders?: CustomAcmeProvider[] = [
{ sslProvider: "letsencrypt", title: "Let's Encrypt", directoryUrl: "", needEAB: false, builtIn: true },
{ sslProvider: "letsencrypt_staging", title: "Let's Encrypt测试环境", directoryUrl: "", needEAB: false, builtIn: true },
{ sslProvider: "letsencrypt", title: "Let's Encrypt(支持7天IP证书)", directoryUrl: "", needEAB: false, builtIn: true },
{ sslProvider: "google", title: "Google", directoryUrl: "", needEAB: true, builtIn: true },
{ sslProvider: "zerossl", title: "ZeroSSL", directoryUrl: "", needEAB: true, builtIn: true },
{ sslProvider: "litessl", title: "litessl", directoryUrl: "", needEAB: true, builtIn: true },
{ sslProvider: "litessl", title: "Litessl", directoryUrl: "", needEAB: true, builtIn: true },
{ sslProvider: "sslcom", title: "SSL.com", directoryUrl: "", needEAB: true, builtIn: true },
{ sslProvider: "letsencrypt_staging", title: "Let's Encrypt测试环境(只能生成测试证书)", directoryUrl: "", needEAB: false, builtIn: true },
];
removeSecret() {
@@ -174,11 +174,11 @@ export class AccessService extends BaseService<AccessEntity> {
throw new ValidateException("userId不能为空");
}
if (userId !== entity.userId) {
throw new PermissionException("您对该Access授权无访问权限");
throw new PermissionException(`您对该Access授权${entity.name}(${entity.id})无访问权限`);
}
}
if (projectId != null && projectId !== entity.projectId) {
throw new PermissionException("您对该Access授权无访问权限");
throw new PermissionException(`您对该Access授权${entity.name}(${entity.id})无访问权限`);
}
// const access = accessRegistry.get(entity.type);
@@ -26,6 +26,7 @@ export type AddonDefine = Registrable & {
};
showTest?: boolean;
icon?: string;
i18n?: Record<string, any>;
};
export type AddonInstanceConfig = {
@@ -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.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/midway-flyway-js
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/midway-flyway-js
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.44.3",
"version": "1.45.3",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -52,5 +52,5 @@
"typeorm": "^0.3.20",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+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.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/plugin-cert
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/plugin-cert
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/plugin-cert
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/plugin-cert
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/plugin-cert
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/plugin-cert
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.44.3",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/plugin-lib": "^1.44.3"
"@certd/plugin-lib": "^1.45.3"
},
"devDependencies": {
"@types/chai": "^4.3.12",
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/plugin-lib
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/plugin-lib
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Performance Improvements
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/plugin-lib
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
**Note:** Version bump only for package @certd/plugin-lib
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
**Note:** Version bump only for package @certd/plugin-lib
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.44.3",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,9 +17,9 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/acme-client": "^1.44.3",
"@certd/basic": "^1.44.3",
"@certd/pipeline": "^1.44.3",
"@certd/acme-client": "^1.45.3",
"@certd/basic": "^1.45.3",
"@certd/pipeline": "^1.45.3",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21",
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "67f3daae625ba734e7e390dd84acc9e3bc1930bc"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
@@ -3,6 +3,7 @@ import { IAccess, IAccessService, IServiceGetter, PageRes, PageSearch, Registrab
export type DnsProviderDefine = Registrable & {
accessType: string;
i18n?: Record<string, any>;
icon?: string;
dependPlugins?: Record<string, string>;
dependPackages?: Record<string, string>;
+54
View File
@@ -3,6 +3,60 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
### Bug Fixes
* 修复修改密码时校验表单字段的错误 ([28e4f70](https://github.com/certd/certd/commit/28e4f70e494f7c56202a8ca4708718c836fff702))
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Bug Fixes
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
### Performance Improvements
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Bug Fixes
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
### Performance Improvements
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Features
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
### Performance Improvements
* **monitor:** 站点证书监控支持手动选中触发检查 ([f60e63f](https://github.com/certd/certd/commit/f60e63fbaf00460d5050cd7705bd75d57dec202d))
* **open-api:** openkey增加用途备注 ([bbeaf61](https://github.com/certd/certd/commit/bbeaf617d19750515f6eca51863e74e23bc1de50))
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
### Bug Fixes
* 修复添加步骤会修改所有步骤标题的bug ([51e83e4](https://github.com/certd/certd/commit/51e83e4c38f7f319314fdc38ef33cf5720b2dde0))
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
### Performance Improvements
* 新增余额管理,管理员充值功能 ([bded368](https://github.com/certd/certd/commit/bded368cbb8974b6937ae260e448c25fae5c33ad))
* **ui:** 商业版套餐购买不要求设置支付方式 ([e971145](https://github.com/certd/certd/commit/e9711456860d7db40914a0c295534f7bca56f83c))
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
### Bug Fixes
@@ -13,7 +13,7 @@ const { packages } = getPackagesSync(process.cwd());
const tailwindPackages = [];
packages.forEach((pkg) => {
packages.forEach(pkg => {
// apps目录下和 @vben-core/tailwind-ui 包需要使用到 tailwindcss ui
// if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) {
tailwindPackages.push(pkg.dir);
@@ -25,96 +25,96 @@ const shadcnUiColors = {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
hover: "hsl(var(--accent-hover))",
lighter: "has(val(--accent-lighter))"
lighter: "has(val(--accent-lighter))",
},
background: {
deep: "hsl(var(--background-deep))",
DEFAULT: "hsl(var(--background))"
DEFAULT: "hsl(var(--background))",
},
border: {
DEFAULT: "hsl(var(--border))"
DEFAULT: "hsl(var(--border))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))"
foreground: "hsl(var(--card-foreground))",
},
destructive: {
...createColorsPalette("destructive"),
DEFAULT: "hsl(var(--destructive))"
DEFAULT: "hsl(var(--destructive))",
},
foreground: {
DEFAULT: "hsl(var(--foreground))"
DEFAULT: "hsl(var(--foreground))",
},
input: {
background: "hsl(var(--input-background))",
DEFAULT: "hsl(var(--input))"
DEFAULT: "hsl(var(--input))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))"
foreground: "hsl(var(--muted-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))"
foreground: "hsl(var(--popover-foreground))",
},
primary: {
...createColorsPalette("primary"),
DEFAULT: "hsl(var(--primary))"
DEFAULT: "hsl(var(--primary))",
},
ring: "hsl(var(--ring))",
secondary: {
DEFAULT: "hsl(var(--secondary))",
desc: "hsl(var(--secondary-desc))",
foreground: "hsl(var(--secondary-foreground))"
}
foreground: "hsl(var(--secondary-foreground))",
},
};
const customColors = {
green: {
...createColorsPalette("green"),
foreground: "hsl(var(--success-foreground))"
foreground: "hsl(var(--success-foreground))",
},
header: {
DEFAULT: "hsl(var(--header))"
DEFAULT: "hsl(var(--header))",
},
heavy: {
DEFAULT: "hsl(var(--heavy))",
foreground: "hsl(var(--heavy-foreground))"
foreground: "hsl(var(--heavy-foreground))",
},
main: {
DEFAULT: "hsl(var(--main))"
DEFAULT: "hsl(var(--main))",
},
overlay: {
content: "hsl(var(--overlay-content))",
DEFAULT: "hsl(var(--overlay))"
DEFAULT: "hsl(var(--overlay))",
},
red: {
...createColorsPalette("red"),
foreground: "hsl(var(--destructive-foreground))"
foreground: "hsl(var(--destructive-foreground))",
},
sidebar: {
deep: "hsl(var(--sidebar-deep))",
DEFAULT: "hsl(var(--sidebar))"
DEFAULT: "hsl(var(--sidebar))",
},
success: {
...createColorsPalette("success"),
DEFAULT: "hsl(var(--success))"
DEFAULT: "hsl(var(--success))",
},
warning: {
...createColorsPalette("warning"),
DEFAULT: "hsl(var(--warning))"
DEFAULT: "hsl(var(--warning))",
},
yellow: {
...createColorsPalette("yellow"),
foreground: "hsl(var(--warning-foreground))"
}
foreground: "hsl(var(--warning-foreground))",
},
};
export default {
content: ["./index.html", ...tailwindPackages.map((item) => path.join(item, "src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}"))],
content: ["./index.html", ...tailwindPackages.map(item => path.join(item, "src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}"))],
darkMode: "selector",
plugins: [animate, typographyPlugin, addDynamicIconSelectors(), enterAnimationPlugin],
prefix: "",
@@ -123,8 +123,8 @@ export default {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px"
}
"2xl": "1400px",
},
},
extend: {
animation: {
@@ -132,64 +132,67 @@ export default {
"accordion-up": "accordion-up 0.2s ease-out",
"collapsible-down": "collapsible-down 0.2s ease-in-out",
"collapsible-up": "collapsible-up 0.2s ease-in-out",
float: "float 5s linear 0ms infinite"
float: "float 5s linear 0ms infinite",
},
flex: {
2: "2 1 0%",
},
animationDuration: {
2000: "2000ms",
3000: "3000ms"
3000: "3000ms",
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
xl: "calc(var(--radius) + 4px)"
xl: "calc(var(--radius) + 4px)",
},
boxShadow: {
float: `0 6px 16px 0 rgb(0 0 0 / 8%),
0 3px 6px -4px rgb(0 0 0 / 12%),
0 9px 28px 8px rgb(0 0 0 / 5%)`
0 9px 28px 8px rgb(0 0 0 / 5%)`,
},
colors: {
...customColors,
...shadcnUiColors
...shadcnUiColors,
},
fontFamily: {
sans: [
"var(--font-family)"
"var(--font-family)",
// ...defaultTheme.fontFamily.sans
]
],
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" }
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" }
to: { height: "0" },
},
"collapsible-down": {
from: { height: "0" },
to: { height: "var(--radix-collapsible-content-height)" }
to: { height: "var(--radix-collapsible-content-height)" },
},
"collapsible-up": {
from: { height: "var(--radix-collapsible-content-height)" },
to: { height: "0" }
to: { height: "0" },
},
float: {
"0%": { transform: "translateY(0)" },
"50%": { transform: "translateY(-20px)" },
"100%": { transform: "translateY(0)" }
}
"100%": { transform: "translateY(0)" },
},
},
zIndex: {
100: "100",
1000: "1000"
}
}
1000: "1000",
},
},
},
safelist: ["dark"]
safelist: ["dark"],
};
function createColorsPalette(name) {
@@ -249,6 +252,6 @@ function createColorsPalette(name) {
// 主色文本激活态
"text-active": `hsl(var(--${name}-700))`,
// 主色文本悬浮态
"text-hover": `hsl(var(--${name}-600))`
"text-hover": `hsl(var(--${name}-600))`,
};
}
@@ -10,6 +10,6 @@ export default {
"postcss-import": {},
"postcss-preset-env": {},
tailwindcss: { config },
"tailwindcss/nesting": {}
}
"tailwindcss/nesting": {},
},
};
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.44.3",
"version": "1.45.3",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -34,11 +34,11 @@
"@aws-sdk/s3-request-presigner": "^3.964.0",
"@certd/vue-js-cron-light": "^4.0.14",
"@ctrl/tinycolor": "^4.1.0",
"@fast-crud/editor-code": "^1.28.7",
"@fast-crud/fast-crud": "^1.28.7",
"@fast-crud/fast-extends": "^1.28.7",
"@fast-crud/ui-antdv4": "^1.28.7",
"@fast-crud/ui-interface": "^1.28.7",
"@fast-crud/editor-code": "^1.29.0",
"@fast-crud/fast-crud": "^1.29.0",
"@fast-crud/fast-extends": "^1.29.0",
"@fast-crud/ui-antdv4": "^1.29.0",
"@fast-crud/ui-interface": "^1.29.0",
"@iconify/tailwind": "^1.2.0",
"@iconify/vue": "^4.1.1",
"@manypkg/get-packages": "^2.2.2",
@@ -104,8 +104,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.44.3",
"@certd/pipeline": "^1.44.3",
"@certd/lib-iframe": "^1.45.3",
"@certd/pipeline": "^1.45.3",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -4,6 +4,7 @@ import { errorLog, errorCreate } from "./tools";
import { env } from "/src/utils/util.env";
import { useUserStore } from "/@/store/user";
import { useProjectStore } from "../store/project";
import { i18n } from "/@/locales";
export class CodeError extends Error {
code: number;
@@ -139,6 +140,7 @@ function createRequestFunction(service: any) {
const configDefault: any = {
headers: {
"Content-Type": get(config, "headers.Content-Type", "application/json"),
"Accept-Language": i18n.global.locale.value,
} as any,
timeout: 30000,
baseURL: env.API,
@@ -154,6 +156,10 @@ function createRequestFunction(service: any) {
configDefault.headers.Authorization = token;
}
Object.assign(configDefault, config);
configDefault.headers = {
...configDefault.headers,
"Accept-Language": i18n.global.locale.value,
};
if (!configDefault.params.projectId && projectStore.isEnterprise && !config.url.startsWith("/sys") && !config.url.startsWith("http")) {
configDefault.params.projectId = projectStore.currentProject?.id;
@@ -1,11 +1,11 @@
<template>
<div class="flex captcha-image-input">
<a-input :value="valueRef" :placeholder="t('certd.captcha.inputImageCode')" autocomplete="off" @update:value="onChange">
<a-input :value="valueRef" :placeholder="t('certd.common.captcha.inputImageCode')" autocomplete="off" @update:value="onChange">
<template #prefix>
<fs-icon icon="ion:image-outline"></fs-icon>
</template>
</a-input>
<div class="input-right pointer" :title="t('certd.captcha.refresh')">
<div class="input-right pointer" :title="t('certd.common.captcha.refresh')">
<img class="image-code" :src="imageCodeSrc" @click="resetImageCode" />
</div>
</div>
@@ -4,8 +4,8 @@
<div class="sweep-animation"></div>
<div class="box-content">
<div class="box-icon">✓</div>
<span v-if="modelValue == null" class="status-text">{{ t("certd.captcha.clickToVerify") }}</span>
<span v-else class="status-text">{{ t("certd.captcha.verifySuccess") }}</span>
<span v-if="modelValue == null" class="status-text">{{ t("certd.common.captcha.clickToVerify") }}</span>
<span v-else class="status-text">{{ t("certd.common.captcha.verifySuccess") }}</span>
</div>
</div>
</div>
@@ -58,7 +58,7 @@ function callback(res: { ret: number; ticket: string; randstr: string; errorCode
if (res.errorCode && res.errorCode > 0) {
notification.error({
message: t("certd.captcha.verifyFailed", { message: res.errorMessage || res.errorCode }),
message: t("certd.common.captcha.verifyFailed", { message: res.errorMessage || res.errorCode }),
});
}
@@ -85,13 +85,13 @@ function loadErrorCallback(error: any) {
// errorMessage: "jsload_error",
// });
notification.error({
message: t("certd.captcha.loadFailed", { message: error?.message || error }),
message: t("certd.common.captcha.loadFailed", { message: error?.message || error }),
});
}
async function triggerCaptcha() {
if (!loaded.value) {
notification.error({
message: t("certd.captcha.notLoaded"),
message: t("certd.common.captcha.notLoaded"),
});
return;
}
@@ -1,13 +1,16 @@
<template>
<div class="pem-input">
<FileInput v-bind="fileInput" class="mb-5" type="primary" text="选择文件" @change="onChange" />
<div class="pem-input__dropzone" :class="{ 'pem-input__dropzone--active': isDragging }" @dragenter.prevent="onDragEnter" @dragover.prevent="onDragOver" @dragleave.prevent="onDragLeave" @drop.prevent="onDrop">
<FileInput v-bind="fileInput" class="pem-input__file" type="primary" text="选择文件" @change="onChange" />
<span>或将文件拖拽到此处</span>
</div>
<a-textarea placeholder="或直接粘贴" v-bind="textarea" :value="modelValue" @update:value="emitValue"></a-textarea>
</div>
</template>
<script setup lang="ts">
import { notification } from "ant-design-vue";
import { ref, watch, defineEmits } from "vue";
import { ref, defineEmits } from "vue";
import FileInput from "/@/components/file-input.vue";
const props = defineProps<{
@@ -17,26 +20,57 @@ const props = defineProps<{
}>();
const emit = defineEmits(["update:modelValue"]);
const isDragging = ref(false);
function readFile(file?: File) {
if (!file) {
return;
}
if (file.size > 100 * 1024) {
notification.error({
message: "文件超过100k,请选择正确的文件",
});
return;
}
const fileReader = new FileReader();
fileReader.onload = function (event: ProgressEvent<FileReader>) {
const value = event.target?.result;
if (typeof value === "string") {
emitValue(value);
}
};
fileReader.readAsText(file);
}
function onDragEnter() {
isDragging.value = true;
}
function onDragOver() {
isDragging.value = true;
}
function onDragLeave(event: DragEvent) {
const currentTarget = event.currentTarget as HTMLElement;
const relatedTarget = event.relatedTarget as Node | null;
if (!relatedTarget || !currentTarget.contains(relatedTarget)) {
isDragging.value = false;
}
}
function onDrop(event: DragEvent) {
isDragging.value = false;
readFile(event.dataTransfer?.files[0]);
}
function emitValue(value: string) {
emit("update:modelValue", value);
}
function onChange(e: any) {
const file = e.target.files[0];
const size = file.size;
if (size > 100 * 1024) {
notification.error({
message: "文件超过100k,请选择正确的文件",
});
return;
}
const fileReader = new FileReader();
fileReader.onload = function (e: any) {
const value = e.target.result;
emitValue(value);
};
fileReader.readAsText(file); // 以文本形式读取文件
readFile(e.target.files?.[0]);
}
</script>
@@ -45,5 +79,26 @@ function onChange(e: any) {
display: flex;
flex-direction: column;
gap: 5px;
&__dropzone {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
transition:
border-color 0.2s,
background-color 0.2s;
}
&__dropzone--active {
border-color: #1677ff;
background-color: #e6f4ff;
}
&__file {
margin-bottom: 0;
}
}
</style>
@@ -5,6 +5,7 @@
<script lang="ts">
import { ref } from "vue";
import * as api from "./api";
import { resolvePluginLocale } from "/@/store/plugin";
export default {
name: "DnsProviderSelector",
@@ -21,12 +22,13 @@ export default {
async function onCreate() {
const list = await api.GetList();
const array: any[] = [];
for (let item of list) {
for (const item of list) {
const localizedItem = resolvePluginLocale(item);
array.push({
value: item.name,
label: item.title,
icon: item.icon,
accessType: item.accessType,
value: localizedItem.name,
label: localizedItem.title,
icon: localizedItem.icon,
accessType: localizedItem.accessType,
});
}
options.value = array;
@@ -4,7 +4,7 @@
<!-- <td class="domain">-->
<!-- {{ props.domain }}-->
<!-- </td>-->
<td class="host-record" :title="t('certd.verifyPlan.domainTitle', { domain: props.domain })">
<td class="host-record" :title="t('certd.certdomain.verifyPlan.domainTitle', { domain: props.domain })">
<fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable>
</td>
<td style="text-align: center">CNAME</td>
@@ -17,18 +17,18 @@
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
</a-tooltip>
<a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.verifyPlan.resetStatusTooltip')">
<a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.certdomain.verifyPlan.resetStatusTooltip')">
<fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
</a-tooltip>
</span>
</td>
<td class="center">
<template v-if="cnameRecord.status !== 'valid'">
<a-button type="primary" size="small" :loading="loading" @click="doVerify">{{ t("certd.verifyPlan.clickToValidate") }}</a-button>
<a-button type="primary" size="small" :loading="loading" @click="doVerify">{{ t("certd.certdomain.verifyPlan.clickToValidate") }}</a-button>
<cname-tip :record="cnameRecord"></cname-tip>
</template>
<div v-else class="helper" :title="t('certd.verifyPlan.keepCnameTitle')">{{ t("certd.verifyPlan.keepCname") }}</div>
<div v-else class="helper" :title="t('certd.certdomain.verifyPlan.keepCnameTitle')">{{ t("certd.certdomain.verifyPlan.keepCname") }}</div>
</td>
</tr>
</tbody>
@@ -46,11 +46,11 @@ import { utils } from "/@/utils/index.js";
const { t } = useI18n();
const statusDict = dict({
data: [
{ label: t("certd.verifyPlan.status.pendingCname"), value: "cname", color: "warning" },
{ label: t("certd.verifyPlan.status.validating"), value: "validating", color: "blue" },
{ label: t("certd.verifyPlan.status.valid"), value: "valid", color: "green" },
{ label: t("certd.verifyPlan.status.failed"), value: "failed", color: "red" },
{ label: t("certd.verifyPlan.status.timeout"), value: "timeout", color: "red" },
{ label: t("certd.certdomain.verifyPlan.status.pendingCname"), value: "cname", color: "warning" },
{ label: t("certd.certdomain.verifyPlan.status.validating"), value: "validating", color: "blue" },
{ label: t("certd.certdomain.verifyPlan.status.valid"), value: "valid", color: "green" },
{ label: t("certd.certdomain.verifyPlan.status.failed"), value: "failed", color: "red" },
{ label: t("certd.certdomain.verifyPlan.status.timeout"), value: "timeout", color: "red" },
],
});
@@ -129,8 +129,8 @@ async function doVerify() {
async function resetStatus() {
Modal.confirm({
title: t("certd.verifyPlan.resetStatus"),
content: t("certd.verifyPlan.confirmResetStatus"),
title: t("certd.certdomain.verifyPlan.resetStatus"),
content: t("certd.certdomain.verifyPlan.confirmResetStatus"),
onOk: async () => {
await api.ResetStatus(cnameRecord.value.id);
await loadRecord();
@@ -2,17 +2,17 @@
<a-tooltip :overlay-style="{ maxWidth: '400px' }">
<template #title>
<div>
<div>{{ t("certd.verifyPlan.cnameTip.intro") }}</div>
<div>{{ t("certd.verifyPlan.cnameTip.step1", { domain: record.domain }) }}</div>
<div>{{ t("certd.verifyPlan.cnameTip.step2") }}</div>
<div>{{ t("certd.verifyPlan.cnameTip.step3", { value: record.recordValue }) }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.intro") }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step1", { domain: record.domain }) }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step2") }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step3", { value: record.recordValue }) }}</div>
<div>
{{ t("certd.verifyPlan.cnameTip.step4") }}
{{ t("certd.certdomain.verifyPlan.cnameTip.step4") }}
<fs-copyable :style="{ color: '#52c41a' }" :model-value="nslookupCmd"></fs-copyable>
{{ t("certd.verifyPlan.cnameTip.or") }}
{{ t("certd.certdomain.verifyPlan.cnameTip.or") }}
<fs-copyable :style="{ color: '#52c41a' }" :model-value="digCmd"></fs-copyable>
</div>
<div>{{ t("certd.verifyPlan.cnameTip.step5") }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step5") }}</div>
</div>
</template>
<fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon>
@@ -2,11 +2,11 @@
<table class="cname-verify-plan">
<thead>
<tr>
<td class="col-host">{{ t("certd.verifyPlan.hostRecord") }}</td>
<td class="col-type center">{{ t("certd.verifyPlan.recordType") }}</td>
<td class="col-value">{{ t("certd.verifyPlan.setCnameRecord") }}</td>
<td class="col-status center">{{ t("certd.status") }}</td>
<td class="col-action center">{{ t("certd.verifyPlan.operation") }}</td>
<td class="col-host">{{ t("certd.certdomain.verifyPlan.hostRecord") }}</td>
<td class="col-type center">{{ t("certd.certdomain.verifyPlan.recordType") }}</td>
<td class="col-value">{{ t("certd.certdomain.verifyPlan.setCnameRecord") }}</td>
<td class="col-status center">{{ t("certd.common.status") }}</td>
<td class="col-action center">{{ t("certd.certdomain.verifyPlan.operation") }}</td>
</tr>
</thead>
<template v-for="key in domains" :key="key">
@@ -2,10 +2,10 @@
<table class="http-verify-plan">
<thead>
<tr>
<td style="width: 160px">{{ t("certd.verifyPlan.websiteDomain") }}</td>
<td style="width: 100px; text-align: center">{{ t("certd.verifyPlan.uploadMethod") }}</td>
<td style="width: 150px">{{ t("certd.verifyPlan.uploadAccess") }}</td>
<td style="width: 200px">{{ t("certd.verifyPlan.websiteRootPath") }}</td>
<td style="width: 160px">{{ t("certd.certdomain.verifyPlan.websiteDomain") }}</td>
<td style="width: 100px; text-align: center">{{ t("certd.certdomain.verifyPlan.uploadMethod") }}</td>
<td style="width: 150px">{{ t("certd.certdomain.verifyPlan.uploadAccess") }}</td>
<td style="width: 200px">{{ t("certd.certdomain.verifyPlan.websiteRootPath") }}</td>
</tr>
</thead>
<tbody v-if="records" class="http-record-body">
@@ -21,7 +21,7 @@
<access-selector v-model="item.httpUploaderAccess" :type="item.httpUploaderType" @change="onRecordChange"></access-selector>
</td>
<td>
<a-input v-model:value="item.httpUploadRootDir" :placeholder="t('certd.verifyPlan.websiteRootPlaceholder')" @change="onRecordChange"></a-input>
<a-input v-model:value="item.httpUploadRootDir" :placeholder="t('certd.certdomain.verifyPlan.websiteRootPlaceholder')" @change="onRecordChange"></a-input>
</td>
</tr>
</template>
@@ -5,7 +5,7 @@
<div class="plan-box bg-white dark:bg-neutral-700">
<div class="fullscreen-button pointer flex-center" @click="fullscreen = !fullscreen">
<span v-if="!fullscreen" style="font-size: 10px" class="flex-center">
{{ t("certd.verifyPlan.expandTip") }}
{{ t("certd.certdomain.verifyPlan.expandTip") }}
<fs-icon icon="ion:arrow-forward-outline"></fs-icon>
</span>
<fs-icon :icon="fullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'"></fs-icon>
@@ -13,9 +13,9 @@
<table class="plan-table">
<thead>
<tr>
<th style="min-width: 100px">{{ t("certd.verifyPlan.mainDomain") }}</th>
<th>{{ t("certd.verifyPlan.challengeType") }}</th>
<th>{{ t("certd.verifyPlan.challengePlan") }}</th>
<th style="min-width: 100px">{{ t("certd.certdomain.verifyPlan.mainDomain") }}</th>
<th>{{ t("certd.certdomain.verifyPlan.challengeType") }}</th>
<th>{{ t("certd.certdomain.verifyPlan.challengePlan") }}</th>
</tr>
</thead>
<tbody>
@@ -30,13 +30,13 @@
<div class="plan">
<div v-if="item.type === 'dns'" class="plan-dns">
<div class="form-item">
<span class="label">{{ t("certd.verifyPlan.dnsType") }}:</span>
<span class="label">{{ t("certd.certdomain.verifyPlan.dnsType") }}:</span>
<span class="input">
<fs-dict-select
v-model:value="item.dnsProviderType"
size="small"
:dict="dnsProviderTypeDict"
:placeholder="t('certd.verifyPlan.dnsProvider')"
:placeholder="t('certd.certdomain.verifyPlan.dnsProvider')"
@change="onPlanChanged"
@selected-change="onDnsProviderChange(item, $event)"
></fs-dict-select>
@@ -44,13 +44,13 @@
</div>
<a-divider type="vertical" />
<div class="form-item">
<span class="label">{{ t("certd.verifyPlan.dnsAccess") }}:</span>
<span class="label">{{ t("certd.certdomain.verifyPlan.dnsAccess") }}:</span>
<span class="input">
<access-selector
v-model="item.dnsProviderAccessId"
size="small"
:type="item.dnsProviderAccessType || item.dnsProviderType"
:placeholder="t('certd.verifyPlan.pleaseSelect')"
:placeholder="t('certd.certdomain.verifyPlan.pleaseSelect')"
@change="onPlanChanged"
></access-selector>
</span>
@@ -70,7 +70,7 @@
</div>
<div v-if="item.type === 'http'" class="plan-http">
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
<div class="helper">{{ t("certd.verifyPlan.httpHelper") }}</div>
<div class="helper">{{ t("certd.certdomain.verifyPlan.httpHelper") }}</div>
</div>
</div>
</td>
@@ -105,7 +105,7 @@ const { t } = useI18n();
const challengeTypeOptions = ref<any[]>([
{
label: t("certd.verifyPlan.dnsChallenge"),
label: t("certd.certdomain.verifyPlan.dnsChallenge"),
value: "dns",
},
{
@@ -113,11 +113,11 @@ const challengeTypeOptions = ref<any[]>([
value: "dns-persist",
},
{
label: t("certd.verifyPlan.cnameChallenge"),
label: t("certd.certdomain.verifyPlan.cnameChallenge"),
value: "cname",
},
{
label: t("certd.verifyPlan.httpChallenge"),
label: t("certd.certdomain.verifyPlan.httpChallenge"),
value: "http",
},
]);
@@ -14,7 +14,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const subDomain of subDomains) {
const plan = value[domain].cnameVerifyPlan[subDomain];
if (plan.status !== "valid") {
throw new Error($t("certd.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
}
}
}
@@ -23,7 +23,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const item of domains) {
//如果有通配符域名则不允许使用http校验
if (item.startsWith("*.")) {
throw new Error($t("certd.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
throw new Error($t("certd.certdomain.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
}
}
@@ -32,19 +32,19 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const subDomain of subDomains) {
const plan = value[domain].httpVerifyPlan[subDomain];
if (!plan.httpUploaderType) {
throw new Error($t("certd.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
}
if (!plan.httpUploaderAccess) {
throw new Error($t("certd.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
}
if (!plan.httpUploadRootDir) {
throw new Error($t("certd.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
}
}
}
} else if (type === "dns") {
if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) {
throw new Error($t("certd.verifyPlan.errors.dnsProviderRequired", { domain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.dnsProviderRequired", { domain }));
}
} else if (type === "dns-persist") {
const subDomains = Object.keys(value[domain].dnsPersistVerifyPlan || {});
@@ -1,7 +1,7 @@
<template>
<div class="api-test">
<div>
<fs-button :loading="loading" type="primary" :text="t('certd.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
<fs-button :loading="loading" type="primary" :text="t('certd.common.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -64,14 +64,14 @@ const doTest = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.errorWithMessage", { message: err.message });
message.value = t("certd.common.pluginCommon.errorWithMessage", { message: err.message });
},
showErrorNotify: false,
}
);
message.value = t("certd.pluginCommon.testRequestSuccess");
message.value = t("certd.common.pluginCommon.testRequestSuccess");
if (res) {
message.value += t("certd.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
message.value += t("certd.common.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
}
} finally {
loading.value = false;
@@ -42,7 +42,7 @@ function findStepFromPipeline(targetStepId: string) {
const errorRef = ref("");
function getStepIdFromInputKey(inputKey: string) {
if (!inputKey) {
errorRef.value = t("certd.pluginCommon.selectCertFirst");
errorRef.value = t("certd.common.pluginCommon.selectCertFirst");
return;
}
return inputKey.split(".")[1];
@@ -51,7 +51,7 @@ function getDomainFromPipeline(inputKey: string) {
let targetStepId = getStepIdFromInputKey(inputKey);
let certStep = findStepFromPipeline(targetStepId);
if (!certStep) {
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
errorRef.value = t("certd.common.pluginCommon.targetStepNotFound");
return;
}
@@ -60,7 +60,7 @@ function getDomainFromPipeline(inputKey: string) {
targetStepId = getStepIdFromInputKey(firstLevelValue);
certStep = findStepFromPipeline(targetStepId);
if (!certStep) {
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
errorRef.value = t("certd.common.pluginCommon.targetStepNotFound");
return;
}
}
@@ -1,4 +1,4 @@
<template>
<template>
<div class="domain-select">
<div class="flex flex-row">
<a-select
@@ -18,12 +18,12 @@
<template v-if="search">
<div class="flex w-full items-center justify-between flex-wrap" style="padding: 4px 8px">
<div class="flex-1 flex flex-row items-center">
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.pluginCommon.search") }}</fs-button>
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.common.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.common.pluginCommon.search") }}</fs-button>
</div>
<div class="manager flex flex-row items-center">
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.pluginCommon.importDomain") }}</fs-button>
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.pluginCommon.manageDomain") }}</fs-button>
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.common.pluginCommon.importDomain") }}</fs-button>
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.common.pluginCommon.manageDomain") }}</fs-button>
</div>
</div>
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
@@ -49,7 +49,7 @@
</a-select>
<div class="ml-5 flex flex-row items-center">
<fs-button title="复制全部域名" icon="ion:copy-outline" class="domain-select-copy-icon mr-5" @click="copySelectedDomains" />
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -8,6 +8,7 @@ import { usePluginStore } from "/@/store/plugin";
export default {
name: "OutputSelector",
// inheritAttrs: false,
props: {
modelValue: {
type: String,
@@ -30,6 +31,9 @@ export default {
const pluginStore = usePluginStore();
// const attrs = ctx.attrs;
// console.log(attrs);
async function onCreate() {
await pluginStore.init();
options.value = pluginStore.group.getPreStepOutputOptions({
@@ -4,7 +4,7 @@
<a-auto-complete class="remote-auto-complete-input" :filter-option="filterOption" :options="optionsRef" :value="value" v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)">
</a-auto-complete>
<div class="ml-5">
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -96,16 +96,16 @@ const getOptions = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
},
showErrorNotify: false,
}
);
const list = res?.list || res || [];
if (list.length > 0) {
message.value = t("certd.pluginCommon.getDataSuccessSelect");
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
} else {
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
}
optionsRef.value = list;
@@ -55,7 +55,7 @@ async function openDialog() {
saveRemind: false,
},
afterSubmit() {
notification.success({ message: t("certd.operationSuccess") });
notification.success({ message: t("certd.common.operationSuccess") });
},
async doSubmit({ form }: any) {
return await doPluginFormSubmit(form);
@@ -5,12 +5,12 @@
<template #dropdownRender="{ menuNode: menu }">
<template v-if="search">
<div class="flex w-full" style="padding: 4px 8px">
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.searchKeyword')" @keydown.enter="doSearch" />
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.common.pluginCommon.searchKeyword')" @keydown.enter="doSearch" />
<a-button class="ml-2" :loading="loading" type="text" @click="doSearch">
<template #icon>
<search-outlined />
</template>
{{ t("certd.pluginCommon.search") }}
{{ t("certd.common.pluginCommon.search") }}
</a-button>
</div>
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
@@ -26,7 +26,7 @@
</template>
</a-select>
<div class="ml-5 flex flex-row no-wrap">
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<UploadCert v-if="uploadCert" class="ml-5" v-bind="uploadCert" @submit="refreshOptions"></UploadCert>
</div>
</div>
@@ -37,7 +37,7 @@
</template>
<script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { defineComponent, inject, ref, useAttrs, watch, Ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils";
@@ -81,6 +81,7 @@ const emit = defineEmits<{
}>();
function updateValue(value: any) {
debugger;
if (props.single === true) {
const last = value?.[value.length - 1];
emit("update:value", last);
@@ -103,8 +104,18 @@ function emitSelectedChange(value: any) {
emit("selected-change", props.single === true ? options[0] : options);
}
const attrs = useAttrs();
const originalAttrs = useAttrs();
const attrs = computed(() => {
const tags = {
mode: "tags",
allowClear: true,
single: false,
};
return {
...tags,
...originalAttrs,
};
});
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => {
return {};
});
@@ -177,7 +188,7 @@ const getOptions = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
optionsRef.value = [];
},
showErrorNotify: false,
@@ -185,9 +196,9 @@ const getOptions = async () => {
);
let list = res?.list || res || [];
if (list.length > 0) {
message.value = t("certd.pluginCommon.getDataSuccessSelect");
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
} else {
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
}
list = list.map((item: any) => {
return {
@@ -3,7 +3,7 @@
<div class="flex flex-row">
<a-tree-select class="remote-tree-select-input" :tree-data="optionsRef" :value="value" tree-checkable allow-clear v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)"> </a-tree-select>
<div class="ml-5">
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -107,16 +107,16 @@ const getOptions = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
},
showErrorNotify: false,
}
);
const list = res?.list || res || [];
if (list.length > 0) {
message.value = t("certd.pluginCommon.getDataSuccessSelect");
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
} else {
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
}
optionsRef.value = list;
pagerRef.value.total = list.length;
@@ -0,0 +1,135 @@
<template>
<div class="template-textarea">
<a-textarea :value="value" v-bind="$attrs" @update:value="emit('update:value', $event)" />
<div class="template-textarea__toolbar">
<a-dropdown trigger="click">
<a-button>
<span class="template-textarea__selected">{{ selectedName }}</span>
<fs-icon icon="ion:chevron-down" class="ml-1" />
</a-button>
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item v-for="item in templates" :key="item.id">
<div class="template-textarea__menu-item">
<span>{{ item.name }}</span
><a-button type="link" danger size="small" @click.stop="deleteTemplate(item.id)">删除</a-button>
</div>
</a-menu-item>
<a-menu-item v-if="templates.length === 0" disabled>暂无模板</a-menu-item>
<a-menu-divider />
<a-menu-item key="save">保存当前脚本为模板</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { notification } from "ant-design-vue";
import { onMounted, ref, watch } from "vue";
import { request } from "/@/api/service";
import { useProjectStore } from "/@/store/project";
import { useFormDialog } from "/@/use/use-dialog";
import { nanoid } from "nanoid";
defineOptions({ name: "TemplateTextarea", inheritAttrs: false });
const props = withDefaults(defineProps<{ value?: string; templateType?: string }>(), { value: "", templateType: "script" });
const emit = defineEmits<{ "update:value": [value: string] }>();
const templates = ref<Array<{ id: string; name: string; content: string }>>([]);
const selected = ref<string>();
const key = "script_templates";
const { openFormDialog } = useFormDialog();
const selectedName = ref("选择脚本模板");
async function load() {
const entity = await request({ url: "/user/settings/get", method: "post", params: { key } });
try {
templates.value = entity?.setting ? JSON.parse(entity.setting)[props.templateType] || [] : [];
} catch {
templates.value = [];
}
}
async function persist() {
const entity = await request({ url: "/user/settings/get", method: "post", params: { key } });
let setting: Record<string, unknown[]> = {};
try {
setting = entity?.setting ? JSON.parse(entity.setting) : {};
} catch {
setting = {};
}
setting[props.templateType] = templates.value;
await request({
url: "/user/settings/save",
method: "post",
data: {
key,
setting: JSON.stringify(setting),
},
});
}
function handleMenuClick({ key: id }: { key: string }) {
if (id === "save") {
saveTemplate();
return;
}
const item = templates.value.find(template => template.id === id);
if (item) {
selected.value = item.id;
selectedName.value = item.name;
emit("update:value", item.content);
}
}
async function saveTemplate() {
if (!props.value?.trim()) {
notification.warning({ message: "请输入脚本内容后再保存模板" });
return;
}
await openFormDialog({
title: "保存脚本模板",
initialForm: { name: "" },
columns: { name: { title: "模板名称", type: "text", form: { rules: [{ required: true, message: "请输入模板名称" }] } } },
async onSubmit(form: { name: string }) {
const name = form.name?.trim();
if (!name) {
notification.warning({ message: "请输入模板名称" });
throw new Error("模板名称不能为空");
}
const id = nanoid();
templates.value.push({ id, name, content: props.value });
await persist();
selected.value = id;
selectedName.value = name;
notification.success({ message: "模板保存成功" });
},
});
}
async function deleteTemplate(id: string) {
templates.value = templates.value.filter(item => item.id !== id);
if (selected.value === id) {
selected.value = undefined;
selectedName.value = "选择脚本模板";
}
await persist();
notification.success({ message: "模板删除成功" });
}
watch(() => props.templateType, load);
onMounted(load);
</script>
<style lang="less">
.template-textarea__toolbar {
display: flex;
margin-top: 6px;
align-items: center;
}
.template-textarea__selected {
display: inline-block;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
.template-textarea__menu-item {
display: flex;
min-width: 220px;
justify-content: space-between;
align-items: center;
}
</style>
@@ -1,6 +1,6 @@
<template>
<div class="upload-cert">
<fs-button v-model:loading="loading" type="primary" :text="t('certd.pluginCommon.upload')" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
<fs-button v-model:loading="loading" type="primary" :text="t('certd.common.pluginCommon.upload')" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
</div>
</template>
<script lang="ts" setup>
@@ -30,18 +30,18 @@ const getScope: any = inject("get:scope", () => {
const getPluginType: any = inject("get:plugin:type", () => {
return "plugin";
});
const title = computed(() => props.title || t("certd.pluginCommon.uploadCert"));
const title = computed(() => props.title || t("certd.common.pluginCommon.uploadCert"));
function openUploadCertDialog() {
const columns = merge(
{
certName: {
title: t("certd.pluginCommon.certName"),
title: t("certd.common.pluginCommon.certName"),
form: {
component: {
name: "a-input",
vModel: "value",
},
helper: t("certd.pluginCommon.certNameHelper"),
helper: t("certd.common.pluginCommon.certNameHelper"),
},
},
},
@@ -51,7 +51,7 @@ function openUploadCertDialog() {
title: title.value,
columns: {
certName: {
title: t("certd.pluginCommon.certName"),
title: t("certd.common.pluginCommon.certName"),
form: {
component: {
name: "a-input",
@@ -86,7 +86,7 @@ function openUploadCertDialog() {
showErrorNotify: true,
}
);
notification.success({ message: t("certd.pluginCommon.uploadSuccess") });
notification.success({ message: t("certd.common.pluginCommon.uploadSuccess") });
emit("submit");
} finally {
loading.value = false;
@@ -14,6 +14,7 @@ import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
import ApiTest from "./common/api-test.vue";
import RefreshInput from "./common/refresh-input.vue";
import ParamsShow from "./common/params-show.vue";
import TemplateTextarea from "./common/template-textarea.vue";
export * from "./cert/index.js";
export default {
install(app: any) {
@@ -35,5 +36,6 @@ export default {
app.component("DomainSelector", DomainSelector);
app.component("InputPassword", InputPassword);
app.component("ParamsShow", ParamsShow);
app.component("TemplateTextarea", TemplateTextarea);
},
};
@@ -4,10 +4,10 @@ import { $t } from "/@/locales";
function createChallengeTypeDict() {
return dict({
data: [
{ value: "dns", label: $t("certd.verifyPlan.dnsChallenge"), color: "green" },
{ value: "dns", label: $t("certd.certdomain.verifyPlan.dnsChallenge"), color: "green" },
{ value: "dns-persist", label: "DNS持久验证", color: "cyan" },
{ value: "cname", label: $t("certd.verifyPlan.cnameProxyChallenge"), color: "blue" },
{ value: "http", label: $t("certd.verifyPlan.httpChallenge"), color: "yellow" },
{ value: "cname", label: $t("certd.certdomain.verifyPlan.cnameProxyChallenge"), color: "blue" },
{ value: "http", label: $t("certd.certdomain.verifyPlan.httpChallenge"), color: "yellow" },
],
});
}
@@ -18,11 +18,11 @@ function createUploaderTypeDict() {
{ label: "SFTP", value: "sftp" },
{ label: "SCP", value: "scp" },
{ label: "FTP", value: "ftp" },
{ label: $t("certd.verifyPlan.uploader.aliyunOss"), value: "alioss" },
{ label: $t("certd.verifyPlan.uploader.tencentCos"), value: "tencentcos" },
{ label: $t("certd.verifyPlan.uploader.qiniuOss"), value: "qiniuoss" },
{ label: $t("certd.certdomain.verifyPlan.uploader.aliyunOss"), value: "alioss" },
{ label: $t("certd.certdomain.verifyPlan.uploader.tencentCos"), value: "tencentcos" },
{ label: $t("certd.certdomain.verifyPlan.uploader.qiniuOss"), value: "qiniuoss" },
{ label: "S3/Minio", value: "s3" },
{ label: $t("certd.verifyPlan.uploader.sshDeprecated"), value: "ssh", disabled: true },
{ label: $t("certd.certdomain.verifyPlan.uploader.sshDeprecated"), value: "ssh", disabled: true },
],
});
}
@@ -30,8 +30,8 @@ function createUploaderTypeDict() {
function createDomainFromTypeDict() {
return dict({
data: [
{ value: "manual", label: $t("certd.verifyPlan.domainFrom.manual") },
{ value: "auto", label: $t("certd.verifyPlan.domainFrom.auto") },
{ value: "manual", label: $t("certd.certdomain.verifyPlan.domainFrom.manual") },
{ value: "auto", label: $t("certd.certdomain.verifyPlan.domainFrom.auto") },
],
});
}
@@ -33,11 +33,11 @@ const { t } = useI18n();
<div class="tutorial-button pointer" @click="open">
<template v-if="!slots.default">
<fs-icon v-if="showIcon === false" icon="ant-design:question-circle-outlined" class="mr-0.5"></fs-icon>
<div class="hidden md:block">{{ t("tutorial.title") }}</div>
<div class="hidden md:block">{{ t("certd.tutorial.title") }}</div>
</template>
<slot></slot>
<a-modal v-model:open="openedRef" class="tutorial-modal" width="90%">
<template #title>{{ t("tutorial.title") }}</template>
<template #title>{{ t("certd.tutorial.title") }}</template>
<tutorial-steps v-if="openedRef" />
<template #footer></template>
</a-modal>
@@ -18,9 +18,9 @@ import { ref } from "vue";
import { mitter } from "/@/utils/util.mitt";
const steps = ref<Step[]>([
{ title: t("certd.steps.createPipeline"), icon: <fs-icon icon="tabler:circle-number-1-filled"></fs-icon> },
{ title: t("certd.steps.addTask"), icon: <fs-icon icon="tabler:circle-number-2-filled"></fs-icon> },
{ title: t("certd.steps.scheduledRun"), icon: <fs-icon icon="tabler:circle-number-3-filled"></fs-icon> },
{ title: t("certd.tutorial.steps.createPipeline"), icon: <fs-icon icon="tabler:circle-number-1-filled"></fs-icon> },
{ title: t("certd.tutorial.steps.addTask"), icon: <fs-icon icon="tabler:circle-number-2-filled"></fs-icon> },
{ title: t("certd.tutorial.steps.scheduledRun"), icon: <fs-icon icon="tabler:circle-number-3-filled"></fs-icon> },
]);
function goPipeline() {
@@ -20,8 +20,8 @@
</div>
<div class="flex-center actions">
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()">{{ t("guide.buttons.prev") }}</fs-button>
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()">{{ t("guide.buttons.next") }}</fs-button>
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()">{{ t("certd.guide.buttons.prev") }}</fs-button>
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()">{{ t("certd.guide.buttons.next") }}</fs-button>
</div>
</div>
</template>
@@ -52,126 +52,126 @@ import { computed, nextTick, ref } from "vue";
const steps = ref<Step[]>([
{
title: t("guide.createCertPipeline.title"),
description: t("guide.createCertPipeline.description"),
title: t("certd.guide.createCertPipeline.title"),
description: t("certd.guide.createCertPipeline.description"),
items: [
{
title: t("guide.createCertPipeline.items.tutorialTitle"),
descriptions: [t("guide.createCertPipeline.items.tutorialDesc1"), t("guide.createCertPipeline.items.tutorialDesc2")],
title: t("certd.guide.createCertPipeline.items.tutorialTitle"),
descriptions: [t("certd.guide.createCertPipeline.items.tutorialDesc1"), t("certd.guide.createCertPipeline.items.tutorialDesc2")],
body: () => {
return <SimpleSteps></SimpleSteps>;
},
},
{
image: "/static/doc/images/1-add.png",
title: t("guide.createCertPipeline.items.createTitle"),
descriptions: [t("guide.createCertPipeline.items.createDesc")],
title: t("certd.guide.createCertPipeline.items.createTitle"),
descriptions: [t("certd.guide.createCertPipeline.items.createDesc")],
},
{
image: "/static/doc/images/3-add-success.png",
title: t("guide.createCertPipeline.items.successTitle"),
descriptions: [t("guide.createCertPipeline.items.successDesc")],
title: t("certd.guide.createCertPipeline.items.successTitle"),
descriptions: [t("certd.guide.createCertPipeline.items.successDesc")],
},
// {
// title: t("guide.createCertPipeline.items.nextTitle"),
// descriptions: [t("guide.createCertPipeline.items.nextDesc")],
// title: t("certd.guide.createCertPipeline.items.nextTitle"),
// descriptions: [t("certd.guide.createCertPipeline.items.nextDesc")],
// },
],
},
{
title: t("guide.addDeployTask.title"),
description: t("guide.addDeployTask.description"),
title: t("certd.guide.addDeployTask.title"),
description: t("certd.guide.addDeployTask.description"),
items: [
{
image: "/static/doc/images/5-1-add-host.png",
title: t("guide.addDeployTask.items.addTaskTitle"),
descriptions: [t("guide.addDeployTask.items.addTaskDesc1"), t("guide.addDeployTask.items.addTaskDesc2")],
title: t("certd.guide.addDeployTask.items.addTaskTitle"),
descriptions: [t("certd.guide.addDeployTask.items.addTaskDesc1"), t("certd.guide.addDeployTask.items.addTaskDesc2")],
},
{
image: "/static/doc/images/5-2-add-host.png",
title: t("guide.addDeployTask.items.fillParamsTitle"),
descriptions: [t("guide.addDeployTask.items.fillParamsDesc1"), t("guide.addDeployTask.items.fillParamsDesc2")],
title: t("certd.guide.addDeployTask.items.fillParamsTitle"),
descriptions: [t("certd.guide.addDeployTask.items.fillParamsDesc1"), t("certd.guide.addDeployTask.items.fillParamsDesc2")],
},
{
image: "/static/doc/images/5-3-add-host.png",
title: t("guide.addDeployTask.items.activateCertTitle"),
descriptions: [t("guide.addDeployTask.items.activateCertDesc1"), t("guide.addDeployTask.items.activateCertDesc2")],
title: t("certd.guide.addDeployTask.items.activateCertTitle"),
descriptions: [t("certd.guide.addDeployTask.items.activateCertDesc1"), t("certd.guide.addDeployTask.items.activateCertDesc2")],
},
{
image: "/static/doc/images/5-4-add-host.png",
title: t("guide.addDeployTask.items.taskSuccessTitle"),
descriptions: [t("guide.addDeployTask.items.taskSuccessDesc")],
title: t("certd.guide.addDeployTask.items.taskSuccessTitle"),
descriptions: [t("certd.guide.addDeployTask.items.taskSuccessDesc")],
},
{
image: "/static/doc/images/5-5-plugin-list.png",
title: t("guide.addDeployTask.items.pluginsTitle"),
descriptions: [t("guide.addDeployTask.items.pluginsDesc")],
title: t("certd.guide.addDeployTask.items.pluginsTitle"),
descriptions: [t("certd.guide.addDeployTask.items.pluginsDesc")],
},
],
},
{
title: t("guide.runAndTestTask.runAndTestTitle"),
description: t("guide.runAndTestTask.runAndTestDescription"),
title: t("certd.guide.runAndTestTask.runAndTestTitle"),
description: t("certd.guide.runAndTestTask.runAndTestDescription"),
items: [
{
image: "/static/doc/images/9-start.png",
title: t("guide.runAndTestTask.runTestOnce"),
descriptions: [t("guide.runAndTestTask.clickManualTriggerToTest")],
title: t("certd.guide.runAndTestTask.runTestOnce"),
descriptions: [t("certd.guide.runAndTestTask.clickManualTriggerToTest")],
},
{
image: "/static/doc/images/10-1-log.png",
title: t("guide.runAndTestTask.viewLogs"),
descriptions: [t("guide.runAndTestTask.clickTaskToViewStatusAndLogs")],
title: t("certd.guide.runAndTestTask.viewLogs"),
descriptions: [t("certd.guide.runAndTestTask.clickTaskToViewStatusAndLogs")],
},
{
image: "/static/doc/images/11-1-error.png",
title: t("guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("guide.runAndTestTask.viewErrorLogs")],
title: t("certd.guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("certd.guide.runAndTestTask.viewErrorLogs")],
},
{
image: "/static/doc/images/11-2-error.png",
title: t("guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("guide.runAndTestTask.viewErrorLogs"), t("guide.runAndTestTask.nginxContainerNotExistFix")],
title: t("certd.guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("certd.guide.runAndTestTask.viewErrorLogs"), t("certd.guide.runAndTestTask.nginxContainerNotExistFix")],
},
{
image: "/static/doc/images/12-1-log-success.png",
title: t("guide.runAndTestTask.executionSuccess"),
descriptions: [t("guide.runAndTestTask.retryAfterFix")],
title: t("certd.guide.runAndTestTask.executionSuccess"),
descriptions: [t("certd.guide.runAndTestTask.retryAfterFix")],
},
{
image: "/static/doc/images/12-2-skip-log.png",
title: t("guide.runAndTestTask.autoSkipAfterSuccess"),
descriptions: [t("guide.runAndTestTask.successSkipExplanation")],
title: t("certd.guide.runAndTestTask.autoSkipAfterSuccess"),
descriptions: [t("certd.guide.runAndTestTask.successSkipExplanation")],
},
{
image: "/static/doc/images/13-1-result.png",
title: t("guide.runAndTestTask.viewCertDeploymentSuccess"),
descriptions: [t("guide.runAndTestTask.visitNginxToSeeCert")],
title: t("certd.guide.runAndTestTask.viewCertDeploymentSuccess"),
descriptions: [t("certd.guide.runAndTestTask.visitNginxToSeeCert")],
},
{
image: "/static/doc/images/13-3-download.png",
title: t("guide.runAndTestTask.downloadCertManualDeploy"),
descriptions: [t("guide.runAndTestTask.downloadIfNoAutoDeployPlugin")],
title: t("certd.guide.runAndTestTask.downloadCertManualDeploy"),
descriptions: [t("certd.guide.runAndTestTask.downloadIfNoAutoDeployPlugin")],
},
],
},
{
title: t("guide.scheduleAndEmailTask.title"),
description: t("guide.scheduleAndEmailTask.description"),
title: t("certd.guide.scheduleAndEmailTask.title"),
description: t("certd.guide.scheduleAndEmailTask.description"),
items: [
{
image: "/static/doc/images/14-timer.png",
title: t("guide.scheduleAndEmailTask.setSchedule"),
descriptions: [t("guide.scheduleAndEmailTask.pipelineSuccessThenSchedule"), t("guide.scheduleAndEmailTask.recommendDailyRun")],
title: t("certd.guide.scheduleAndEmailTask.setSchedule"),
descriptions: [t("certd.guide.scheduleAndEmailTask.pipelineSuccessThenSchedule"), t("certd.guide.scheduleAndEmailTask.recommendDailyRun")],
},
{
image: "/static/doc/images/15-1-email.png",
title: t("guide.scheduleAndEmailTask.setEmailNotification"),
descriptions: [t("guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails")],
title: t("certd.guide.scheduleAndEmailTask.setEmailNotification"),
descriptions: [t("certd.guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails")],
},
{
title: t("guide.scheduleAndEmailTask.tutorialEndTitle"),
descriptions: [t("guide.scheduleAndEmailTask.thanksForWatching")],
title: t("certd.guide.scheduleAndEmailTask.tutorialEndTitle"),
descriptions: [t("certd.guide.scheduleAndEmailTask.thanksForWatching")],
},
],
},
@@ -48,56 +48,56 @@ const text = computed<Text>(() => {
const map = {
isComm: {
comm: {
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
button: {
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
icon: {
name: "",
title: t("vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.name", { vipLabel }),
},
nav: {
name: t("vip.comm.nav", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.nav", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
},
isPlus: {
comm: {
name: t("vip.plus.name"),
title: t("vip.plus.title"),
name: t("certd.vip.plus.name"),
title: t("certd.vip.plus.title"),
},
button: {
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
icon: {
name: "",
title: t("vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.name", { vipLabel }),
},
nav: {
name: t("vip.comm.nav", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.nav", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
},
free: {
comm: {
name: t("vip.free.comm.name"),
title: t("vip.free.comm.title"),
name: t("certd.vip.free.comm.name"),
title: t("certd.vip.free.comm.title"),
},
button: {
name: t("vip.free.button.name"),
title: t("vip.free.button.title"),
name: t("certd.vip.free.button.name"),
title: t("certd.vip.free.button.title"),
},
icon: {
name: "",
title: t("vip.free.button.name"),
title: t("certd.vip.free.button.name"),
},
nav: {
name: t("vip.free.nav.name"),
title: plusMessage || t("vip.free.nav.title"),
name: t("certd.vip.free.nav.name"),
title: plusMessage || t("certd.vip.free.nav.title"),
},
},
};
@@ -131,7 +131,7 @@ const userStore = useUserStore();
async function getVipTrial(vipType = "plus") {
const res = await api.getVipTrial(vipType);
notification.success({ message: t("vip.congratulations_vip_trial", { duration: res.duration }) });
notification.success({ message: t("certd.vip.congratulations_vip_trial", { duration: res.duration }) });
await settingStore.init();
}
@@ -139,8 +139,8 @@ function openTrialModal(vipType = "plus") {
Modal.destroyAll();
modal.confirm({
title: t("vip.trial_modal_title"),
okText: t("vip.trial_modal_ok_text"),
title: t("certd.vip.trial_modal_title"),
okText: t("certd.vip.trial_modal_ok_text"),
onOk() {
getVipTrial(vipType);
},
@@ -148,8 +148,8 @@ function openTrialModal(vipType = "plus") {
content: () => {
return (
<div class="flex-col mt-10 mb-10">
<div>{t("vip.trial_modal_thanks")}</div>
<div>{t("vip.trial_modal_click_confirm", { vipType })}</div>
<div>{t("certd.vip.trial_modal_thanks")}</div>
<div>{t("certd.vip.trial_modal_click_confirm", { vipType })}</div>
</div>
);
},
@@ -158,7 +158,7 @@ function openTrialModal(vipType = "plus") {
function openStarModal(vipType: string) {
if (settingStore.isPlus) {
notification.error({ message: t("vip.already_vip") });
notification.error({ message: t("certd.vip.already_vip") });
return;
}
Modal.destroyAll();
@@ -170,8 +170,8 @@ function openStarModal(vipType: string) {
// };
// modal.confirm({
// title: t("vip.get_7_day_pro_trial"),
// okText: t("vip.star_now"),
// title: t("certd.vip.get_7_day_pro_trial"),
// okText: t("certd.vip.star_now"),
// onOk() {
// goGithub();
// openTrialModal(vipType);
@@ -180,7 +180,7 @@ function openStarModal(vipType: string) {
// content: () => {
// return (
// <div class="flex mt-10 mb-10">
// <div>{t("vip.please_help_star")}</div>
// <div>{t("certd.vip.please_help_star")}</div>
// <img class="ml-5" src="https://img.shields.io/github/stars/certd/certd?logo=github" />
// </div>
// );
@@ -190,16 +190,16 @@ function openStarModal(vipType: string) {
function openUpgrade() {
if (!userStore.isAdmin) {
notification.info({ message: t("vip.admin_only_operation") });
notification.info({ message: t("certd.vip.admin_only_operation") });
return;
}
const placeholder = t("vip.enter_activation_code");
const placeholder = t("certd.vip.enter_activation_code");
const isPlus = settingStore.isPlus;
let title = t("vip.activate_pro_business");
let title = t("certd.vip.activate_pro_business");
if (settingStore.isComm) {
title = t("vip.renew_business");
title = t("certd.vip.renew_business");
} else if (settingStore.isPlus) {
title = t("vip.renew_pro_upgrade_business");
title = t("certd.vip.renew_pro_upgrade_business");
}
// const goBuyUrl = "https://afdian.com/a/greper"
@@ -214,10 +214,10 @@ function openUpgrade() {
function checkPerpetualPlus() {
if (settingStore.isPerpetual) {
Modal.warn({
title: t("vip.already_perpetual_plus"),
okText: t("vip.confirm"),
title: t("certd.vip.already_perpetual_plus"),
okText: t("certd.vip.confirm"),
});
throw new Error(t("vip.already_perpetual_plus"));
throw new Error(t("certd.vip.already_perpetual_plus"));
}
}
@@ -225,8 +225,8 @@ function openUpgrade() {
checkPerpetualPlus();
if (settingStore.isComm) {
Modal.warn({
title: t("vip.already_comm"),
okText: t("vip.confirm"),
title: t("certd.vip.already_comm"),
okText: t("certd.vip.confirm"),
});
return;
}
@@ -237,8 +237,8 @@ function openUpgrade() {
checkPerpetualPlus();
if (settingStore.isPlus && !settingStore.isComm) {
Modal.confirm({
title: t("vip.already_plus"),
okText: t("vip.confirm"),
title: t("certd.vip.already_plus"),
okText: t("certd.vip.confirm"),
onOk() {
window.open(goBuyCommUrl);
},
@@ -251,7 +251,7 @@ function openUpgrade() {
title,
class: "vip-modal",
maskClosable: true,
okText: t("vip.close"),
okText: t("certd.vip.close"),
width: 1180,
content: () => {
return <VipModalContent placeholder={placeholder} isPlus={isPlus} productInfo={productInfo} goBuyPlusPage={goBuyPlusPage} goBuyCommPage={goBuyCommPage} openStarModal={openStarModal} modalRef={modalRef} />;
@@ -34,7 +34,7 @@
</a-tooltip>
</span>
<span v-else>
<span class="price-text">{{ t("vip.freee") }}</span>
<span class="price-text">{{ t("certd.vip.freee") }}</span>
</span>
</div>
<div class="get-show">
@@ -44,15 +44,15 @@
<span class="color-red"> {{ todayOrderCount.vipTotal }}</span>
位伙伴支持
</span>
<a-tooltip :title="t('vip.afdian_support_vip')">
<a-tooltip :title="t('certd.vip.afdian_support_vip')">
<a-button size="small" type="primary" @click="goBuyPlusPage">
{{ t("vip.get_after_support") }}
{{ t("certd.vip.get_after_support") }}
</a-button>
</a-tooltip>
</template>
<template v-else-if="item.type === 'comm'">
<a-button size="small" type="primary" @click="goBuyCommPage">
{{ t("vip.buy") }}
{{ t("certd.vip.buy") }}
</a-button>
</template>
</div>
@@ -63,36 +63,36 @@
</div>
<div>
<a href="https://certd.docmirror.cn/guide/donate/#相关问题" target="_blank">
{{ t("vip.question") }}
{{ t("certd.vip.question") }}
</a>
</div>
<div class="mt-10">
<div class="w-100 flex-col md:flex-row">
<span>{{ t("vip.site_id") }}:</span>
<span>{{ t("certd.vip.site_id") }}:</span>
<fs-copyable v-model="computedSiteId" class="mr-2 inline-flex"></fs-copyable>
<a @click="goBindAccount">{{ t("vip.not_effective") }}</a>
<a @click="goBindAccount">{{ t("certd.vip.not_effective") }}</a>
</div>
</div>
<div v-if="isPlus" class="mt-10 flex flex-col md:flex-row">
<span class="mr-2"> {{ t("vip.current") }} {{ vipLabel }} {{ t("vip.activated_expire_time") }} {{ settingStore.expiresText }} </span>
<span class="mr-2"> {{ t("certd.vip.current") }} {{ vipLabel }} {{ t("certd.vip.activated_expire_time") }} {{ settingStore.expiresText }} </span>
<a href="https://app.handfree.work/subject/#/page/detail/1" target="_blank">
{{ t("vip.learn_more") }}
{{ t("certd.vip.learn_more") }}
</a>
</div>
<div class="mt-10">
<span class="mr-2">{{ t("vip.have_activation_code") }}</span>
<span class="mr-2">{{ t("certd.vip.have_activation_code") }}</span>
<span>
<a @click="showManualActivation">{{ t("vip.manual_activation") }}</a>
<a @click="showManualActivation">{{ t("certd.vip.manual_activation") }}</a>
</span>
</div>
<div v-if="manualActiveFlag" class="mt-10">
<div class="mt-10">
<a-input-search v-model:value="formState.code" class="w-2/6" :placeholder="placeholder" :enter-button="t('vip.activate')" @search="doActive"></a-input-search>
<a-input-search v-model:value="formState.code" class="w-2/6" :placeholder="placeholder" :enter-button="t('certd.vip.activate')" @search="doActive"></a-input-search>
</div>
<div class="mt-10">
{{ t("vip.activation_code_one_use") }}
<a @click="goAccount">{{ t("vip.bind_account") }}</a
>,{{ t("vip.transfer_vip") }}
{{ t("certd.vip.activation_code_one_use") }}
<a @click="goAccount">{{ t("certd.vip.bind_account") }}</a
>,{{ t("certd.vip.transfer_vip") }}
</div>
</div>
</div>
@@ -128,24 +128,24 @@ const formState = reactive({
async function doActive() {
if (!formState.code) {
notification.error({ message: t("vip.enterCode") });
throw new Error(t("vip.enterCode"));
notification.error({ message: t("certd.vip.enterCode") });
throw new Error(t("certd.vip.enterCode"));
}
const res = await api.doActive(formState);
if (res) {
await settingStore.init();
const vipLabel = settingStore.vipLabel;
Modal.success({
title: t("vip.successTitle"),
content: t("vip.successContent", {
title: t("certd.vip.successTitle"),
content: t("certd.vip.successContent", {
vipLabel,
expireDate: settingStore.plusInfo.expireTime === -1 ? t("vip.permanent") : dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
expireDate: settingStore.plusInfo.expireTime === -1 ? t("certd.vip.permanent") : dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
}),
onOk() {
if (!(settingStore.installInfo.bindUserId > 0)) {
Modal.confirm({
title: t("vip.bindAccountTitle"),
content: t("vip.bindAccountContent"),
title: t("certd.vip.bindAccountTitle"),
content: t("certd.vip.bindAccountContent"),
onOk() {
router.push("/sys/account");
},
@@ -179,39 +179,39 @@ function goBindAccount() {
const vipTypeDefine: any = {
free: {
title: t("vip.basic_edition"),
desc: t("vip.community_free_version"),
title: t("certd.vip.basic_edition"),
desc: t("certd.vip.community_free_version"),
type: "free",
icon: "lucide:package-open",
privilege: t("vip.free_privilege").split("\n"),
privilege: t("certd.vip.free_privilege").split("\n"),
},
plus: {
title: t("vip.professional_edition"),
desc: t("vip.open_source_support"),
title: t("certd.vip.professional_edition"),
desc: t("certd.vip.open_source_support"),
type: "plus",
privilege: t("vip.plus_privilege").split("\n"),
privilege: t("certd.vip.plus_privilege").split("\n"),
trial: {
title: t("vip.click_to_get_7_day_trial"),
title: t("certd.vip.click_to_get_7_day_trial"),
click: () => {
props.openStarModal("plus");
},
},
icon: "stash:thumb-up",
priceText: props.productInfo.plus.priceText || `¥${props.productInfo.plus.price}/${t("vip.years")}`,
discountText: props.productInfo.plus.discountText || `¥${props.productInfo.plus.price3}/3${t("vip.years")}`,
priceText: props.productInfo.plus.priceText || `¥${props.productInfo.plus.price}/${t("certd.vip.years")}`,
discountText: props.productInfo.plus.discountText || `¥${props.productInfo.plus.price3}/3${t("certd.vip.years")}`,
tooltip: props.productInfo.plus.tooltip,
},
comm: {
title: t("vip.business_edition"),
desc: t("vip.commercial_license"),
title: t("certd.vip.business_edition"),
desc: t("certd.vip.commercial_license"),
type: "comm",
icon: "vaadin:handshake",
privilege: t("vip.comm_privilege").split("\n"),
priceText: props.productInfo.comm.priceText || `¥${props.productInfo.comm.price}/${t("vip.years")}`,
discountText: props.productInfo.comm.discountText || `¥${props.productInfo.comm.price3}/3${t("vip.years")}`,
privilege: t("certd.vip.comm_privilege").split("\n"),
priceText: props.productInfo.comm.priceText || `¥${props.productInfo.comm.price}/${t("certd.vip.years")}`,
discountText: props.productInfo.comm.discountText || `¥${props.productInfo.comm.price3}/3${t("certd.vip.years")}`,
tooltip: props.productInfo.comm.tooltip,
trial: {
title: t("vip.click_to_get_7_day_trial"),
title: t("certd.vip.click_to_get_7_day_trial"),
click: () => {
props.openStarModal("comm");
},
@@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" setup>
import { BasicLayout, LockScreen, UserDropdown } from "/@/vben/layouts";
import { computed, onErrorCaptured, onMounted, provide, ref } from "vue";
@@ -23,14 +23,14 @@ const menus = computed(() => [
router.push("/cert/mine/user-profile");
},
icon: "fa-solid:book",
text: t("certd.accountInfo"),
text: t("certd.mine.accountInfo"),
},
{
handler: () => {
router.push("/cert/mine/security");
},
icon: "fluent:shield-keyhole-16-regular",
text: t("certd.securitySettings"),
text: t("certd.mine.securitySettings"),
},
]);
@@ -109,7 +109,7 @@ const projectStore = useProjectStore();
<vip-button class="flex-center header-btn" mode="nav" />
</div>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<fs-button shape="circle" type="text" icon="clarity:host-solid-badged" :text="null" :tooltip="{ title: t('certd.client') }" @click="goCertdClient" />
<fs-button shape="circle" type="text" icon="clarity:host-solid-badged" :text="null" :tooltip="{ title: t('certd.navigation.client') }" @click="goCertdClient" />
</div>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<fs-button shape="circle" type="text" icon="ion:logo-github" :text="null" @click="goGithub" />
@@ -13,34 +13,54 @@ import notification from "./certd/notification";
import addon from "./certd/addon";
import suite from "./certd/suite";
import project from "./certd/project";
import syssettings from "./certd/sys-settings";
import sysplugin from "./certd/sys-plugin";
import sysauthority from "./certd/sys-authority";
import syscname from "./certd/sys-cname";
import settings from "./certd/sys-settings";
import plugin from "./certd/sys-plugin";
import authority from "./certd/sys-authority";
import cnameSettings from "./certd/sys-cname";
import tutorial from "./certd/tutorial";
import cron from "./certd/cron";
import client from "./certd/client";
import wallet from "./certd/wallet";
import nettest from "./certd/nettest";
import audit from "./certd/audit";
import applyTemplate from "./certd/apply-template";
import dnsPersist from "./certd/dns-persist";
import auth from "./certd/auth";
import guide from "./certd/guide";
import vip from "./certd/vip";
// Note: @ is reserved in locale messages; use {'@'} when needed.
export default {
...common,
...navigation,
...dashboard,
...pipeline,
...history,
...monitor,
...certdomain,
...cname,
...access,
...open,
...mine,
...notification,
...addon,
...suite,
...project,
...syssettings,
...sysplugin,
...sysauthority,
...syscname,
...tutorial,
...cron,
common,
navigation,
dashboard,
pipeline,
history,
monitor,
certdomain,
cname,
access,
open,
mine,
notification,
addon,
suite,
project,
sys: {
settings,
plugin,
authority,
cname: cnameSettings,
},
tutorial,
cron,
client,
wallet,
nettest,
audit,
applyTemplate,
dnsPersist,
auth,
guide,
vip,
};
@@ -4,4 +4,11 @@ export default {
nameHelper: "Fill in as you like, useful to distinguish when multiple authorizations of the same type exist",
level: "Level",
system: "System",
select: "Select",
selectProvider: "Select Authorization Provider",
deleteConfirm: "If this authorization is already in use, pipelines may fail. Proceed with caution.",
name: "Name",
user: "User",
type: "Type",
selectType: "Please select a type",
};
@@ -0,0 +1,11 @@
export default {
setSuccess: "Settings saved",
editTitle: "Edit Certificate Application Template",
addTitle: "Add Certificate Application Template",
name: "Template Name",
default: "Default",
setDefault: "Set as Default",
disabled: "Disabled",
createTime: "Created At",
config: "Configuration",
};
@@ -0,0 +1,10 @@
export default {
createTime: "Operation Time",
type: "Operation Type",
action: "Action",
result: "Result",
success: "Success",
failed: "Failed",
remark: "Remark",
ipAddress: "IP Address",
};
@@ -1,58 +1,4 @@
export default {
welcomeBack: "Welcome Back",
pageTitle: "Plug-and-play Admin system",
pageDesc: "Efficient, versatile frontend template",
loginSuccess: "Login Successful",
loginSuccessDesc: "Welcome Back",
loginSubtitle: "Enter your account details to manage your projects",
selectAccount: "Quick Select Account",
username: "Username",
password: "Password",
usernameTip: "Please enter username",
passwordErrorTip: "Password is incorrect",
passwordTip: "Please enter password",
verifyRequiredTip: "Please complete the verification first",
rememberMe: "Remember Me",
createAnAccount: "Create an Account",
createAccount: "Create Account",
alreadyHaveAccount: "Already have an account?",
accountTip: "Don't have an account?",
signUp: "Sign Up",
signUpSubtitle: "Make managing your applications simple and fun",
confirmPassword: "Confirm Password",
confirmPasswordTip: "The passwords do not match",
agree: "I agree to",
privacyPolicy: "Privacy-policy",
terms: "Terms",
agreeTip: "Please agree to the Privacy Policy and Terms",
goToLogin: "Login instead",
passwordStrength: "Use 8 or more characters with a mix of letters, numbers & symbols",
forgetPassword: "Forget Password?",
forgetPasswordSubtitle: "Enter your email and we'll send you instructions to reset your password",
emailTip: "Please enter email",
emailValidErrorTip: "The email format you entered is incorrect",
sendResetLink: "Send Reset Link",
email: "Email",
qrcodeSubtitle: "Scan the QR code with your phone to login",
qrcodePrompt: "Click 'Confirm' after scanning to complete login",
qrcodeLogin: "QR Code Login",
codeSubtitle: "Enter your phone number to start managing your project",
code: "Security code",
codeTip: "Security code required {0} characters",
mobile: "Mobile",
mobileLogin: "Mobile Login",
mobileTip: "Please enter mobile number",
mobileErrortip: "The phone number format is incorrect",
sendCode: "Get Security code",
sendText: "Resend in {0}s",
thirdPartyLogin: "Or continue with",
loginAgainTitle: "Please Log In Again",
loginAgainSubTitle: "Your login session has expired. Please log in again to continue.",
layout: {
center: "Align Center",
alignLeft: "Align Left",
alignRight: "Align Right",
},
loginType: {
password: "Password Login",
sms: "Mobile Login",
@@ -110,4 +56,30 @@ export default {
userInfo: "User Info",
securitySettingTip: "2FA Setting",
securitySetting: "2FA Setting",
register: {
usernameTab: "Register with username",
emailTab: "Register with email",
mobileTab: "Register with mobile",
username: "Username",
email: "Email",
password: "Password",
confirmPassword: "Confirm Password",
captcha: "Captcha",
emailCode: "Email verification code",
mobileLogin: "Go to mobile login (login to register)",
inviteCode: "Invitation code",
inviteCodeOptional: "Invitation code (optional)",
submit: "Register",
login: "Log In",
noMethodEnabled: "No registration method is enabled",
usernameRequired: "Please enter your username",
emailRequired: "Please enter your email",
emailInvalid: "Please enter a valid email address",
passwordRequired: "Please enter your password",
confirmPasswordRequired: "Please confirm your password",
passwordMismatch: "The two passwords do not match",
smsCodeRequired: "Please enter the SMS verification code",
emailCodeRequired: "Please enter the email verification code",
captchaRequired: "Please complete the captcha",
},
};
@@ -1,5 +1,6 @@
export default {
domain: {
dnsPersistDescription: "Currently, only Google certificates support DNS persistent validation.",
domainManager: "Domain Manager",
domainDescription: "used to auto apply for certificate", //管理域名的校验方式,用于申请证书时自动选择验证方式
domain: "Domain",
@@ -19,6 +20,7 @@ export default {
subdomainConfirmContent: "{domain} appears to be a subdomain. Only delegated subdomains and free second-level subdomains need to be maintained here. Otherwise certificate application may fail. Continue?",
importFromProvider: "Import from Domain Provider",
importFromResolveRecords: "Import from DNS Records",
importFromResolveRecordsTooltip: "Import DNS records as monitoring sites",
syncExpirationDate: "Sync Domain Expiration Time",
syncTaskSubmitted: "Sync task submitted",
syncExpirationProgress: "Sync Domain Expiration Progress",

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