Compare commits

...
52 Commits
Author SHA1 Message Date
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 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
642 changed files with 8747 additions and 2961 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. **插件命名**:插件名称应简洁明了,反映其功能。
+26 -4
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,6 +116,7 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
- 修改后保存修改摘要。
- 恢复前再次备份当前版本。
- 不上传历史文件,不保存 Token、证书、私钥或真实授权值。
- 修改完一版后立即提交更新到certd,但不要发布到市场。
详细格式见 `references/local-history.md`。
@@ -125,6 +124,7 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。
- 新插件的 `author` 必须使用启动提示词中给出的插件作者,不要自行编造作者名;修改已有插件时保留原 `author`,不要改成其他作者。
- 统一使用 `await _ctx.import(...)` 引用模块。
- `"/@/..."` 表示以绝对路径引用 `server/src/` 下的模块。
- 最后返回继承目标基类的 class。
@@ -135,6 +135,28 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
需要字段格式时读取 `references/online-yaml-format.md`。
需要组件示例时读取 `references/component-examples.md`。
需要按其他字段动态显隐/必填/联动时读取 `references/merge-script.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
@@ -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
@@ -61,6 +61,8 @@ content: |
### 编辑规则
- 修改已有插件时保留 `name`、`author`、`pluginType` 和已有兼容字段。
- 新插件的 `author` 使用启动提示词中给出的插件作者,不要自行编造作者名;启动提示词未给出作者时先向用户确认。
- `fullName` 由 `author/name` 组成,作者变化会导致插件身份变化,不要随意修改。
- 只修改需求涉及的字段,避免删除未知的 YAML 字段。
- 脚本过长时仍放在同一个 `content` block scalar 中。
- 提交前检查 YAML 可解析、`content` 非空、版本和插件类型没有被意外修改。
@@ -57,4 +57,41 @@ return class DemoAccess extends BaseAccess {
- `onTestRequest` 应调用实际 API 方法并在失败时抛出异常。
- 对外 API 方法应统一处理分页、错误和返回字段。
- 使用 `this.logger` 或框架提供的 logger,禁止 `console.log`。
- 一般将API接口方法封装到Access中,其他plugin调用access的方法当做client sdk使用
- 一般将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`。
+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. **定义任务输入参数**
+23
View File
@@ -3,6 +3,29 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
@@ -0,0 +1 @@
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" };
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.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
+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.4"
"version": "1.45.0"
}
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+4 -3
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.44.4",
"version": "1.45.0",
"type": "module",
"module": "./dist/index.js",
"main": "./dist/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.44.4",
"@certd/basic": "^1.45.0",
"@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": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
@@ -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'
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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 -1
View File
@@ -1 +1 @@
19:17
00:51
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.44.4",
"version": "1.45.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -54,5 +54,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
@@ -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;
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.44.4",
"version": "1.45.0",
"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.4",
"@certd/plus-core": "^1.44.4",
"@certd/basic": "^1.45.0",
"@certd/plus-core": "^1.45.0",
"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": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
+1
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>;
@@ -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;
}
}
//动态获取模块解析
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.44.4",
"version": "1.45.0",
"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": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.44.4",
"version": "1.45.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -37,5 +37,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.44.4",
"version": "1.45.0",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -63,5 +63,5 @@
"fetch"
]
},
"gitHead": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.44.4",
"version": "1.45.0",
"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.4",
"@certd/basic": "^1.45.0",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.44.4",
"version": "1.45.0",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -29,11 +29,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.44.4",
"@certd/basic": "^1.44.4",
"@certd/pipeline": "^1.44.4",
"@certd/plugin-lib": "^1.44.4",
"@certd/plus-core": "^1.44.4",
"@certd/acme-client": "^1.45.0",
"@certd/basic": "^1.45.0",
"@certd/pipeline": "^1.45.0",
"@certd/plugin-lib": "^1.45.0",
"@certd/plus-core": "^1.45.0",
"@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": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
@@ -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,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.44.4",
"version": "1.45.0",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -52,5 +52,5 @@
"typeorm": "^0.3.20",
"typescript": "^5.4.2"
},
"gitHead": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.44.4",
"version": "1.45.0",
"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.4"
"@certd/plugin-lib": "^1.45.0"
},
"devDependencies": {
"@types/chai": "^4.3.12",
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.44.4",
"version": "1.45.0",
"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.4",
"@certd/basic": "^1.44.4",
"@certd/pipeline": "^1.44.4",
"@certd/acme-client": "^1.45.0",
"@certd/basic": "^1.45.0",
"@certd/pipeline": "^1.45.0",
"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": "cc60cd4cd1d914110be65284bcf16cd3c5a63b8d"
"gitHead": "fcbf1045fe3806fa1a0bebf5038a6f67aec283d0"
}
@@ -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>;
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
@@ -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.4",
"version": "1.45.0",
"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.4",
"@certd/pipeline": "^1.44.4",
"@certd/lib-iframe": "^1.45.0",
"@certd/pipeline": "^1.45.0",
"@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;
}
@@ -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 }">
@@ -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>
@@ -177,7 +177,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 +185,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;
@@ -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;
@@ -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",
@@ -0,0 +1,18 @@
export default {
clientManager: "Client Manager",
title: "Client Manager",
description: "View online status and managed site count of connected Certd clients. Clients report a heartbeat every 10 minutes and are marked offline if no heartbeat arrives for 30 minutes.",
plusRequired: "This feature requires the Pro edition",
machineName: "Machine",
clientId: "Client ID",
version: "Version",
os: "OS",
status: "Status",
online: "Online",
offline: "Offline",
siteCount: "Sites",
httpsSiteCount: "HTTPS Sites",
syncedSiteCount: "Synced",
failedSiteCount: "Failed",
lastHeartbeatAt: "Last Heartbeat",
};
@@ -1,4 +1,6 @@
export default {
required: "Required",
no: "No",
usera: "User",
disabledSuccess: "Disabled successfully",
app: {
@@ -21,12 +23,18 @@ export default {
add: "Generate New Key",
edit: "Edit",
deleteSuccess: "Delete successful",
fastCrud: {
view: "View",
copy: "Copy",
edit: "Edit",
remove: "Delete",
addSuccess: "Added successfully",
saveSuccess: "Saved successfully",
},
saveSuccess: "Saved successfully",
cancel: "Cancel",
yes: "Yes",
no: "No",
all: "All",
required: "This field is required",
requiredField: "This field is required",
fieldRequired: "This field is required",
field_required: "This field is required",
@@ -1,50 +1,59 @@
export default {
dashboard: {
greeting: "Hello, {name}, welcome to 【{site}】",
latestVersion: "Latest version: {version}",
validUntil: "Valid until:",
tutorialTooltip: "Click to view detailed tutorial",
tutorialText: "Only 3 steps to automatically apply and deploy certificates",
alertMessage: "Certificates and credentials are sensitive. Do not use untrusted online Certd services or images. Always self-host and use official release channels:",
helpDoc: "Help Docs",
pipelineCount: "Number of Certificate Pipelines",
noPipeline: "You have no certificate pipelines yet",
enabledCount: "Enabled",
disabledCount: "Disabled",
certCount: "Number of Certificates",
noCert: "You have no certificates yet",
manageCert: "View Certificates",
siteMonitorCount: "Number of Monitored Sites",
siteAbnormalCount: "Abnormal",
siteNormalCount: "Normal",
manageSiteMonitor: "Manage Monitored Sites",
domainCount: "Number of Domains",
domainExpiringCount: "Expiring",
domainExpiredCount: "Expired",
domainNotExpiredCount: "Not Expired",
manageDomain: "Manage Domains",
certExpiringCount: "Soon-to-Expire",
certExpiredCount: "Expired",
certNoExpireCount: "Not Expired",
createNow: "Create Now",
managePipeline: "Manage Pipelines",
pipelineStatus: "Pipeline Status",
recentRun: "Recent Run Statistics",
runCount: "Run Count",
expiringCerts: "Soon-to-Expire Certificates",
totalGeneratedCertCount: "Total Certificates Generated",
singleDomainCertShortCount: "Single-domain",
multiDomainCertShortCount: "Multi-domain",
wildcardCertShortCount: "Wildcard",
singleDomainCertCount: "Single-domain Certificates Generated",
multiDomainCertCount: "Multi-domain Certificates Generated",
wildcardCertCount: "Wildcard Certificates Generated",
savedAmount: "Estimated savings",
savedAmountRule: "Estimated at one quarter of Tencent Cloud's annual certificate prices: single-domain ¥532, multi-domain ¥912, and wildcard ¥2147. Actual savings may vary.",
totalPipelineRuns: "Total Pipeline Runs",
supportedTasks: "Overview of Supported Deployment Tasks",
changeLog: "Change Log",
stableRelease: "Stable",
latestRelease: "Preview",
},
greeting: "Hello, {name}, welcome to 【{site}】",
latestVersion: "Latest version: {version}",
validUntil: "Valid until:",
tutorialTooltip: "Click to view detailed tutorial",
tutorialText: "Only 3 steps to automatically apply and deploy certificates",
alertMessage: "Certificates and credentials are sensitive. Do not use untrusted online Certd services or images. Always self-host and use official release channels:",
helpDoc: "Help Docs",
pipelineCount: "Number of Certificate Pipelines",
noPipeline: "You have no certificate pipelines yet",
enabledCount: "Enabled",
disabledCount: "Disabled",
certCount: "Number of Certificates",
noCert: "You have no certificates yet",
manageCert: "View Certificates",
siteMonitorCount: "Number of Monitored Sites",
siteAbnormalCount: "Abnormal",
siteNormalCount: "Normal",
manageSiteMonitor: "Manage Monitored Sites",
domainCount: "Number of Domains",
domainExpiringCount: "Expiring",
domainExpiredCount: "Expired",
domainNotExpiredCount: "Not Expired",
manageDomain: "Manage Domains",
certExpiringCount: "Soon-to-Expire",
certExpiredCount: "Expired",
certNoExpireCount: "Not Expired",
createNow: "Create Now",
managePipeline: "Manage Pipelines",
pipelineStatus: "Pipeline Status",
recentRun: "Recent Run Statistics",
runCount: "Run Count",
expiringCerts: "Soon-to-Expire Certificates",
totalGeneratedCertCount: "Total Certificates Generated",
singleDomainCertShortCount: "Single-domain",
multiDomainCertShortCount: "Multi-domain",
wildcardCertShortCount: "Wildcard",
singleDomainCertCount: "Single-domain Certificates Generated",
multiDomainCertCount: "Multi-domain Certificates Generated",
wildcardCertCount: "Wildcard Certificates Generated",
savedAmount: "Estimated savings",
savedAmountRule: "Estimated at one quarter of Tencent Cloud's annual certificate prices: single-domain ¥532, multi-domain ¥912, and wildcard ¥2147. Actual savings may vary.",
totalPipelineRuns: "Total Pipeline Runs",
supportedTasks: "Overview of Supported Deployment Tasks",
supportedTasksHelper: "This list is for display only. Create a pipeline to use these tasks.",
changeLog: "Change Log",
stableRelease: "Stable",
latestRelease: "Preview",
consoleTitle: "Console",
userTotal: "Total Users",
manageUsers: "Manage Users",
userGrowth: "User Growth",
newUsers: "New Users",
allPipelineTotal: "Total Pipelines",
managePipelines: "Manage Pipelines",
pipelineGrowth: "Pipeline Growth",
newPipelines: "New Pipelines",
recentRunTitle: "Recent Run Statistics",
};
@@ -0,0 +1,24 @@
export default {
pending: "Pending Setup",
created: "Created",
validating: "Validating",
valid: "Valid",
retry: "Retry",
deleteTxtTip: "Delete the TXT record at the provider",
accountUriMissing: "The ACME account authorization is missing accountUri. Please regenerate the account.",
verifySuccess: "Validation succeeded",
verifyFailed: "No matching TXT record found. Please try again later.",
submitted: "Validation submitted",
selectProviderAndAccess: "Select a DNS provider and authorization",
txtCreated: "TXT record created",
settingTitle: "Set DNS TXT Record",
manualAdd: "Add Manually",
autoAdd: "Add with Authorization",
manualTip: "Add the following TXT record in your DNS console, then click OK to validate immediately.",
autoTip: "Select a DNS provider and authorization. The system will create the TXT record and validate it in the background.",
mainDomain: "Main Domain",
txtHost: "TXT Host",
txtValue: "TXT Value",
provider: "DNS Provider",
access: "DNS Authorization",
};
@@ -7,6 +7,8 @@ export default {
settingLink: "Site Monitoring Settings",
limitInfo: "Basic edition limited to 1, professional and above unlimited, current",
checkAll: "Check All",
checkBatch: "Check Selected",
batchCheckConfirm: "Check the {count} selected sites now?",
checkNow: "Check Now",
batchDelete: "Batch Delete",
confirmTitle: "Confirm",
@@ -1,4 +1,7 @@
import monitorSite from "./monitor-site";
export default {
...monitorSite,
certificateRepo: {
title: "Certificate Repository",
sub: "Certificates generated from pipeline",
@@ -1,4 +1,9 @@
export default {
home: "Home",
login: "Login",
register: "Register",
forgotPassword: "Forgot Password",
oauthCallback: "OAuth Callback",
framework: {
title: "Framework",
home: "Home",
@@ -13,10 +18,13 @@ export default {
accessManager: "Access Management",
dnsPersistRecord: "DNS Persist Records",
certApplyTemplate: "Certificate Apply Templates",
certApplyTemplateHelper: "Preset certificate application parameters. They are filled automatically when creating a pipeline (excluding domains and validation methods).",
subDomain: "Subdomain Delegation Settings",
pipelineGroup: "Pipeline Group Management",
pipelineGroupHelper: "Manage pipeline groups",
openKey: "Open API Key",
notification: "Notification Settings",
notificationHelper: "Manage notification configurations",
siteMonitorSetting: "Site Monitor Settings",
auditLog: "Audit Log",
userSecurity: "Security Settings",
@@ -25,6 +33,7 @@ export default {
mySuite: "My Suite",
suiteBuy: "Suite Purchase",
myTrade: "My Orders",
myWallet: "My Wallet",
inviteCommission: "Incentive Plan",
paymentReturn: "Payment Return",
client: "Certd Client",
@@ -48,7 +57,9 @@ export default {
userManager: "User Management",
userDataManager: "User Data Management",
pipelineManager: "User Pipeline Management",
pipelineManagerHelper: "Manage all users' pipelines",
siteMonitorManager: "User Certificate Monitor",
siteMonitorManagerHelper: "Manage all users' certificate monitors",
suiteManager: "Suite Management",
suiteSetting: "Suite Settings",
orderManager: "Order Management",
@@ -72,6 +83,8 @@ export default {
currentProject: "Current Project",
projectMemberManager: "Project Member",
auditLog: "Audit Log",
auditLogHelper: "View all users' operation records",
jobHistory: "Monitoring Execution History",
domainMonitorSetting: "Domain Monitor Settings",
},
};
@@ -0,0 +1,13 @@
export default {
title: "Network Test",
helper: "Test whether your server container network is working",
customDomain: "Custom Domain Test",
serverInfo: "Server Information",
refresh: "Refresh",
loading: "Loading...",
fetchFailed: "Failed to fetch server information",
noInfo: "No information",
localIp: "Local IP",
publicIp: "Public IP",
dnsServer: "DNS Server",
};
@@ -3,6 +3,13 @@ export default {
scopeOpenApiOnly: "Open API Only",
scopeFullAccount: "Full Account Permissions",
scopeHelper: "Open API only allows access to open APIs; full account permissions allow access to all APIs",
keyRemark: "Key Purpose",
keyRemarkHelper: "Describe the purpose of this key for easier identification",
keyRemarkMaxChars: "Maximum 512 characters",
returnCertPassword: "Return certificate password",
returnCertPasswordHelper: "When enabled, the certificate password is included in Open API certificate responses. Keep this key secure.",
yes: "Yes",
no: "No",
gen: {
text: "API Test",
title: "x-certd-token",
@@ -33,6 +33,10 @@ export default {
pipelineStarted: "Pipeline has started running",
},
actions: {
edit: "Edit",
delete: "Delete",
enable: "Enable",
disable: "Disable",
editPipeline: "Edit Pipeline",
editConfigGroup: "Modify Configuration/Group",
viewCertificate: "View Certificate",
@@ -65,6 +69,145 @@ export default {
legoAcme: "Lego-ACME: Based on Lego, supports a wide range of DNS providers, suitable for users familiar with Lego",
aliyunOrder: "Aliyun-Order: Get certificate from Aliyun certificate order",
},
editor: {
editTask: "Edit Task",
editStep: "Edit Step",
installedPlugins: "Installed Plugins",
pluginMarket: "Plugin Market",
searchPluginMarket: "Search plugin market",
searchLocalPlugins: "Search local plugins",
loadingPluginMarket: "Loading plugin market...",
noPluginFound: "No plugins found",
triggerManually: "Trigger Manually",
triggerSource: "Trigger Source",
scheduledTrigger: "Trigger Source (Scheduled)",
webhookTrigger: "Trigger Source (Webhook)",
certExpireTime: "Certificate expiration time: ",
certExpired: "Certificate expired ",
viewCertificate: "View Certificate",
downloadCertificate: "Download Certificate",
nextExecutionTime: "Next execution time: ",
noTriggerSource: "No trigger source configured; it will not run automatically",
validityPeriod: "Validity period: ",
save: "Save",
cancel: "Cancel",
addStage: "New Stage",
addTask: "Add Task",
addAfterTask: "Add After-Task",
addNotification: "Add Notification",
notificationNotSet: "Notification not set",
history: "Execution History",
historyHint: "Click a task to view logs",
taskName: "Task Name",
taskSteps: "Task Steps",
addStep: "Add Step",
copy: "Copy",
paste: "Paste",
confirm: "Confirm",
required: "This field is required",
taskNeedsStep: "At least one step is required, or delete this task using the button beside the title",
step: "Step",
endStage: "End",
confirmTitle: "Confirm",
confirmManualRun: "Are you sure you want to trigger the run manually?",
taskLog: "Task Log",
noLogs: "No logs (this node was not triggered or has no log records for this run)",
aiAnalyze: "AI Analysis",
rerun: "Rerun",
downloadLogs: "Download Logs",
close: "Close",
batchEditSteps: "Batch Edit Steps",
selectAllSteps: "Select All Steps",
batchEnableSteps: "Batch Enable Steps",
batchDisableSteps: "Batch Disable Steps",
batchDeleteSteps: "Batch Delete Steps",
exitBatchEdit: "Exit Batch Edit",
stepsSerialHelper: "Steps run sequentially. If an earlier step fails, later steps will not run.",
current: "Current",
view: "View",
confirmCancel: "Confirm Cancellation",
confirmCancelTask: "Cancel this task?",
taskCanceled: "Task canceled successfully",
selectAllTasks: "Select all tasks",
batchEnableTasks: "Enable tasks in batch",
batchDisableTasks: "Disable tasks in batch",
batchDeleteTasks: "Delete tasks in batch",
exitBatchEditTasks: "Exit batch edit",
editStageTitle: "Edit stage title",
batchEditTasks: "Edit tasks in batch",
dragSort: "Drag to reorder",
forceRerun: "Force rerun this step",
resizeStage: "Drag to resize stage",
afterLabel: "After-task",
notificationLabel: "Notification",
unsavedLeave: "This pipeline is not saved. Leave anyway?",
newPipeline: "New Pipeline",
newTask: "New Task",
newStage: "New Stage",
stageSuffix: " Stage",
saveBeforeRunTitle: "Save before running the pipeline",
saveBeforeRunContent: "Save it first?",
runUnsupported: "Running is not supported yet",
addStageAndTask: "Please add a stage and task first",
prerequisiteMissing: "The prerequisite output step {param} for {step} does not exist or is disabled",
selectPlugin: "Select plugin",
searchPlugin: "Search plugins",
searchResults: "Search results",
plusPluginRequired: "This plugin requires Certd Professional",
immediateRun: "Run immediately",
immediateRunHelper: "Run the pipeline immediately after saving",
operationSuccess: "Operation successful",
regenerate: "Regenerate",
webhookHelper: "Supports POST and GET requests (each pipeline has a unique Webhook URL)",
regenerateWebhookTitle: "Regenerate the Webhook URL?",
regenerateWebhookContent: "The old Webhook URL will become invalid",
confirmDeleteTask: "Delete this task?",
confirmDeleteStep: "Delete this step?",
confirmDeleteSelectedTasks: "Delete the selected {count} tasks?",
confirmDeleteSelectedSteps: "Delete the selected {count} steps?",
stepCopied: "Step configuration copied. You can paste it in another task editor",
allStepsCopied: "All steps copied. You can paste them in another task editor",
copyFirst: "Please copy first",
pasted: "Pasted successfully",
enterName: "Please enter a name",
pluginTypeFirst: "Please select a type first",
runStrategy: "Run strategy",
runStrategyNormal: "Run normally (required for certificate tasks unless you want this task to run every time)",
runStrategySkip: "Skip after success (choose this for other tasks)",
runStrategyDefault: "Usually keep the default",
runStrategyNormalHelp: "Run normally: runs every time; certificate tasks need this",
runStrategySkipHelp: "Skip after success: skip after one successful run and run again when the certificate changes",
triggerWebhook: "Webhook Trigger",
triggerSchedule: "Scheduled Trigger",
choose: "Select",
noLogsShort: "No logs",
statusSuccess: "success",
statusError: "error",
statusSkip: "skip",
statusStart: "running",
statusCanceled: "canceled",
statusNone: "not run",
statusDisabled: "disabled",
statusNoDeployCount: "insufficient runs",
pluginGroups: {
all: "All",
cert: "Certificate",
host: "Host",
cdn: "CDN",
panel: "Panel",
aliyun: "Alibaba Cloud",
huawei: "Huawei Cloud",
tencent: "Tencent Cloud",
volcengine: "Volcengine",
jdcloud: "JD Cloud",
ucloud: "UCloud",
baidu: "Baidu Cloud",
qiniu: "Qiniu Cloud",
aws: "Amazon Web Services",
other: "Other",
admin: "Administration",
},
},
pipelineForm: {
createTitle: "Create Certificate Pipeline",
moreParams: "More Parameters",
@@ -1,4 +1,50 @@
export default {
title: "Suite",
mySuite: "My Suite",
suiteBuy: "Suite Purchase",
myTrade: "My Orders",
inviteCommission: "Incentive Plan",
inviteSubtitle: "Invite friends and earn generous commissions",
promotionAgreement: "Promotion Agreement",
withdraw: "Withdraw",
withdrawAvailable: "Available to withdraw {amount}",
inviteCode: "Invitation Code",
orderTime: "Time",
inviteTime: "Invitation Time",
inviteeUsername: "Invited Username",
inviteLink: "Invitation Link",
myLevel: "My Level",
unset: "Not set",
commissionRate: "Commission Rate",
commissionAfterPayment: "Commission is calculated at this rate after a friend pays",
proportion: "Rate",
inviteSuccess: "Successful Invites",
incomeRecords: "Income Records",
enableFirst: "Please enable the incentive plan first",
enable: "Enable Incentive Plan",
disabled: "Incentive plan is not enabled",
totalIncome: "Total Income",
monthIncome: "This Month's Income",
promotionAmount: "Total Promotion Amount",
inviteeCount: "People Invited",
people: " people",
agreePromotion: "I have read and agree to the promotion agreement",
close: "Close",
postpone: "Later",
agreeAndEnable: "Agree and Enable",
agreeRequired: "Please check the agreement first",
enabledSuccess: "Incentive plan enabled",
purchaseSuccess: "Suite purchased successfully",
agreementPlaceholder: "Enter the promotion agreement users must accept before enabling the incentive plan",
activationExchange: "Redeem Activation Code",
activationCode: "Activation Code",
enterActivationCode: "Please enter the activation code",
enterCdkCode: "Please enter the CDK activation code",
activateSuccess: "Activated successfully",
exchangeFailed: "Redemption failed",
noSuite: "No suites available for purchase",
suiteHelperStack: "Note: Multiple suites and addons can be purchased, and their quantities are accumulated",
suiteHelperLatest: "Note: 1) Only the latest suite is active at a time; 2) Multiple addons can be purchased and take effect immediately; 3) Suite and addon quantities are accumulated",
order: {
confirmTitle: "Order Confirmation",
package: "Package",
@@ -1,4 +1,14 @@
export default {
aiDevTitle: "AI Plugin Development",
certPluginConfig: "Certificate Plugin Configuration",
saveSuccess: "Saved successfully",
commonGoogleAcme: "Common Google ACME Account",
commonZeroSslAcme: "Common ZeroSSL ACME Account",
commonLiteSslAcme: "Common LiteSSL ACME Account",
otherConfig: "Other Configuration",
certApplyDefaults: "Certificate Apply Plugin Defaults",
customCertApplyParams: "Custom certificate application plugin parameters",
save: "Save",
titlea: "Title",
pluginFile: "Plugin File",
selectPluginFile: "Select plugin file",
@@ -1,118 +1,134 @@
export default {
sys: {
setting: {
baseSetting: "Base Settings",
registerSetting: "Register Settings",
safeSetting: "Safe Settings",
paymentSetting: "Payment Settings",
captchaSetting: "Captcha Setting",
pipelineSetting: "Pipeline Settings",
oauthSetting: "Login Settings",
networkSetting: "Network Settings",
adminModeSetting: "Admin Mode Settings",
adminModeHelper: "enterprise mode : allow to create and manage pipelines, roles, users, etc.\n saas mode : only allow to create and manage pipelines",
enterpriseMode: "Enterprise Mode",
saasMode: "SaaS Mode",
modeIntroTitle: "Management Mode Introduction",
saasModeDescription: "SaaS mode: each user manages and independently uses their own pipelines and authorization resources.",
enterpriseModeDescription: "Enterprise mode: pipelines, certificates, and authorization resources are managed through projects for all internal users.",
switchModeNow: "Switch Mode Now",
suiteSetting: "Suite Settings",
orderManager: "Order Management",
headerMenuSetting: "Top Menu Settings",
sitePersonalization: "Site Personalization",
siteName: "Site Name",
slogan: "Subtitle / Slogan",
loginLogo: "Login Page Logo",
entityName: "Entity Name",
shownAtBottom: "Displayed at the bottom",
entityUrl: "Entity URL",
modeSaasHelper: "SaaS mode: each user manages and independently uses their own pipelines and authorization resources.",
modeEnterpriseHelper: "Enterprise mode: pipelines, certificates, and authorization resources are managed through projects for all internal users.",
modeMore: "More Management Mode Details",
modeFixedWarning: "Choose a suitable mode before use and avoid switching it frequently afterwards.",
modeMigrate: "After switching to enterprise mode, personal data created earlier will be hidden. You can migrate it to a project.",
modeCommWarning: "Enterprise mode is not recommended for the commercial edition unless you intend to use it internally.",
baseSetting: "Base Settings",
registerSetting: "Register Settings",
safeSetting: "Safe Settings",
paymentSetting: "Payment Settings",
captchaSetting: "Captcha Setting",
pipelineSetting: "Pipeline Settings",
oauthSetting: "Login Settings",
networkSetting: "Network Settings",
adminModeSetting: "Admin Mode Settings",
adminModeHelper: "enterprise mode : allow to create and manage pipelines, roles, users, etc.\n saas mode : only allow to create and manage pipelines",
enterpriseMode: "Enterprise Mode",
saasMode: "SaaS Mode",
showRunStrategy: "Show RunStrategy",
showRunStrategyHelper: "Allow modify the run strategy of the task",
homePageEnabled: "Enable Home Page",
showRunStrategy: "Show RunStrategy",
showRunStrategyHelper: "Allow modify the run strategy of the task",
homePageEnabled: "Enable Home Page",
captchaEnabled: "Enable Login Captcha",
captchaHelper: "Whether to enable captcha verification for login",
captchaType: "Captcha Setting",
captchaTest: "Captcha Test",
// 保存后再点击测试,请务必测试通过了,再开启登录验证码
captchaTestHelper: "Save and click test, please make sure the test is passed before enabling login captcha",
captchaEnabled: "Enable Login Captcha",
captchaHelper: "Whether to enable captcha verification for login",
captchaType: "Captcha Setting",
captchaTest: "Captcha Test",
// 保存后再点击测试,请务必测试通过了,再开启登录验证码
captchaTestHelper: "Save and click test, please make sure the test is passed before enabling login captcha",
pipelineValidTimeEnabled: "Enable Pipeline Valid Time",
pipelineValidTimeEnabledHelper: "Whether to enable the valid time of the pipeline",
certDomainAddToMonitorEnabled: "Add Domain to Certificate Monitor",
certDomainAddToMonitorEnabledHelper: "Whether to add the domain to the certificate monitor",
pipelineValidTimeEnabled: "Enable Pipeline Valid Time",
pipelineValidTimeEnabledHelper: "Whether to enable the valid time of the pipeline",
certDomainAddToMonitorEnabled: "Add Domain to Certificate Monitor",
certDomainAddToMonitorEnabledHelper: "Whether to add the domain to the certificate monitor",
defaultCertRenewDays: "Default Certificate Renew Days",
defaultCertRenewDaysHelper: "Default certificate renewal days, helpful for table list progress bar display",
defaultCertRenewDaysRecommend: "Recommend 15",
defaultCertRenewDays: "Default Certificate Renew Days",
defaultCertRenewDaysHelper: "Default certificate renewal days, helpful for table list progress bar display",
defaultCertRenewDaysRecommend: "Recommend 15",
pipelineMaxRunningCount: "Max Running Count",
pipelineMaxRunningCountHelper: "Max running count of the pipeline",
pipelineMaxRunningCountRecommend: "Recommend 5-15, default 10",
pipelineMaxRunningCount: "Max Running Count",
pipelineMaxRunningCountHelper: "Max running count of the pipeline",
pipelineMaxRunningCountRecommend: "Recommend 5-15, default 10",
acmeWalkFromAuthoritative: "Check TXT Record from Authoritative NS",
acmeWalkFromAuthoritativeHelper: "Apply certificate when whether to check the TXT record from authoritative NS server first",
acmeWalkFromAuthoritative: "Check TXT Record from Authoritative NS",
acmeWalkFromAuthoritativeHelper: "Apply certificate when whether to check the TXT record from authoritative NS server first",
customAcmeManage: "Custom ACME Management",
customAcmeManageHelper:
"Configure certificate authorities (built-in + custom ACME). Built-in providers cannot be modified or deleted. Custom ACME providers (intranet CA, enterprise CA or any RFC 8555 compatible ACME service) become selectable as certificate authorities in apply tasks and ACME account authorization",
customAcmeName: "Name",
customAcmeSslProvider: "SSL Provider",
customAcmeSslProviderHelper: "Unique identifier, e.g. myca; used to distinguish providers in pipeline and ACME account",
customAcmeReverseProxy: "Reverse Proxy",
customAcmeReverseProxyHelper:
"For example, for Let's Encrypt, configure the reverse proxy on your overseas server: \nproxy_pass https://acme-v02.api.letsencrypt.org/; \nthen fill in your reverse proxy server domain here, without http://",
customAcmeAdd: "Add Custom ACME",
customAcmeEdit: "Edit Custom ACME",
customAcmeNeedEab: "Need EAB",
customAcmeBuiltIn: "Built-in",
customAcmeTag: "Custom",
customAcmeDeleteConfirmTitle: "Delete Custom ACME",
customAcmeDeleteConfirmContent: "Deleting '{title}' may affect pipelines using it to apply certificates. Are you sure?",
customAcmeSaveCheck: "Name, sslProvider and Directory URL are required",
customAcmeSslProviderUnique: "The sslProvider identifier must be unique",
customAcmeManage: "Custom ACME Management",
customAcmeManageHelper:
"Configure certificate authorities (built-in + custom ACME). Built-in providers cannot be modified or deleted. Custom ACME providers (intranet CA, enterprise CA or any RFC 8555 compatible ACME service) become selectable as certificate authorities in apply tasks and ACME account authorization",
customAcmeName: "Name",
customAcmeSslProvider: "SSL Provider",
customAcmeSslProviderHelper: "Unique identifier, e.g. myca; used to distinguish providers in pipeline and ACME account",
customAcmeReverseProxy: "Reverse Proxy",
customAcmeReverseProxyHelper:
"For example, for Let's Encrypt, configure the reverse proxy on your overseas server: \nproxy_pass https://acme-v02.api.letsencrypt.org/; \nthen fill in your reverse proxy server domain here, without http://",
customAcmeAdd: "Add Custom ACME",
customAcmeEdit: "Edit Custom ACME",
customAcmeNeedEab: "Need EAB",
customAcmeBuiltIn: "Built-in",
customAcmeTag: "Custom",
customAcmeDeleteConfirmTitle: "Delete Custom ACME",
customAcmeDeleteConfirmContent: "Deleting '{title}' may affect pipelines using it to apply certificates. Are you sure?",
customAcmeSaveCheck: "Name, sslProvider and Directory URL are required",
customAcmeSslProviderUnique: "The sslProvider identifier must be unique",
fixedCertExpireDays: "Fixed Cert Expire Days",
fixedCertExpireDaysHelper: "Fixed cert expiration days, helpful for table list progress bar display",
fixedCertExpireDaysRecommend: "Recommend 90",
fixedCertExpireDays: "Fixed Cert Expire Days",
fixedCertExpireDaysHelper: "Fixed cert expiration days, helpful for table list progress bar display",
fixedCertExpireDaysRecommend: "Recommend 90",
enableOauth: "Enable OAuth2 Login",
oauthEnabledHelper: "Whether to enable OAuth2 login",
oauthProviders: "OAuth2 Login Providers",
oauthType: "OAuth2 Login Type",
oauthConfig: "OAuth2 Login Config",
oauthProviderSelectorPlaceholder: "Not Configured",
oauthCallback: "Callback URL",
oauthCallbackHelper: "Copy this URL to the callback address of the OAuth2 login provider",
oauthCallbackCopy: "Copy Callback URL",
oauthAutoRegister: "Auto Register User",
oauthAutoRegisterCheckedText: "Auto Register",
oauthAutoRegisterUnCheckedText: "User Select",
oauthAutoRegisterHelper: "Whether to auto register user when login",
oauthAutoRedirect: "Auto Redirect to OAuth2 Login",
oauthAutoRedirectHelper: "Whether to auto redirect to OAuth2 login when login (using the first enabled OAuth2 login type)",
oauthOnly: "OAuth2 Login Only",
oauthOnlyHelper: "Only allow OAuth2 login. Password login remains available for 10 minutes after server startup as an emergency window.",
enablePasskey: "Enable Passkey Login",
passkeyHostnameNotSame: "Passkey hostname must be the same as the main domain",
passkeyEnabledHelper:
"1、Site must enable https \n2、Domain name must not change, otherwise the registered passkey will be invalid \n3、Domain name must be the same as the main domain, otherwise the registered passkey will be invalid",
enableOauth: "Enable OAuth2 Login",
oauthEnabledHelper: "Whether to enable OAuth2 login",
oauthProviders: "OAuth2 Login Providers",
oauthType: "OAuth2 Login Type",
oauthConfig: "OAuth2 Login Config",
oauthProviderSelectorPlaceholder: "Not Configured",
oauthCallback: "Callback URL",
oauthCallbackHelper: "Copy this URL to the callback address of the OAuth2 login provider",
oauthCallbackCopy: "Copy Callback URL",
oauthAutoRegister: "Auto Register User",
oauthAutoRegisterCheckedText: "Auto Register",
oauthAutoRegisterUnCheckedText: "User Select",
oauthAutoRegisterHelper: "Whether to auto register user when login",
oauthAutoRedirect: "Auto Redirect to OAuth2 Login",
oauthAutoRedirectHelper: "Whether to auto redirect to OAuth2 login when login (using the first enabled OAuth2 login type)",
oauthOnly: "OAuth2 Login Only",
oauthOnlyHelper: "Only allow OAuth2 login. Password login remains available for 10 minutes after server startup as an emergency window.",
enablePasskey: "Enable Passkey Login",
passkeyHostnameNotSame: "Passkey hostname must be the same as the main domain",
passkeyEnabledHelper:
"1、Site must enable https \n2、Domain name must not change, otherwise the registered passkey will be invalid \n3、Domain name must be the same as the main domain, otherwise the registered passkey will be invalid",
email: {
templates: "Email Templates",
templateType: "Template Type",
templateProvider: "Template Config",
email: {
templates: "Email Templates",
templateType: "Template Type",
templateProvider: "Template Config",
templateSetting: "Email Template Setting",
serverSetting: "Email Server Setting",
sendTest: "Send Test",
templateSetting: "Email Template Setting",
serverSetting: "Email Server Setting",
sendTest: "Send Test",
templateProviderSelectorPlaceholder: "Not Configured",
},
notice: "System Notice",
noticeHelper: "System notice, will be displayed on the login page",
noticePlaceholder: "System notice",
customFooter: "Custom Footer",
customFooterHelper: "Custom site footer, displayed at the bottom of the page, support HTML format",
customFooterPlaceholder: "Custom footer content",
environmentVars: "Environment Variables",
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
commonHeaders: "Common Headers",
commonHeadersHelper: "Common headers automatically added to server-side HTTP requests, one per line, format: KEY=VALUE. Existing request headers with the same name are not overwritten.",
bindUrl: "Bind URL",
bindUrlHelper: "Bind URL, used as your site URL in notifications",
},
templateProviderSelectorPlaceholder: "Not Configured",
},
notice: "System Notice",
noticeHelper: "System notice, will be displayed on the login page",
noticePlaceholder: "System notice",
customFooter: "Custom Footer",
customFooterHelper: "Custom site footer, displayed at the bottom of the page, support HTML format",
customFooterPlaceholder: "Custom footer content",
environmentVars: "Environment Variables",
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
commonHeaders: "Common Headers",
commonHeadersHelper: "Common headers automatically added to server-side HTTP requests, one per line, format: KEY=VALUE. Existing request headers with the same name are not overwritten.",
bindUrl: "Bind URL",
bindUrlHelper: "Bind URL, used as your site URL in notifications",
icpRegistrationNumber: "ICP Registration Number",
icpPlaceholder: "Guangdong ICP xxxxxxx Number",
publicSecurityRegistrationNumber: "Public Security Registration Number",

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