Compare commits

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

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

docs: 移除重复的英文翻译项 "no" 和 "required"
2026-10-13 23:50:03 +08:00
xiaojunnuo e2aff952f9 chore: 添加证书密码字段sql并更新路由标题 2026-10-13 22:31:01 +08:00
xiaojunnuo 9d9ae64aff fix: 修复批量修改证书申请参数,无法选择证书颁发机构的bug 2026-10-13 22:18:56 +08:00
xiaojunnuo 22715a430e perf: 证书申请成功通知支持批量修改
- 新增英文和中文的操作按钮国际化文案(编辑、删除、启用、禁用)
- 优化状态国际化处理逻辑,支持动态加载对应语言的状态文本
- 新增证书申请成功通知批量修改组件和API逻辑
- 修复状态文本显示逻辑,确保正确使用国际化文案
- 优化通知更新逻辑,支持替换指定触发条件的通知规则
2026-10-13 21:37:06 +08:00
xiaojunnuo 949a69f0dc fix(aliyun-cert): 阿里云订阅证书增加申请请求接口 2026-10-13 20:28:16 +08:00
xiaojunnuo 0874cb2223 chore(email-service): 注释plus邮件发送逻辑并更新错误提示
注释plus邮件发送的自动使用逻辑,同时更新错误提示信息,明确指引用户配置邮件服务器
2026-10-12 10:38:02 +08:00
xiaojunnuo 53db97b6f2 chore: i18n 优化 2026-10-12 00:18:36 +08:00
xiaojunnuo cd2dcaa859 chore: 国际化持续完善 2026-10-11 23:28:45 +08:00
xiaojunnuo d0463abc26 perf: 完善插件翻译 2026-10-11 18:24:42 +08:00
xiaojunnuo 8f1b38ad7d perf: 多语言国际化优化,插件支持国际化配置
- 新增钱包模块,包含余额、提现、收益明细等功能
- 为插件、访问授权、任务等添加英文国际化支持
- 重构页面标题为多语言文本,统一国际化展示
- 完善导航、设置等页面的多语言支持
- 修复部分代码格式问题,优化插件国际化处理逻辑
2026-10-11 15:09:52 +08:00
xiaojunnuo e11349b8cc refactor: 重构国际化文案结构,按模块拆分命名空间
调整所有国际化文案的命名空间,将零散的文案归类到对应业务模块下,优化语言文件的组织结构,便于后续维护和扩展。
2026-10-11 13:39:34 +08:00
xiaojunnuo 2394cc6dd9 chore(certd-client): 修复开关选项的字典数据顺序
调整了字典数据的顺序,确保“否”选项在“是”选项之后,符合常规交互逻辑
2026-10-11 13:23:27 +08:00
xiaojunnuo 8209c3d6e1 chore(open-api): 添加返回证书密码开关及多语言支持
在开放接口密钥配置中新增返回证书密码的开关选项,添加中英文多语言提示文本和开关选项文案,明确开关开启时响应会包含证书密码,提醒妥善保管。
2026-10-11 13:22:58 +08:00
xiaojunnuo 3e001b8ba4 perf: 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署
新增证书密码字段到证书信息和开放密钥表,添加返回开关,开放接口可根据开关决定是否返回证书密码
2026-10-11 12:24:09 +08:00
xiaojunnuo 41ca198116 perf: 插件市场新增同步按钮并调整搜索区域布局 2026-10-04 14:23:51 +08:00
xiaojunnuo 6eca5dad9b chore(wangsu): 修复网宿access getCertList 不支持普通CDN的bug 2026-10-04 00:35:31 +08:00
xiaojunnuo c285522500 chore(wangsu): 新增网宿CDN Pro证书列表获取方法,更新插件说明
新增getCdnProCertList方法用于获取网宿CDN Pro的证书列表,同时更新WangsuRefreshCert插件的描述,明确仅支持普通CDN证书更新,CDN Pro证书需使用对应在线插件。
2026-10-04 00:08:10 +08:00
xiaojunnuo b12f7c54d4 build: release 2026-10-03 02:47:15 +08:00
xiaojunnuo b055bbecfc build: publish 2026-10-03 01:01:10 +08:00
xiaojunnuo a52a6ce8f6 build: trigger build image 2026-10-03 01:01:00 +08:00
894 changed files with 9269 additions and 2999 deletions
+2
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. **定义授权属性**
+12 -3
View File
@@ -122,20 +122,29 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
## YAML 和脚本规范
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。`content` 必须使用 YAML 字面块 `|` 保留 JavaScript 换行,禁止使用折叠块 `>`,避免语句拼接导致 `Unexpected token 'const'` 等解析错误。
- 新插件的 `author` 必须使用启动提示词中给出的插件作者,不要自行编造作者名;修改已有插件时保留原 `author`,不要改成其他作者。
- 统一使用 `await _ctx.import(...)` 引用模块。
- `"/@/..."` 表示以绝对路径引用 `server/src/` 下的模块。
- 最后返回继承目标基类的 class。
- 不使用 `import`、`export`、装饰器或独立源码文件语法。
- 使用 `this.logger` 打印插件执行日志。
- 使用 `this.ctx.http` 访问 HTTP 能力。
- 使用目标基类提供的 logger 打印插件执行日志;在线 Access 使用 `this.ctx.logger`。
- 使用 `this.ctx.http` 访问 HTTP 能力;默认返回响应体 `response.data`,需要响应头、状态码或 Cookie 时必须设置 `returnOriginRes: true`,再从 `response.data` 读取响应体。详见 `references/context-utils.md`。
- 优先复用 `this.ctx.utils` 中的现成工具,尤其是 `this.ctx.utils.hash` 的摘要和编码方法;完整分类见 `references/context-utils.md`。
- 失败时抛出 `Error`。
- 日志打印对象时必须使用 `JSON.stringify(value)`,禁止直接字符串拼接对象导致输出 `[object Object]`;需要可读日志时先构造明确的 JSON 字符串。
### 证书域名匹配与列表组件
- 需要从前置证书获取域名时,优先在 `input` 前置配置 `certDomains` 字段,使用 `cert-domains-getter`,并通过 `inputKey` 指向证书字段。
- 远程证书或资源列表如果需要按证书域名匹配,必须为每个选项提供 `domain` 字段;`domain` 可以是单个域名字符串,也可以是域名数组。只有满足此前提,才能使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)` 返回分组后的列表。
- 如果服务商接口不支持分页或服务端搜索,列表组件不要设置 `pager: true` 或 `search: true`;确需搜索时在插件中获取完整列表后执行本地过滤。
需要字段格式时读取 `references/online-yaml-format.md`。
需要组件示例时读取 `references/component-examples.md`。
需要按其他字段动态显隐/必填/联动时读取 `references/merge-script.md`。
需要查找 `this.ctx.utils` 工具或确认 HTTP 返回结构时读取 `references/context-utils.md`。
### 字段动态显隐(mergeScript)
@@ -17,3 +17,61 @@ Access 插件负责保存授权配置,也负责封装平台 API/SDK,供 Task
- 业务插件通过 `dependPlugins` 依赖 Access。
- `editable: false` 时不要尝试修改 Access,在业务插件内部实现必要的调用。
- 不要在日志中打印完整授权配置。
## 地址与请求复用
- `BaseAccess` 已提供通用的 `normalizeEndpoint(endpoint)`,用于校验地址并清理 URL 末尾的 `/`。Access 插件直接调用 `this.normalizeEndpoint(this.endpoint)`,不要在插件中重复实现同名方法。
- Cookie 解析优先使用 `this.ctx.http.getCookies(response)` 或 `this.ctx.http.getCookie(response, name)`;需要原始响应时为请求设置 `returnOriginRes: true`。
## TLS 证书校验
需要兼容自签名或过期 HTTPS 证书时,在 Access 表单中使用 `skipSslVerify` 开关,并将其显式传给 `this.ctx.http.request`。默认关闭校验跳过,仅用户主动开启后才跳过:
```yaml
input:
skipSslVerify:
title: 忽略 HTTPS 证书校验
required: false
component:
name: a-switch
vModel: checked
```
```js
const response = await this.ctx.http.request({
url: `${baseUrl}/api`,
method: "POST",
skipSslVerify: this.skipSslVerify === true,
});
```
## 测试按钮
通过 `api-test` 输入项绑定 `onTestRequest`,用户点击按钮后 Certd 会调用该方法。测试方法应执行轻量、只读的连通性请求,并在失败时抛出有意义的错误:
```yaml
input:
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口是否正常
```
```js
async onTestRequest() {
const baseUrl = this.normalizeEndpoint(this.endpoint);
const response = await this.ctx.http.request({
url: `${baseUrl}/api/status`,
method: "GET",
skipSslVerify: this.skipSslVerify === true,
});
if (!response || response.code !== 0) {
throw new Error(`连接测试失败:${response?.message || "接口返回异常"}`);
}
return "连接成功";
}
```
@@ -70,29 +70,8 @@ name: "a-auto-complete",
options: [
{ value: "cn-qingdao", label: "华北1(青岛)" },
{ value: "cn-beijing", label: "华北2(北京)" },
{ value: "cn-zhangjiakou", label: "华北3(张家口)" },
{ value: "cn-huhehaote", label: "华北5(呼和浩特)" },
{ value: "cn-wulanchabu", label: "华北6(乌兰察布)" },
{ value: "cn-hangzhou", label: "华东1(杭州)" },
{ value: "cn-shanghai", label: "华东2(上海)" },
{ value: "cn-shenzhen", label: "华南1(深圳)" },
{ value: "cn-guangzhou", label: "华南3(广州)" },
{ value: "ap-southeast-2", label: "澳大利亚(悉尼)" },
{ value: "ap-southeast-3", label: "马来西亚(吉隆坡)" },
{ value: "ap-northeast-1", label: "日本(东京)" },
{ value: "cn-chengdu", label: "西南1(成都)" },
{ value: "ap-southeast-1", label: "新加坡" },
{ value: "ap-southeast-5", label: "印度尼西亚(雅加达)" },
{ value: "cn-hongkong", label: "中国香港" },
{ value: "eu-central-1", label: "德国(法兰克福)" },
{ value: "us-east-1", label: "美国(弗吉尼亚)" },
{ value: "us-west-1", label: "美国(硅谷)" },
{ value: "eu-west-1", label: "英国(伦敦)" },
{ value: "me-east-1", label: "阿联酋(迪拜)" },
//金融云
...
{ value: "cn-beijing-finance-1", label: "华北2 金融云(邀测)" },
{ value: "cn-hangzhou-finance", label: "华东1 金融云" },
{ value: "cn-shanghai-finance-1", label: "华东2 金融云" },
{ value: "cn-shenzhen-finance-1", label: "华南1 金融云" },
],
placeholder: "集群所属大区",
@@ -0,0 +1,103 @@
# 在线插件运行时工具
在线插件实例可以通过 `this.ctx.utils` 复用 Certd 已有工具。开发前按分类查找现成方法,避免重复实现。
## 工具分类
| 分类 | 调用入口 | 常用方法 | 用途 |
| --- | --- | --- | --- |
| 哈希与编码 | `this.ctx.utils.hash` | `md5(data, digest?)`、`sha256(data, digest?)`、`base64(data)`、`base64Decode(data)`、`hmacSha256(data, digest?)`、`hmacSha256WithKey(key, data, digest?)`、`toHex(data)`、`hexToStr(data)`、`hexToNumber(data)` | 摘要、签名、Base64/Hex 编解码 |
| 字符串 | `this.ctx.utils.string` | 读取 `packages/core/basic/src/utils/util.string.ts` | 转换、截断、格式化 |
| 域名 | `this.ctx.utils.domain` | 读取 `packages/core/basic/src/utils/util.domain.ts` | 主域名和子域名处理,域名选项列表,域名匹配分组 |
| 请求 | `this.ctx.utils.request` / `this.ctx.http` | `getCookies(response)`、`getCookie(response, name)`、`download(config)` | Cookie、下载和请求辅助 |
| 文件 | `this.ctx.utils.file` | 读取 `packages/core/basic/src/utils/util.file.ts` | 文件读写 |
| Promise/延迟 | `this.ctx.utils.promises`、`this.ctx.utils.sleep` | 读取对应实现 | 重试、等待、并发 |
| 时间 | `this.ctx.utils.dayjs` | Day.js API | 时间处理 |
| ID/缓存/合并 | `this.ctx.utils.id`、`nanoid`、`cache`、`mergeUtils` | 读取对应 `util.*.ts` | ID、缓存和配置合并 |
示例:
```javascript
const passwd = this.ctx.utils.hash.md5(password);
const pass = this.ctx.utils.hash.base64(`salt_11${password}`);
```
方法签名以仓库中 `packages/core/basic/src/utils/util.*.ts` 为准,不要凭名称猜测参数顺序。
## `this.ctx.http.request` 返回结构
`this.ctx.http.request` 默认直接返回响应体,也就是 Axios 的 `response.data`:
```javascript
const body = await this.ctx.http.request({ url: "https://api.example.com/status", method: "GET" });
```
此时 `body` 不能再读取 `body.data`。如果需要响应头、状态码或 `Set-Cookie`,必须设置 `returnOriginRes: true`:
```javascript
const response = await this.ctx.http.request({
url: "https://api.example.com/login",
method: "POST",
returnOriginRes: true,
data: { username, password },
});
const body = response.data;
const setCookie = response.headers?.["set-cookie"];
const cookies = this.ctx.http.getCookies(response);
const sessionId = this.ctx.http.getCookie(response, "session_id");
```
规则:普通 API 直接使用 `response` 作为 body;需要 headers/status/Cookie 时使用 `returnOriginRes: true`,再从 `response.data` 读取 body。首页预请求和登录请求都需要 Cookie 时,两次请求都要设置该选项。`skipSslVerify: true` 仅在用户明确配置忽略证书校验时传入。
## `HttpRequestConfig` 请求参数
`this.ctx.http.request` 接受以下 Certd 扩展参数,同时兼容 Axios 的标准请求配置(如 `url`、`method`、`headers`、`params`、`data`、`timeout`、`responseType` 等):
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `skipSslVerify` | `boolean` | 为 `true` 时关闭 HTTPS 证书校验。仅在授权表单提供了明确开关且用户主动启用时传入。 |
| `skipCheckRes` | `boolean` | Certd 内部兼容参数,在线插件通常不需要设置。 |
| `logReq` | `boolean` | 是否打印请求地址和方法。默认开启;敏感请求可设为 `false`。 |
| `logParams` | `boolean` | 是否打印 URL 查询参数。默认关闭。参数中含密钥、Token 或隐私数据时保持关闭。 |
| `logRes` | `boolean` | 是否打印响应状态和响应体摘要。默认关闭。 |
| `logData` | `boolean` | 是否打印请求体。默认关闭;禁止对包含密码、Token、Cookie 或证书私钥的请求开启。 |
| `httpProxy` | `string` | 本次请求使用的 HTTP 代理地址。没有明确代理需求时不要设置。 |
| `returnOriginRes` | `boolean` | 默认返回响应体 `response.data`;设为 `true` 时返回 Axios 原始响应对象,可读取 `response.data`、`response.headers` 和 `response.status`。获取 `Set-Cookie` 时必须开启。 |
示例:
```javascript
const response = await this.ctx.http.request({
url: `${baseUrl}/Action/login`,
method: "POST",
returnOriginRes: true,
skipSslVerify: this.ignoreTls === true,
logData: false,
headers: { "Content-Type": "application/json" },
data: requestData,
});
const body = response.data;
const cookies = response.headers?.["set-cookie"];
```
## Cookie 和文件下载辅助
原始响应可传给 HTTP 客户端提供的 Cookie 辅助方法:
```javascript
const cookies = this.ctx.http.getCookies(response);
const sessionId = this.ctx.http.getCookie(response, "session_id");
```
`getCookies(response)` 返回 `{ name, value }[]`;没有 `set-cookie` 时返回空数组。`getCookie(response, name)` 找不到目标 Cookie 时返回 `null`。
下载文件可使用 `this.ctx.http.download`,参数为 `{ http, config, savePath, logger }`,`config` 遵循 `HttpRequestConfig`:
```javascript
await this.ctx.http.download({
http: this.ctx.http,
config: { url: fileUrl, method: "GET", logRes: false },
savePath,
logger: this.ctx.logger,
});
```
@@ -47,17 +47,27 @@ content: |
### `content` 规则
- `content` 必须使用 YAML 字面块 `|` 保留脚本换行,禁止使用折叠块 `>`;折叠换行可能导致多个 JavaScript 语句拼接,出现 `Unexpected token 'const'` 等解析错误。
- 统一使用 `await _ctx.import(...)` 加载模块。
- 使用 `_ctx.import("/@/...")` 以绝对路径加载 `certd-server/src/` 下的模块,`/@` 代表 `certd-server/src` 根路径。
- 需要确认模块时使用 `_ctx.logger`,插件执行过程使用 `this.logger`。
- 最后返回继承目标基类的 class。
- 不使用 `import`、`export`、装饰器或独立源码文件语法。
- 输入字段在 class 中声明为同名属性,并与 YAML 的 `input` 配置保持一致。
- HTTP 使用 `this.ctx.http`;
- HTTP 使用 `this.ctx.http`。默认 `request()` 返回响应体 `response.data`;需要 headers、status 或 Set-Cookie 时设置 `returnOriginRes: true`,此时返回原始响应对象,响应体从 `response.data` 读取。请求扩展参数 `skipSslVerify`、`skipCheckRes`、`logReq`、`logParams`、`logRes`、`logData`、`httpProxy`、`returnOriginRes` 的说明见 `context-utils.md`。
- 优先使用 `this.ctx.utils` 中已有的哈希、编码、字符串、域名和请求工具,详见 `context-utils.md`。
- 不要使用 `console.log`。
- 日志打印对象时使用 `JSON.stringify(value)`,不要直接拼接对象输出 `[object Object]`。
- 读取授权使用 `await this.getAccess(accessId)` 或目标基类规定的授权方式。
- 失败时抛出 `Error`,不要吞掉错误。
### 证书域名与列表选项
- 需要使用前置证书域名时,在列表字段之前配置 `certDomains`,组件使用 `cert-domains-getter`,并通过 `inputKey` 绑定证书字段。
- 列表选项可按证书域名匹配时,必须为每个选项设置 `domain`;该字段支持单个域名字符串或域名数组。只有配置该字段后,才使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)`。
- 服务商接口不支持翻页或服务端搜索时,远程选择组件不要配置 `pager: true`、`search: true`;本地搜索可在获取完整列表后过滤。
### 编辑规则
- 修改已有插件时保留 `name`、`author`、`pluginType` 和已有兼容字段。
@@ -14,6 +14,42 @@ description: 用于开发 Certd 在线 Access 插件。输出完整 YAML,conte
- 敏感字段在 input 中设置加密或密码类组件。
- `content` 中实现授权 class 和 API 方法。
## Metadata 示例
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
```yaml
name: DemoAccess
author: certd
fullName: certd/DemoAccess
title: Demo-授权
desc: Demo 平台授权配置
icon: clarity:plugin-line
group: other
version: 1.0.0
pluginType: access
input:
demoKeyId:
title: Key ID
required: true
component:
name: a-input
vModel: value
demoKeySecret:
title: Key Secret
required: true
encrypt: true
component:
name: a-input-password
vModel: value
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口是否正常
```
## `content` 模板
```javascript
@@ -29,7 +65,7 @@ return class DemoAccess extends BaseAccess {
}
async getDomainList(req) {
this.logger.info("获取域名列表", { searchKey: req.searchKey })
this.ctx.logger.info("获取域名列表", { searchKey: req.searchKey })
const res = await this.ctx.http.request({
url: "https://api.example.com/domains",
method: "GET",
@@ -56,7 +92,8 @@ return class DemoAccess extends BaseAccess {
- 所有敏感授权值只通过 `this` 和 Certd 授权上下文使用,不打印真实值。
- `onTestRequest` 应调用实际 API 方法并在失败时抛出异常。
- 对外 API 方法应统一处理分页、错误和返回字段。
- 使用 `this.logger` 或框架提供的 logger,禁止 `console.log`。
- 在线 Access 使用 `this.ctx.logger` 打印日志,禁止使用未注入的 `this.logger` 和 `console.log`。
- 优先复用 `this.ctx.utils` 中的现成工具,例如 `this.ctx.utils.hash.md5` 和 `this.ctx.utils.hash.base64`;HTTP 返回值和 Cookie 处理规则见父 Skill 的 `references/context-utils.md`。
- 一般将API接口方法封装到Access中,其他plugin调用access的方法当做client sdk使用
## 字段动态显隐
@@ -81,7 +118,6 @@ input:
name: a-input
vModel: value
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
return !form.access.authType || form.access.authType === 'aksk';
@@ -94,4 +130,4 @@ input:
- 授权表单的字段值在 `form.access.xxx`(任务表单才是 `form.xxx`)。
- 历史授权没有 `authType` 字段,条件里要用 `!form.access.authType || ...` 兜底走旧逻辑。
完整用法见父 Skill 的 `references/merge-script.md`。
完整用法见父 Skill 的 `references/merge-script.md`。
@@ -13,6 +13,22 @@ description: 用于开发 Certd 在线 DNS Provider 插件。输出完整 YAML
- `input` 中配置授权选择、域名或平台所需的参数。
- `content` 中实现创建和删除 DNS 记录的 class。
## Metadata 示例
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
```yaml
name: DemoDnsProvider
author: certd
fullName: certd/DemoDnsProvider
title: Demo-DNS 解析
desc: Demo 平台 DNS-01 验证
icon: clarity:plugin-line
group: dns
version: 1.0.0
pluginType: dnsProvider
```
## `content` 模板
```javascript
@@ -21,10 +37,10 @@ const { DemoAccess } = await _ctx.import("/@/plugins/plugin-lib/demo/access/inde
_ctx.logger.info("DemoAccess:", DemoAccess)
return class DemoDnsProvider extends AbstractDnsProvider {
accessId
access!: DemoAccess;
async onInstance() {
this.access = await this.getAccess(this.accessId)
this.access = this.ctx.access as DemoAccess;
}
async createRecord(options) {
@@ -14,6 +14,38 @@ description: 用于开发 Certd 在线 Task 插件。输出完整 YAML,脚本
- 输入配置放在 YAML 的 `input` 字段。
- 执行脚本放在 YAML 顶层 `content` 字段。
## Metadata 示例
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
```yaml
name: DemoTask
author: certd
fullName: certd/DemoTask
title: Demo-执行任务
desc: 调用 Demo 平台 API 执行操作
icon: clarity:plugin-line
group: other
version: 1.0.0
pluginType: task
input:
cert:
title: 前置任务证书
required: true
component:
name: cert-select
certDomains:
title: 当前证书域名
required: true
component:
name: cert-domains-getter
accessId:
title: Demo 授权
required: true
component:
name: access-selector
type: demo/DemoAccess # 如果是在线Access插件,要写全称, author/name, 如果是本地插件,只需要写插件名即可
```
## runStrategy
@@ -26,10 +58,11 @@ description: 用于开发 Certd 在线 Task 插件。输出完整 YAML,脚本
- 0 表示成功后跳过,1 表示正常运行。
- 设置任务的默认运行策略runStrategy。
当证书申请任务,申请证书成功后,会清空后续任务状态。此时后续`runStrategy = 0` 的任务,会重新执行。
这样可以保证相同的证书不会重复部署。
在其他场景的部署任务,可能希望每次运行都检查是否需要部署,如果即将过期就部署,没有过期就跳过。
有如下三种场景:
1、证书申请任务,设置为 `default.strategy.runStrategy = 1`,表示正常运行,此时executor每次都会执行plugin的execute方法,当无需更新证书时,返回 `"skip"`。
当申请证书成功后,调用`this.clearLastStatus();`清空后续任务的成功状态,后续任务就都会重新执行。
2、直接部署任务,建议设置为 `default.strategy.runStrategy = 0`,表示成功后跳过,executor参数与状态,如果参数没有变化,且上次的状态是成功或跳过,则直接跳过该任务,这样可以保证相同的证书不会重复部署。
3、检查后再部署的任务,可能希望每次运行都检查是否需要部署,如果即将过期就部署,没有过期就跳过。
此时可以设置 `default.strategy.runStrategy = 1`,然后在`execute`方法中,如果没有过期,就返回 `"skip"`。
## `content` 模板
+2
View File
@@ -30,6 +30,8 @@ version: 1.0.0
- 指定对应的云平台的 access 类型名称
- 设置排序值(可选)
- 继承 `AbstractDnsProvider` 类
- 插件默认文案使用中文;需要英文时,在注解参数对象中添加同级 `i18n: { "en-US": { ... } }`,不要写成 `metadata.i18n`。`define` 本身就是插件 metadata。
- `i18n["en-US"]` 仅配置要翻译的展示字段,如 `title`、`desc`、`input`;输入字段可翻译 `title`、`helper`、组件展示文案和 `optionsMapping`。缺少英文翻译时回退默认中文。
4. **实现 onInstance 方法**
+2
View File
@@ -28,6 +28,8 @@ version: 1.0.0
- 设置插件分组
- 配置默认策略(如 `SkipWhenSucceed`)
- 确保类名与插件名称一致
- 插件默认文案使用中文;需要英文时,在注解参数对象中添加同级 `i18n: { "en-US": { ... } }`,不要写成 `metadata.i18n`。`define` 本身就是插件 metadata。
- `i18n["en-US"]` 仅配置要翻译的展示字段,如 `title`、`desc`、`group`、`input`、`output`;未配置的字段自动回退默认中文。不要翻译插件标识、组件类型、默认值或执行参数。
3. **定义任务输入参数**
+34
View File
@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
### Bug Fixes
* 修复修改密码时校验表单字段的错误 ([28e4f70](https://github.com/certd/certd/commit/28e4f70e494f7c56202a8ca4708718c836fff702))
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Bug Fixes
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
### Performance Improvements
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
* **baota:** 宝塔站点全自动部署插件支持部署docker站点 ([61ef971](https://github.com/certd/certd/commit/61ef971518eeccbc31b09c06ec1b48589929d6bb))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Bug Fixes
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
* **aliyun-cert:** 阿里云订阅证书增加申请请求接口 ([949a69f](https://github.com/certd/certd/commit/949a69f0dcf929980363fc2b3d772edae696dcaf))
### Performance Improvements
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
+51
View File
@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Bug Fixes
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
### Performance Improvements
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
* **baota:** 宝塔站点全自动部署插件支持部署docker站点 ([61ef971](https://github.com/certd/certd/commit/61ef971518eeccbc31b09c06ec1b48589929d6bb))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Bug Fixes
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
* **aliyun-cert:** 阿里云订阅证书增加申请请求接口 ([949a69f](https://github.com/certd/certd/commit/949a69f0dcf929980363fc2b3d772edae696dcaf))
### Performance Improvements
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
* [BUG]使用天翼云CDN部署证书时无法匹配域名 ([bb1f232](https://github.com/certd/certd/commit/bb1f232f415c522851dede979f5d609dda844fd3))
* 缺失运行时嵌套依赖时添加清除缓存提示 ([2d0b85b](https://github.com/certd/certd/commit/2d0b85b3054fc1aa82b3ab5db34a2a56308f71f2))
* 支持google persist校验方式 ([97d2b50](https://github.com/certd/certd/commit/97d2b5007409ec79ea864d437c473a2f9b5dfae4))
* **config:** 修复环境变量加载逻辑并更新配置文件 ([07f0d3b](https://github.com/certd/certd/commit/07f0d3b4d626628a0e3dfc7e5c52e8c4a98bad47))
* **ctyun-cdn:** 修复天翼云CDN部署插件的域名选择问题 ([ea7d14c](https://github.com/certd/certd/commit/ea7d14c289b8720781bf101bfef59eac7bb03765))
* **webhook:** 修复模版转义换行符,避免手动上传的证书报错 ([2735feb](https://github.com/certd/certd/commit/2735feb3302f8308708dafc8b295adcd4b91d7f0))
### Features
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
### Performance Improvements
* 宝塔站点列表支持搜索和排序 ([4420472](https://github.com/certd/certd/commit/4420472f8fac272d30fb8ccb1cc2ee9763989001))
* 网宿插件双鉴权支持 ([241b2e5](https://github.com/certd/certd/commit/241b2e55848471e61b5d2be0c87a7dc9a0d29557))
* **monitor:** 站点证书监控支持手动选中触发检查 ([f60e63f](https://github.com/certd/certd/commit/f60e63fbaf00460d5050cd7705bd75d57dec202d))
* **open-api:** openkey增加用途备注 ([bbeaf61](https://github.com/certd/certd/commit/bbeaf617d19750515f6eca51863e74e23bc1de50))
* **tencent clb:** 修改所有CLB地域的endpoint为就近接入 ([c427735](https://github.com/certd/certd/commit/c4277350bcc8cf034cf4e76db2a6daa1842bc5d7))
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
### Bug Fixes
+2 -2
View File
@@ -13,7 +13,7 @@
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **主机-复制到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 |
| 1.| **主机-复制证书到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 |
| 2.| **主机-执行远程主机脚本命令** | 可以执行重启nginx等操作让证书生效 |
| 3.| **IIS-部署到IIS站点** | |
| 4.| **上传证书到对象存储OSS** | 支持阿里云OSS、腾讯云COS、七牛云KODO、S3、MinIO、FTP、SFTP |
@@ -51,7 +51,7 @@
| 24.| **易盾-部署到易盾RCDN** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 25.| **雨云-更新证书** | app.rainyun.com |
| 26.| **又拍云-部署证书到CDN/USS** | 支持又拍云CDN,又拍云云存储USS |
| 27.| **网宿-更新证书** | 网宿证书自动更新 |
| 27.| **网宿-更新证书** | 网宿证书自动更新,注意:仅支持普通CDN证书更新,CDN pro的证书请安装在线插件“网宿-CDNPro更新证书” |
| 28.| **西数-部署到虚拟主机** | 西部数码部署证书到虚拟主机 |
| 29.| **我爱云-部署证书到我爱云CDN** | 部署证书到我爱云CDN |
| 30.| **Zenlayer-刷新证书** | 刷新Zenlayer CDN证书 |
+1 -1
View File
@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.45.0"
"version": "1.45.3"
}
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/publishlab/node-acme-client/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/acme-client
## [1.45.2](https://github.com/publishlab/node-acme-client/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/acme-client
## [1.45.1](https://github.com/publishlab/node-acme-client/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/acme-client
# [1.45.0](https://github.com/publishlab/node-acme-client/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
+3 -3
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.45.0",
"version": "1.45.3",
"type": "module",
"module": "./dist/index.js",
"main": "./dist/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.45.0",
"@certd/basic": "^1.45.3",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.9.0",
@@ -77,5 +77,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/basic
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Performance Improvements
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/basic
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
+1 -1
View File
@@ -1 +1 @@
00:51
10:19
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.45.0",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -54,5 +54,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+1
View File
@@ -59,5 +59,6 @@ export const utils = {
locker,
mitter,
amount: amountUtils,
//废弃
request,
};
@@ -1,5 +1,5 @@
import { expect } from "chai";
import { createAgent, createAxiosService, getGlobalAgents, HttpClient, isNoProxyMatched, setGlobalHeaders, setGlobalProxy } from "./util.request.js";
import { createAgent, createAxiosService, getCookie, getCookies, getGlobalAgents, HttpClient, isNoProxyMatched, setGlobalHeaders, setGlobalProxy } from "./util.request.js";
import { ILogger } from "./util.log.js";
const testLogger = {
@@ -167,9 +167,40 @@ describe("util.request", () => {
expect(isNoProxyMatched("192.168.*", { hostname: "192.169.1.10", port: "" })).to.equal(false);
expect(isNoProxyMatched("[::1]", { hostname: "::1", port: "" })).to.equal(true);
expect(isNoProxyMatched("[::1]:8443", { hostname: "::1", port: "8443" })).to.equal(true);
expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "8443" })).to.equal(true);
expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "443" })).to.equal(false);
expect(isNoProxyMatched("127.0.0.1", { hostname: "127.0.0.2", port: "" })).to.equal(false);
expect(isNoProxyMatched(".example.com", { hostname: "example.org", port: "" })).to.equal(false);
});
it("should parse cookies from array set-cookie header", () => {
const response = {
headers: {
"set-cookie": ["JSESSIONID=abc123; Path=/; HttpOnly", "token=xyz=789; Expires=Wed, 21 Oct 2026 07:28:00 GMT"],
},
};
expect(getCookies(response)).to.deep.equal([
{ name: "JSESSIONID", value: "abc123" },
{ name: "token", value: "xyz=789" },
]);
expect(getCookie(response, "JSESSIONID")).to.equal("abc123");
expect(getCookie(response, "token")).to.equal("xyz=789");
expect(getCookie(response, "not-exists")).to.equal(null);
});
it("should parse cookies from string set-cookie header", () => {
const response = {
headers: {
"set-cookie": "JSESSIONID=abc123; Path=/",
},
};
expect(getCookies(response)).to.deep.equal([{ name: "JSESSIONID", value: "abc123" }]);
});
it("should return empty cookies when set-cookie header is missing", () => {
expect(getCookies({ headers: {} })).to.deep.equal([]);
expect(getCookies({})).to.deep.equal([]);
expect(getCookies(undefined)).to.deep.equal([]);
});
});
+61 -10
View File
@@ -1,4 +1,4 @@
import axios, { AxiosHeaders, AxiosRequestConfig } from "axios";
import axios, { AxiosHeaders, AxiosRequestConfig, AxiosResponse } from "axios";
import { ILogger, logger } from "./util.log.js";
import { HttpProxyAgent } from "http-proxy-agent";
import { HttpsProxyAgent } from "https-proxy-agent";
@@ -118,7 +118,7 @@ export function getGlobalHeaders() {
/**
* @description 创建请求实例
*/
export function createAxiosService({ logger }: { logger: ILogger }) {
export function createAxiosService({ logger }: { logger: ILogger }): HttpClient {
// 创建一个 axios 实例
const service = axios.create();
@@ -344,14 +344,24 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
return Promise.reject(err);
}
);
return service;
return {
...service,
request: service.request,
getCookies,
getCookie,
download,
};
}
//@ts-ignore
export const http = createAxiosService({ logger }) as HttpClient;
//默认返回 response.data 。 可以通过 returnOriginRes 来返回原始响应。
export type HttpClientResponse<R> = any;
export type HttpRequestConfig<D = any> = {
skipSslVerify?: boolean;
skipCheckRes?: boolean;
skipCheckRes?: boolean; //非标参数,不需要让用户知道
logReq?: boolean;
logParams?: boolean;
logRes?: boolean;
@@ -359,8 +369,18 @@ export type HttpRequestConfig<D = any> = {
httpProxy?: string;
returnOriginRes?: boolean;
} & AxiosRequestConfig<D>;
export type DownloadReq = {
http: HttpClient;
config: HttpRequestConfig;
savePath: string;
logger: ILogger;
};
export type HttpClient = {
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;
getCookies: (response: AxiosResponse) => { name: string; value: string }[];
getCookie: (response: AxiosResponse, name: string) => string | null;
download: (config: DownloadReq) => Promise<boolean>;
};
// const http_proxy_backup = process.env.HTTP_PROXY || process.env.http_proxy;
@@ -540,7 +560,7 @@ function splitNoProxyRules(noProxy?: string) {
.filter(Boolean);
}
export async function download(req: { http: HttpClient; config: HttpRequestConfig; savePath: string; logger: ILogger }) {
export async function download(req: DownloadReq): Promise<boolean> {
const { http, config, savePath, logger } = req;
return safePromise((resolve, reject) => {
http
@@ -581,13 +601,44 @@ export async function download(req: { http: HttpClient; config: HttpRequestConfi
});
}
/**
* 解析响应头中的 set-cookie
*
* Node 环境下 set-cookie 是字符串数组,每个元素是一个完整 Cookie:
* `name=value; Path=/; HttpOnly; Expires=Wed, 21 Oct 2026 07:28:00 GMT`。
* 只取分号前的 name=value,避免把 Path、Expires 等属性误解析成 Cookie;
* value 本身可能包含 "="(如 base64),所以按第一个 "=" 切分。
*/
export function getCookies(response: any): { name: string; value: string }[] {
const setCookie = response?.headers?.["set-cookie"];
if (!setCookie) {
return [];
}
const cookieList: string[] = Array.isArray(setCookie) ? setCookie : [setCookie];
const cookies: { name: string; value: string }[] = [];
for (const cookieItem of cookieList) {
const cookiePair = String(cookieItem).split(";")[0].trim();
if (!cookiePair) {
continue;
}
const equalIndex = cookiePair.indexOf("=");
if (equalIndex <= 0) {
continue;
}
const name = cookiePair.substring(0, equalIndex).trim();
const value = cookiePair.substring(equalIndex + 1).trim();
cookies.push({ name, value });
}
return cookies;
}
export function getCookie(response: any, name: string) {
const cookies = response.headers["set-cookie"];
//根据name 返回对应的cookie
const found = cookies.find((cookie: any) => cookie.includes(name));
const cookies = getCookies(response);
const found = cookies.find((cookie: any) => cookie.name === name);
if (!found) {
return null;
}
const cookie = found.split(";")[0];
return cookie.substring(cookie.indexOf("=") + 1);
return found.value;
}
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/pipeline
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Performance Improvements
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Performance Improvements
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Bug Fixes
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.45.0",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -21,8 +21,8 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/basic": "^1.45.0",
"@certd/plus-core": "^1.45.0",
"@certd/basic": "^1.45.3",
"@certd/plus-core": "^1.45.3",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.2.2"
@@ -51,5 +51,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+3
View File
@@ -19,6 +19,7 @@ export type AccessInputDefine = FormItemProps & {
encrypt?: boolean;
};
export type AccessDefine = Registrable & {
i18n?: Record<string, any>;
icon?: string;
subtype?: string;
dependPlugins?: Record<string, string>;
@@ -48,6 +49,7 @@ export type AccessContext = {
export abstract class BaseAccess implements IAccess {
ctx!: AccessContext;
logger!: ILogger;
async importRuntime(specifier: string) {
return await getRuntimeDepsService().importRuntime(specifier, this.ctx.logger);
@@ -55,6 +57,7 @@ export abstract class BaseAccess implements IAccess {
async setCtx(ctx: AccessContext) {
this.ctx = ctx;
this.logger = ctx.logger;
}
async onRequest(req: AccessRequestHandleReq) {
@@ -44,6 +44,7 @@ export type NotificationDefine = Registrable & {
input?: {
[key: string]: NotificationInputDefine;
};
i18n?: Record<string, any>;
};
export type NotificationInstanceConfig = {
+2
View File
@@ -43,6 +43,7 @@ export type TaskInputDefine = {
} & FormItemProps;
export type PluginDefine = Registrable & {
i18n?: Record<string, any>;
default?: any;
group?: string;
icon?: string;
@@ -97,6 +98,7 @@ export type CertTargetItem = {
export type PipelineEvent<T = any> = {
cert: T;
certPassword?: string;
runnableId: string;
pipelineId: number;
};
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-huawei
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-huawei
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/lib-huawei
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/lib-huawei
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.45.0",
"version": "1.45.3",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -31,5 +31,5 @@
"prettier": "3.3.3",
"tslib": "^2.8.1"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-iframe
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-iframe
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/lib-iframe
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/lib-iframe
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.45.0",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -37,5 +37,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/jdcloud
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/jdcloud
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/jdcloud
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/jdcloud
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.45.0",
"version": "1.45.3",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -63,5 +63,5 @@
"fetch"
]
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-k8s
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-k8s
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/lib-k8s
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/lib-k8s
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.45.0",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -21,7 +21,7 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/basic": "^1.45.0",
"@certd/basic": "^1.45.3",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/lib-server
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/lib-server
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Performance Improvements
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Features
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.45.0",
"version": "1.45.3",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -29,11 +29,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.45.0",
"@certd/basic": "^1.45.0",
"@certd/pipeline": "^1.45.0",
"@certd/plugin-lib": "^1.45.0",
"@certd/plus-core": "^1.45.0",
"@certd/acme-client": "^1.45.3",
"@certd/basic": "^1.45.3",
"@certd/pipeline": "^1.45.3",
"@certd/plugin-lib": "^1.45.3",
"@certd/plus-core": "^1.45.3",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",
@@ -69,5 +69,5 @@
"typeorm": "^0.3.20",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
@@ -26,6 +26,7 @@ export type AddonDefine = Registrable & {
};
showTest?: boolean;
icon?: string;
i18n?: Record<string, any>;
};
export type AddonInstanceConfig = {
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/midway-flyway-js
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/midway-flyway-js
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.45.0",
"version": "1.45.3",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -52,5 +52,5 @@
"typeorm": "^0.3.20",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/plugin-cert
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/plugin-cert
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
**Note:** Version bump only for package @certd/plugin-cert
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/plugin-cert
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.45.0",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/plugin-lib": "^1.45.0"
"@certd/plugin-lib": "^1.45.3"
},
"devDependencies": {
"@types/chai": "^4.3.12",
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
**Note:** Version bump only for package @certd/plugin-lib
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
**Note:** Version bump only for package @certd/plugin-lib
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Performance Improvements
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
**Note:** Version bump only for package @certd/plugin-lib
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.45.0",
"version": "1.45.3",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,9 +17,9 @@
"lint": "eslint --fix --ext .ts src"
},
"dependencies": {
"@certd/acme-client": "^1.45.0",
"@certd/basic": "^1.45.0",
"@certd/pipeline": "^1.45.0",
"@certd/acme-client": "^1.45.3",
"@certd/basic": "^1.45.3",
"@certd/pipeline": "^1.45.3",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21",
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "14809127d6cd3aa8e2a7c5fab6b1f35e95e45e43"
"gitHead": "9f15bb8a2151e2dc948589c99a1c724e4811e2a9"
}
@@ -3,6 +3,7 @@ import { IAccess, IAccessService, IServiceGetter, PageRes, PageSearch, Registrab
export type DnsProviderDefine = Registrable & {
accessType: string;
i18n?: Record<string, any>;
icon?: string;
dependPlugins?: Record<string, string>;
dependPackages?: Record<string, string>;
+31
View File
@@ -3,6 +3,37 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.45.3](https://github.com/certd/certd/compare/v1.45.2...v1.45.3) (2026-10-19)
### Bug Fixes
* 修复修改密码时校验表单字段的错误 ([28e4f70](https://github.com/certd/certd/commit/28e4f70e494f7c56202a8ca4708718c836fff702))
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
### Bug Fixes
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
### Performance Improvements
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
### Bug Fixes
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
### Performance Improvements
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
### Features
@@ -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.45.0",
"version": "1.45.3",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -34,11 +34,11 @@
"@aws-sdk/s3-request-presigner": "^3.964.0",
"@certd/vue-js-cron-light": "^4.0.14",
"@ctrl/tinycolor": "^4.1.0",
"@fast-crud/editor-code": "^1.28.7",
"@fast-crud/fast-crud": "^1.28.7",
"@fast-crud/fast-extends": "^1.28.7",
"@fast-crud/ui-antdv4": "^1.28.7",
"@fast-crud/ui-interface": "^1.28.7",
"@fast-crud/editor-code": "^1.29.0",
"@fast-crud/fast-crud": "^1.29.0",
"@fast-crud/fast-extends": "^1.29.0",
"@fast-crud/ui-antdv4": "^1.29.0",
"@fast-crud/ui-interface": "^1.29.0",
"@iconify/tailwind": "^1.2.0",
"@iconify/vue": "^4.1.1",
"@manypkg/get-packages": "^2.2.2",
@@ -104,8 +104,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.45.0",
"@certd/pipeline": "^1.45.0",
"@certd/lib-iframe": "^1.45.3",
"@certd/pipeline": "^1.45.3",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -4,6 +4,7 @@ import { errorLog, errorCreate } from "./tools";
import { env } from "/src/utils/util.env";
import { useUserStore } from "/@/store/user";
import { useProjectStore } from "../store/project";
import { i18n } from "/@/locales";
export class CodeError extends Error {
code: number;
@@ -139,6 +140,7 @@ function createRequestFunction(service: any) {
const configDefault: any = {
headers: {
"Content-Type": get(config, "headers.Content-Type", "application/json"),
"Accept-Language": i18n.global.locale.value,
} as any,
timeout: 30000,
baseURL: env.API,
@@ -154,6 +156,10 @@ function createRequestFunction(service: any) {
configDefault.headers.Authorization = token;
}
Object.assign(configDefault, config);
configDefault.headers = {
...configDefault.headers,
"Accept-Language": i18n.global.locale.value,
};
if (!configDefault.params.projectId && projectStore.isEnterprise && !config.url.startsWith("/sys") && !config.url.startsWith("http")) {
configDefault.params.projectId = projectStore.currentProject?.id;
@@ -1,11 +1,11 @@
<template>
<div class="flex captcha-image-input">
<a-input :value="valueRef" :placeholder="t('certd.captcha.inputImageCode')" autocomplete="off" @update:value="onChange">
<a-input :value="valueRef" :placeholder="t('certd.common.captcha.inputImageCode')" autocomplete="off" @update:value="onChange">
<template #prefix>
<fs-icon icon="ion:image-outline"></fs-icon>
</template>
</a-input>
<div class="input-right pointer" :title="t('certd.captcha.refresh')">
<div class="input-right pointer" :title="t('certd.common.captcha.refresh')">
<img class="image-code" :src="imageCodeSrc" @click="resetImageCode" />
</div>
</div>
@@ -4,8 +4,8 @@
<div class="sweep-animation"></div>
<div class="box-content">
<div class="box-icon">✓</div>
<span v-if="modelValue == null" class="status-text">{{ t("certd.captcha.clickToVerify") }}</span>
<span v-else class="status-text">{{ t("certd.captcha.verifySuccess") }}</span>
<span v-if="modelValue == null" class="status-text">{{ t("certd.common.captcha.clickToVerify") }}</span>
<span v-else class="status-text">{{ t("certd.common.captcha.verifySuccess") }}</span>
</div>
</div>
</div>
@@ -58,7 +58,7 @@ function callback(res: { ret: number; ticket: string; randstr: string; errorCode
if (res.errorCode && res.errorCode > 0) {
notification.error({
message: t("certd.captcha.verifyFailed", { message: res.errorMessage || res.errorCode }),
message: t("certd.common.captcha.verifyFailed", { message: res.errorMessage || res.errorCode }),
});
}
@@ -85,13 +85,13 @@ function loadErrorCallback(error: any) {
// errorMessage: "jsload_error",
// });
notification.error({
message: t("certd.captcha.loadFailed", { message: error?.message || error }),
message: t("certd.common.captcha.loadFailed", { message: error?.message || error }),
});
}
async function triggerCaptcha() {
if (!loaded.value) {
notification.error({
message: t("certd.captcha.notLoaded"),
message: t("certd.common.captcha.notLoaded"),
});
return;
}
@@ -1,13 +1,16 @@
<template>
<div class="pem-input">
<FileInput v-bind="fileInput" class="mb-5" type="primary" text="选择文件" @change="onChange" />
<div class="pem-input__dropzone" :class="{ 'pem-input__dropzone--active': isDragging }" @dragenter.prevent="onDragEnter" @dragover.prevent="onDragOver" @dragleave.prevent="onDragLeave" @drop.prevent="onDrop">
<FileInput v-bind="fileInput" class="pem-input__file" type="primary" text="选择文件" @change="onChange" />
<span>或将文件拖拽到此处</span>
</div>
<a-textarea placeholder="或直接粘贴" v-bind="textarea" :value="modelValue" @update:value="emitValue"></a-textarea>
</div>
</template>
<script setup lang="ts">
import { notification } from "ant-design-vue";
import { ref, watch, defineEmits } from "vue";
import { ref, defineEmits } from "vue";
import FileInput from "/@/components/file-input.vue";
const props = defineProps<{
@@ -17,26 +20,57 @@ const props = defineProps<{
}>();
const emit = defineEmits(["update:modelValue"]);
const isDragging = ref(false);
function readFile(file?: File) {
if (!file) {
return;
}
if (file.size > 100 * 1024) {
notification.error({
message: "文件超过100k,请选择正确的文件",
});
return;
}
const fileReader = new FileReader();
fileReader.onload = function (event: ProgressEvent<FileReader>) {
const value = event.target?.result;
if (typeof value === "string") {
emitValue(value);
}
};
fileReader.readAsText(file);
}
function onDragEnter() {
isDragging.value = true;
}
function onDragOver() {
isDragging.value = true;
}
function onDragLeave(event: DragEvent) {
const currentTarget = event.currentTarget as HTMLElement;
const relatedTarget = event.relatedTarget as Node | null;
if (!relatedTarget || !currentTarget.contains(relatedTarget)) {
isDragging.value = false;
}
}
function onDrop(event: DragEvent) {
isDragging.value = false;
readFile(event.dataTransfer?.files[0]);
}
function emitValue(value: string) {
emit("update:modelValue", value);
}
function onChange(e: any) {
const file = e.target.files[0];
const size = file.size;
if (size > 100 * 1024) {
notification.error({
message: "文件超过100k,请选择正确的文件",
});
return;
}
const fileReader = new FileReader();
fileReader.onload = function (e: any) {
const value = e.target.result;
emitValue(value);
};
fileReader.readAsText(file); // 以文本形式读取文件
readFile(e.target.files?.[0]);
}
</script>
@@ -45,5 +79,26 @@ function onChange(e: any) {
display: flex;
flex-direction: column;
gap: 5px;
&__dropzone {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
transition:
border-color 0.2s,
background-color 0.2s;
}
&__dropzone--active {
border-color: #1677ff;
background-color: #e6f4ff;
}
&__file {
margin-bottom: 0;
}
}
</style>
@@ -5,6 +5,7 @@
<script lang="ts">
import { ref } from "vue";
import * as api from "./api";
import { resolvePluginLocale } from "/@/store/plugin";
export default {
name: "DnsProviderSelector",
@@ -21,12 +22,13 @@ export default {
async function onCreate() {
const list = await api.GetList();
const array: any[] = [];
for (let item of list) {
for (const item of list) {
const localizedItem = resolvePluginLocale(item);
array.push({
value: item.name,
label: item.title,
icon: item.icon,
accessType: item.accessType,
value: localizedItem.name,
label: localizedItem.title,
icon: localizedItem.icon,
accessType: localizedItem.accessType,
});
}
options.value = array;
@@ -4,7 +4,7 @@
<!-- <td class="domain">-->
<!-- {{ props.domain }}-->
<!-- </td>-->
<td class="host-record" :title="t('certd.verifyPlan.domainTitle', { domain: props.domain })">
<td class="host-record" :title="t('certd.certdomain.verifyPlan.domainTitle', { domain: props.domain })">
<fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable>
</td>
<td style="text-align: center">CNAME</td>
@@ -17,18 +17,18 @@
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
</a-tooltip>
<a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.verifyPlan.resetStatusTooltip')">
<a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.certdomain.verifyPlan.resetStatusTooltip')">
<fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
</a-tooltip>
</span>
</td>
<td class="center">
<template v-if="cnameRecord.status !== 'valid'">
<a-button type="primary" size="small" :loading="loading" @click="doVerify">{{ t("certd.verifyPlan.clickToValidate") }}</a-button>
<a-button type="primary" size="small" :loading="loading" @click="doVerify">{{ t("certd.certdomain.verifyPlan.clickToValidate") }}</a-button>
<cname-tip :record="cnameRecord"></cname-tip>
</template>
<div v-else class="helper" :title="t('certd.verifyPlan.keepCnameTitle')">{{ t("certd.verifyPlan.keepCname") }}</div>
<div v-else class="helper" :title="t('certd.certdomain.verifyPlan.keepCnameTitle')">{{ t("certd.certdomain.verifyPlan.keepCname") }}</div>
</td>
</tr>
</tbody>
@@ -46,11 +46,11 @@ import { utils } from "/@/utils/index.js";
const { t } = useI18n();
const statusDict = dict({
data: [
{ label: t("certd.verifyPlan.status.pendingCname"), value: "cname", color: "warning" },
{ label: t("certd.verifyPlan.status.validating"), value: "validating", color: "blue" },
{ label: t("certd.verifyPlan.status.valid"), value: "valid", color: "green" },
{ label: t("certd.verifyPlan.status.failed"), value: "failed", color: "red" },
{ label: t("certd.verifyPlan.status.timeout"), value: "timeout", color: "red" },
{ label: t("certd.certdomain.verifyPlan.status.pendingCname"), value: "cname", color: "warning" },
{ label: t("certd.certdomain.verifyPlan.status.validating"), value: "validating", color: "blue" },
{ label: t("certd.certdomain.verifyPlan.status.valid"), value: "valid", color: "green" },
{ label: t("certd.certdomain.verifyPlan.status.failed"), value: "failed", color: "red" },
{ label: t("certd.certdomain.verifyPlan.status.timeout"), value: "timeout", color: "red" },
],
});
@@ -129,8 +129,8 @@ async function doVerify() {
async function resetStatus() {
Modal.confirm({
title: t("certd.verifyPlan.resetStatus"),
content: t("certd.verifyPlan.confirmResetStatus"),
title: t("certd.certdomain.verifyPlan.resetStatus"),
content: t("certd.certdomain.verifyPlan.confirmResetStatus"),
onOk: async () => {
await api.ResetStatus(cnameRecord.value.id);
await loadRecord();
@@ -2,17 +2,17 @@
<a-tooltip :overlay-style="{ maxWidth: '400px' }">
<template #title>
<div>
<div>{{ t("certd.verifyPlan.cnameTip.intro") }}</div>
<div>{{ t("certd.verifyPlan.cnameTip.step1", { domain: record.domain }) }}</div>
<div>{{ t("certd.verifyPlan.cnameTip.step2") }}</div>
<div>{{ t("certd.verifyPlan.cnameTip.step3", { value: record.recordValue }) }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.intro") }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step1", { domain: record.domain }) }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step2") }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step3", { value: record.recordValue }) }}</div>
<div>
{{ t("certd.verifyPlan.cnameTip.step4") }}
{{ t("certd.certdomain.verifyPlan.cnameTip.step4") }}
<fs-copyable :style="{ color: '#52c41a' }" :model-value="nslookupCmd"></fs-copyable>
{{ t("certd.verifyPlan.cnameTip.or") }}
{{ t("certd.certdomain.verifyPlan.cnameTip.or") }}
<fs-copyable :style="{ color: '#52c41a' }" :model-value="digCmd"></fs-copyable>
</div>
<div>{{ t("certd.verifyPlan.cnameTip.step5") }}</div>
<div>{{ t("certd.certdomain.verifyPlan.cnameTip.step5") }}</div>
</div>
</template>
<fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon>
@@ -2,11 +2,11 @@
<table class="cname-verify-plan">
<thead>
<tr>
<td class="col-host">{{ t("certd.verifyPlan.hostRecord") }}</td>
<td class="col-type center">{{ t("certd.verifyPlan.recordType") }}</td>
<td class="col-value">{{ t("certd.verifyPlan.setCnameRecord") }}</td>
<td class="col-status center">{{ t("certd.status") }}</td>
<td class="col-action center">{{ t("certd.verifyPlan.operation") }}</td>
<td class="col-host">{{ t("certd.certdomain.verifyPlan.hostRecord") }}</td>
<td class="col-type center">{{ t("certd.certdomain.verifyPlan.recordType") }}</td>
<td class="col-value">{{ t("certd.certdomain.verifyPlan.setCnameRecord") }}</td>
<td class="col-status center">{{ t("certd.common.status") }}</td>
<td class="col-action center">{{ t("certd.certdomain.verifyPlan.operation") }}</td>
</tr>
</thead>
<template v-for="key in domains" :key="key">
@@ -2,10 +2,10 @@
<table class="http-verify-plan">
<thead>
<tr>
<td style="width: 160px">{{ t("certd.verifyPlan.websiteDomain") }}</td>
<td style="width: 100px; text-align: center">{{ t("certd.verifyPlan.uploadMethod") }}</td>
<td style="width: 150px">{{ t("certd.verifyPlan.uploadAccess") }}</td>
<td style="width: 200px">{{ t("certd.verifyPlan.websiteRootPath") }}</td>
<td style="width: 160px">{{ t("certd.certdomain.verifyPlan.websiteDomain") }}</td>
<td style="width: 100px; text-align: center">{{ t("certd.certdomain.verifyPlan.uploadMethod") }}</td>
<td style="width: 150px">{{ t("certd.certdomain.verifyPlan.uploadAccess") }}</td>
<td style="width: 200px">{{ t("certd.certdomain.verifyPlan.websiteRootPath") }}</td>
</tr>
</thead>
<tbody v-if="records" class="http-record-body">
@@ -21,7 +21,7 @@
<access-selector v-model="item.httpUploaderAccess" :type="item.httpUploaderType" @change="onRecordChange"></access-selector>
</td>
<td>
<a-input v-model:value="item.httpUploadRootDir" :placeholder="t('certd.verifyPlan.websiteRootPlaceholder')" @change="onRecordChange"></a-input>
<a-input v-model:value="item.httpUploadRootDir" :placeholder="t('certd.certdomain.verifyPlan.websiteRootPlaceholder')" @change="onRecordChange"></a-input>
</td>
</tr>
</template>
@@ -5,7 +5,7 @@
<div class="plan-box bg-white dark:bg-neutral-700">
<div class="fullscreen-button pointer flex-center" @click="fullscreen = !fullscreen">
<span v-if="!fullscreen" style="font-size: 10px" class="flex-center">
{{ t("certd.verifyPlan.expandTip") }}
{{ t("certd.certdomain.verifyPlan.expandTip") }}
<fs-icon icon="ion:arrow-forward-outline"></fs-icon>
</span>
<fs-icon :icon="fullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'"></fs-icon>
@@ -13,9 +13,9 @@
<table class="plan-table">
<thead>
<tr>
<th style="min-width: 100px">{{ t("certd.verifyPlan.mainDomain") }}</th>
<th>{{ t("certd.verifyPlan.challengeType") }}</th>
<th>{{ t("certd.verifyPlan.challengePlan") }}</th>
<th style="min-width: 100px">{{ t("certd.certdomain.verifyPlan.mainDomain") }}</th>
<th>{{ t("certd.certdomain.verifyPlan.challengeType") }}</th>
<th>{{ t("certd.certdomain.verifyPlan.challengePlan") }}</th>
</tr>
</thead>
<tbody>
@@ -30,13 +30,13 @@
<div class="plan">
<div v-if="item.type === 'dns'" class="plan-dns">
<div class="form-item">
<span class="label">{{ t("certd.verifyPlan.dnsType") }}:</span>
<span class="label">{{ t("certd.certdomain.verifyPlan.dnsType") }}:</span>
<span class="input">
<fs-dict-select
v-model:value="item.dnsProviderType"
size="small"
:dict="dnsProviderTypeDict"
:placeholder="t('certd.verifyPlan.dnsProvider')"
:placeholder="t('certd.certdomain.verifyPlan.dnsProvider')"
@change="onPlanChanged"
@selected-change="onDnsProviderChange(item, $event)"
></fs-dict-select>
@@ -44,13 +44,13 @@
</div>
<a-divider type="vertical" />
<div class="form-item">
<span class="label">{{ t("certd.verifyPlan.dnsAccess") }}:</span>
<span class="label">{{ t("certd.certdomain.verifyPlan.dnsAccess") }}:</span>
<span class="input">
<access-selector
v-model="item.dnsProviderAccessId"
size="small"
:type="item.dnsProviderAccessType || item.dnsProviderType"
:placeholder="t('certd.verifyPlan.pleaseSelect')"
:placeholder="t('certd.certdomain.verifyPlan.pleaseSelect')"
@change="onPlanChanged"
></access-selector>
</span>
@@ -70,7 +70,7 @@
</div>
<div v-if="item.type === 'http'" class="plan-http">
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
<div class="helper">{{ t("certd.verifyPlan.httpHelper") }}</div>
<div class="helper">{{ t("certd.certdomain.verifyPlan.httpHelper") }}</div>
</div>
</div>
</td>
@@ -105,7 +105,7 @@ const { t } = useI18n();
const challengeTypeOptions = ref<any[]>([
{
label: t("certd.verifyPlan.dnsChallenge"),
label: t("certd.certdomain.verifyPlan.dnsChallenge"),
value: "dns",
},
{
@@ -113,11 +113,11 @@ const challengeTypeOptions = ref<any[]>([
value: "dns-persist",
},
{
label: t("certd.verifyPlan.cnameChallenge"),
label: t("certd.certdomain.verifyPlan.cnameChallenge"),
value: "cname",
},
{
label: t("certd.verifyPlan.httpChallenge"),
label: t("certd.certdomain.verifyPlan.httpChallenge"),
value: "http",
},
]);
@@ -14,7 +14,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const subDomain of subDomains) {
const plan = value[domain].cnameVerifyPlan[subDomain];
if (plan.status !== "valid") {
throw new Error($t("certd.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
}
}
}
@@ -23,7 +23,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const item of domains) {
//如果有通配符域名则不允许使用http校验
if (item.startsWith("*.")) {
throw new Error($t("certd.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
throw new Error($t("certd.certdomain.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
}
}
@@ -32,19 +32,19 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const subDomain of subDomains) {
const plan = value[domain].httpVerifyPlan[subDomain];
if (!plan.httpUploaderType) {
throw new Error($t("certd.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
}
if (!plan.httpUploaderAccess) {
throw new Error($t("certd.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
}
if (!plan.httpUploadRootDir) {
throw new Error($t("certd.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
}
}
}
} else if (type === "dns") {
if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) {
throw new Error($t("certd.verifyPlan.errors.dnsProviderRequired", { domain }));
throw new Error($t("certd.certdomain.verifyPlan.errors.dnsProviderRequired", { domain }));
}
} else if (type === "dns-persist") {
const subDomains = Object.keys(value[domain].dnsPersistVerifyPlan || {});
@@ -1,7 +1,7 @@
<template>
<div class="api-test">
<div>
<fs-button :loading="loading" type="primary" :text="t('certd.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
<fs-button :loading="loading" type="primary" :text="t('certd.common.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -64,14 +64,14 @@ const doTest = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.errorWithMessage", { message: err.message });
message.value = t("certd.common.pluginCommon.errorWithMessage", { message: err.message });
},
showErrorNotify: false,
}
);
message.value = t("certd.pluginCommon.testRequestSuccess");
message.value = t("certd.common.pluginCommon.testRequestSuccess");
if (res) {
message.value += t("certd.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
message.value += t("certd.common.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
}
} finally {
loading.value = false;
@@ -42,7 +42,7 @@ function findStepFromPipeline(targetStepId: string) {
const errorRef = ref("");
function getStepIdFromInputKey(inputKey: string) {
if (!inputKey) {
errorRef.value = t("certd.pluginCommon.selectCertFirst");
errorRef.value = t("certd.common.pluginCommon.selectCertFirst");
return;
}
return inputKey.split(".")[1];
@@ -51,7 +51,7 @@ function getDomainFromPipeline(inputKey: string) {
let targetStepId = getStepIdFromInputKey(inputKey);
let certStep = findStepFromPipeline(targetStepId);
if (!certStep) {
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
errorRef.value = t("certd.common.pluginCommon.targetStepNotFound");
return;
}
@@ -60,7 +60,7 @@ function getDomainFromPipeline(inputKey: string) {
targetStepId = getStepIdFromInputKey(firstLevelValue);
certStep = findStepFromPipeline(targetStepId);
if (!certStep) {
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
errorRef.value = t("certd.common.pluginCommon.targetStepNotFound");
return;
}
}
@@ -1,4 +1,4 @@
<template>
<template>
<div class="domain-select">
<div class="flex flex-row">
<a-select
@@ -18,12 +18,12 @@
<template v-if="search">
<div class="flex w-full items-center justify-between flex-wrap" style="padding: 4px 8px">
<div class="flex-1 flex flex-row items-center">
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.pluginCommon.search") }}</fs-button>
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.common.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.common.pluginCommon.search") }}</fs-button>
</div>
<div class="manager flex flex-row items-center">
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.pluginCommon.importDomain") }}</fs-button>
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.pluginCommon.manageDomain") }}</fs-button>
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.common.pluginCommon.importDomain") }}</fs-button>
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.common.pluginCommon.manageDomain") }}</fs-button>
</div>
</div>
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
@@ -49,7 +49,7 @@
</a-select>
<div class="ml-5 flex flex-row items-center">
<fs-button title="复制全部域名" icon="ion:copy-outline" class="domain-select-copy-icon mr-5" @click="copySelectedDomains" />
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -8,6 +8,7 @@ import { usePluginStore } from "/@/store/plugin";
export default {
name: "OutputSelector",
// inheritAttrs: false,
props: {
modelValue: {
type: String,
@@ -30,6 +31,9 @@ export default {
const pluginStore = usePluginStore();
// const attrs = ctx.attrs;
// console.log(attrs);
async function onCreate() {
await pluginStore.init();
options.value = pluginStore.group.getPreStepOutputOptions({
@@ -4,7 +4,7 @@
<a-auto-complete class="remote-auto-complete-input" :filter-option="filterOption" :options="optionsRef" :value="value" v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)">
</a-auto-complete>
<div class="ml-5">
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -96,16 +96,16 @@ const getOptions = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
},
showErrorNotify: false,
}
);
const list = res?.list || res || [];
if (list.length > 0) {
message.value = t("certd.pluginCommon.getDataSuccessSelect");
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
} else {
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
}
optionsRef.value = list;
@@ -55,7 +55,7 @@ async function openDialog() {
saveRemind: false,
},
afterSubmit() {
notification.success({ message: t("certd.operationSuccess") });
notification.success({ message: t("certd.common.operationSuccess") });
},
async doSubmit({ form }: any) {
return await doPluginFormSubmit(form);
@@ -5,12 +5,12 @@
<template #dropdownRender="{ menuNode: menu }">
<template v-if="search">
<div class="flex w-full" style="padding: 4px 8px">
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.searchKeyword')" @keydown.enter="doSearch" />
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.common.pluginCommon.searchKeyword')" @keydown.enter="doSearch" />
<a-button class="ml-2" :loading="loading" type="text" @click="doSearch">
<template #icon>
<search-outlined />
</template>
{{ t("certd.pluginCommon.search") }}
{{ t("certd.common.pluginCommon.search") }}
</a-button>
</div>
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
@@ -26,7 +26,7 @@
</template>
</a-select>
<div class="ml-5 flex flex-row no-wrap">
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<UploadCert v-if="uploadCert" class="ml-5" v-bind="uploadCert" @submit="refreshOptions"></UploadCert>
</div>
</div>
@@ -37,7 +37,7 @@
</template>
<script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { defineComponent, inject, ref, useAttrs, watch, Ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils";
@@ -81,6 +81,7 @@ const emit = defineEmits<{
}>();
function updateValue(value: any) {
debugger;
if (props.single === true) {
const last = value?.[value.length - 1];
emit("update:value", last);
@@ -103,8 +104,18 @@ function emitSelectedChange(value: any) {
emit("selected-change", props.single === true ? options[0] : options);
}
const attrs = useAttrs();
const originalAttrs = useAttrs();
const attrs = computed(() => {
const tags = {
mode: "tags",
allowClear: true,
single: false,
};
return {
...tags,
...originalAttrs,
};
});
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => {
return {};
});
@@ -177,7 +188,7 @@ const getOptions = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
optionsRef.value = [];
},
showErrorNotify: false,
@@ -185,9 +196,9 @@ const getOptions = async () => {
);
let list = res?.list || res || [];
if (list.length > 0) {
message.value = t("certd.pluginCommon.getDataSuccessSelect");
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
} else {
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
}
list = list.map((item: any) => {
return {
@@ -3,7 +3,7 @@
<div class="flex flex-row">
<a-tree-select class="remote-tree-select-input" :tree-data="optionsRef" :value="value" tree-checkable allow-clear v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)"> </a-tree-select>
<div class="ml-5">
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -107,16 +107,16 @@ const getOptions = async () => {
{
onError(err: any) {
hasError.value = true;
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
},
showErrorNotify: false,
}
);
const list = res?.list || res || [];
if (list.length > 0) {
message.value = t("certd.pluginCommon.getDataSuccessSelect");
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
} else {
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
}
optionsRef.value = list;
pagerRef.value.total = list.length;
@@ -0,0 +1,135 @@
<template>
<div class="template-textarea">
<a-textarea :value="value" v-bind="$attrs" @update:value="emit('update:value', $event)" />
<div class="template-textarea__toolbar">
<a-dropdown trigger="click">
<a-button>
<span class="template-textarea__selected">{{ selectedName }}</span>
<fs-icon icon="ion:chevron-down" class="ml-1" />
</a-button>
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item v-for="item in templates" :key="item.id">
<div class="template-textarea__menu-item">
<span>{{ item.name }}</span
><a-button type="link" danger size="small" @click.stop="deleteTemplate(item.id)">删除</a-button>
</div>
</a-menu-item>
<a-menu-item v-if="templates.length === 0" disabled>暂无模板</a-menu-item>
<a-menu-divider />
<a-menu-item key="save">保存当前脚本为模板</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { notification } from "ant-design-vue";
import { onMounted, ref, watch } from "vue";
import { request } from "/@/api/service";
import { useProjectStore } from "/@/store/project";
import { useFormDialog } from "/@/use/use-dialog";
import { nanoid } from "nanoid";
defineOptions({ name: "TemplateTextarea", inheritAttrs: false });
const props = withDefaults(defineProps<{ value?: string; templateType?: string }>(), { value: "", templateType: "script" });
const emit = defineEmits<{ "update:value": [value: string] }>();
const templates = ref<Array<{ id: string; name: string; content: string }>>([]);
const selected = ref<string>();
const key = "script_templates";
const { openFormDialog } = useFormDialog();
const selectedName = ref("选择脚本模板");
async function load() {
const entity = await request({ url: "/user/settings/get", method: "post", params: { key } });
try {
templates.value = entity?.setting ? JSON.parse(entity.setting)[props.templateType] || [] : [];
} catch {
templates.value = [];
}
}
async function persist() {
const entity = await request({ url: "/user/settings/get", method: "post", params: { key } });
let setting: Record<string, unknown[]> = {};
try {
setting = entity?.setting ? JSON.parse(entity.setting) : {};
} catch {
setting = {};
}
setting[props.templateType] = templates.value;
await request({
url: "/user/settings/save",
method: "post",
data: {
key,
setting: JSON.stringify(setting),
},
});
}
function handleMenuClick({ key: id }: { key: string }) {
if (id === "save") {
saveTemplate();
return;
}
const item = templates.value.find(template => template.id === id);
if (item) {
selected.value = item.id;
selectedName.value = item.name;
emit("update:value", item.content);
}
}
async function saveTemplate() {
if (!props.value?.trim()) {
notification.warning({ message: "请输入脚本内容后再保存模板" });
return;
}
await openFormDialog({
title: "保存脚本模板",
initialForm: { name: "" },
columns: { name: { title: "模板名称", type: "text", form: { rules: [{ required: true, message: "请输入模板名称" }] } } },
async onSubmit(form: { name: string }) {
const name = form.name?.trim();
if (!name) {
notification.warning({ message: "请输入模板名称" });
throw new Error("模板名称不能为空");
}
const id = nanoid();
templates.value.push({ id, name, content: props.value });
await persist();
selected.value = id;
selectedName.value = name;
notification.success({ message: "模板保存成功" });
},
});
}
async function deleteTemplate(id: string) {
templates.value = templates.value.filter(item => item.id !== id);
if (selected.value === id) {
selected.value = undefined;
selectedName.value = "选择脚本模板";
}
await persist();
notification.success({ message: "模板删除成功" });
}
watch(() => props.templateType, load);
onMounted(load);
</script>
<style lang="less">
.template-textarea__toolbar {
display: flex;
margin-top: 6px;
align-items: center;
}
.template-textarea__selected {
display: inline-block;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
.template-textarea__menu-item {
display: flex;
min-width: 220px;
justify-content: space-between;
align-items: center;
}
</style>
@@ -1,6 +1,6 @@
<template>
<div class="upload-cert">
<fs-button v-model:loading="loading" type="primary" :text="t('certd.pluginCommon.upload')" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
<fs-button v-model:loading="loading" type="primary" :text="t('certd.common.pluginCommon.upload')" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
</div>
</template>
<script lang="ts" setup>
@@ -30,18 +30,18 @@ const getScope: any = inject("get:scope", () => {
const getPluginType: any = inject("get:plugin:type", () => {
return "plugin";
});
const title = computed(() => props.title || t("certd.pluginCommon.uploadCert"));
const title = computed(() => props.title || t("certd.common.pluginCommon.uploadCert"));
function openUploadCertDialog() {
const columns = merge(
{
certName: {
title: t("certd.pluginCommon.certName"),
title: t("certd.common.pluginCommon.certName"),
form: {
component: {
name: "a-input",
vModel: "value",
},
helper: t("certd.pluginCommon.certNameHelper"),
helper: t("certd.common.pluginCommon.certNameHelper"),
},
},
},
@@ -51,7 +51,7 @@ function openUploadCertDialog() {
title: title.value,
columns: {
certName: {
title: t("certd.pluginCommon.certName"),
title: t("certd.common.pluginCommon.certName"),
form: {
component: {
name: "a-input",
@@ -86,7 +86,7 @@ function openUploadCertDialog() {
showErrorNotify: true,
}
);
notification.success({ message: t("certd.pluginCommon.uploadSuccess") });
notification.success({ message: t("certd.common.pluginCommon.uploadSuccess") });
emit("submit");
} finally {
loading.value = false;
@@ -14,6 +14,7 @@ import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
import ApiTest from "./common/api-test.vue";
import RefreshInput from "./common/refresh-input.vue";
import ParamsShow from "./common/params-show.vue";
import TemplateTextarea from "./common/template-textarea.vue";
export * from "./cert/index.js";
export default {
install(app: any) {
@@ -35,5 +36,6 @@ export default {
app.component("DomainSelector", DomainSelector);
app.component("InputPassword", InputPassword);
app.component("ParamsShow", ParamsShow);
app.component("TemplateTextarea", TemplateTextarea);
},
};
@@ -4,10 +4,10 @@ import { $t } from "/@/locales";
function createChallengeTypeDict() {
return dict({
data: [
{ value: "dns", label: $t("certd.verifyPlan.dnsChallenge"), color: "green" },
{ value: "dns", label: $t("certd.certdomain.verifyPlan.dnsChallenge"), color: "green" },
{ value: "dns-persist", label: "DNS持久验证", color: "cyan" },
{ value: "cname", label: $t("certd.verifyPlan.cnameProxyChallenge"), color: "blue" },
{ value: "http", label: $t("certd.verifyPlan.httpChallenge"), color: "yellow" },
{ value: "cname", label: $t("certd.certdomain.verifyPlan.cnameProxyChallenge"), color: "blue" },
{ value: "http", label: $t("certd.certdomain.verifyPlan.httpChallenge"), color: "yellow" },
],
});
}
@@ -18,11 +18,11 @@ function createUploaderTypeDict() {
{ label: "SFTP", value: "sftp" },
{ label: "SCP", value: "scp" },
{ label: "FTP", value: "ftp" },
{ label: $t("certd.verifyPlan.uploader.aliyunOss"), value: "alioss" },
{ label: $t("certd.verifyPlan.uploader.tencentCos"), value: "tencentcos" },
{ label: $t("certd.verifyPlan.uploader.qiniuOss"), value: "qiniuoss" },
{ label: $t("certd.certdomain.verifyPlan.uploader.aliyunOss"), value: "alioss" },
{ label: $t("certd.certdomain.verifyPlan.uploader.tencentCos"), value: "tencentcos" },
{ label: $t("certd.certdomain.verifyPlan.uploader.qiniuOss"), value: "qiniuoss" },
{ label: "S3/Minio", value: "s3" },
{ label: $t("certd.verifyPlan.uploader.sshDeprecated"), value: "ssh", disabled: true },
{ label: $t("certd.certdomain.verifyPlan.uploader.sshDeprecated"), value: "ssh", disabled: true },
],
});
}
@@ -30,8 +30,8 @@ function createUploaderTypeDict() {
function createDomainFromTypeDict() {
return dict({
data: [
{ value: "manual", label: $t("certd.verifyPlan.domainFrom.manual") },
{ value: "auto", label: $t("certd.verifyPlan.domainFrom.auto") },
{ value: "manual", label: $t("certd.certdomain.verifyPlan.domainFrom.manual") },
{ value: "auto", label: $t("certd.certdomain.verifyPlan.domainFrom.auto") },
],
});
}
@@ -33,11 +33,11 @@ const { t } = useI18n();
<div class="tutorial-button pointer" @click="open">
<template v-if="!slots.default">
<fs-icon v-if="showIcon === false" icon="ant-design:question-circle-outlined" class="mr-0.5"></fs-icon>
<div class="hidden md:block">{{ t("tutorial.title") }}</div>
<div class="hidden md:block">{{ t("certd.tutorial.title") }}</div>
</template>
<slot></slot>
<a-modal v-model:open="openedRef" class="tutorial-modal" width="90%">
<template #title>{{ t("tutorial.title") }}</template>
<template #title>{{ t("certd.tutorial.title") }}</template>
<tutorial-steps v-if="openedRef" />
<template #footer></template>
</a-modal>
@@ -18,9 +18,9 @@ import { ref } from "vue";
import { mitter } from "/@/utils/util.mitt";
const steps = ref<Step[]>([
{ title: t("certd.steps.createPipeline"), icon: <fs-icon icon="tabler:circle-number-1-filled"></fs-icon> },
{ title: t("certd.steps.addTask"), icon: <fs-icon icon="tabler:circle-number-2-filled"></fs-icon> },
{ title: t("certd.steps.scheduledRun"), icon: <fs-icon icon="tabler:circle-number-3-filled"></fs-icon> },
{ title: t("certd.tutorial.steps.createPipeline"), icon: <fs-icon icon="tabler:circle-number-1-filled"></fs-icon> },
{ title: t("certd.tutorial.steps.addTask"), icon: <fs-icon icon="tabler:circle-number-2-filled"></fs-icon> },
{ title: t("certd.tutorial.steps.scheduledRun"), icon: <fs-icon icon="tabler:circle-number-3-filled"></fs-icon> },
]);
function goPipeline() {
@@ -20,8 +20,8 @@
</div>
<div class="flex-center actions">
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()">{{ t("guide.buttons.prev") }}</fs-button>
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()">{{ t("guide.buttons.next") }}</fs-button>
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()">{{ t("certd.guide.buttons.prev") }}</fs-button>
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()">{{ t("certd.guide.buttons.next") }}</fs-button>
</div>
</div>
</template>
@@ -52,126 +52,126 @@ import { computed, nextTick, ref } from "vue";
const steps = ref<Step[]>([
{
title: t("guide.createCertPipeline.title"),
description: t("guide.createCertPipeline.description"),
title: t("certd.guide.createCertPipeline.title"),
description: t("certd.guide.createCertPipeline.description"),
items: [
{
title: t("guide.createCertPipeline.items.tutorialTitle"),
descriptions: [t("guide.createCertPipeline.items.tutorialDesc1"), t("guide.createCertPipeline.items.tutorialDesc2")],
title: t("certd.guide.createCertPipeline.items.tutorialTitle"),
descriptions: [t("certd.guide.createCertPipeline.items.tutorialDesc1"), t("certd.guide.createCertPipeline.items.tutorialDesc2")],
body: () => {
return <SimpleSteps></SimpleSteps>;
},
},
{
image: "/static/doc/images/1-add.png",
title: t("guide.createCertPipeline.items.createTitle"),
descriptions: [t("guide.createCertPipeline.items.createDesc")],
title: t("certd.guide.createCertPipeline.items.createTitle"),
descriptions: [t("certd.guide.createCertPipeline.items.createDesc")],
},
{
image: "/static/doc/images/3-add-success.png",
title: t("guide.createCertPipeline.items.successTitle"),
descriptions: [t("guide.createCertPipeline.items.successDesc")],
title: t("certd.guide.createCertPipeline.items.successTitle"),
descriptions: [t("certd.guide.createCertPipeline.items.successDesc")],
},
// {
// title: t("guide.createCertPipeline.items.nextTitle"),
// descriptions: [t("guide.createCertPipeline.items.nextDesc")],
// title: t("certd.guide.createCertPipeline.items.nextTitle"),
// descriptions: [t("certd.guide.createCertPipeline.items.nextDesc")],
// },
],
},
{
title: t("guide.addDeployTask.title"),
description: t("guide.addDeployTask.description"),
title: t("certd.guide.addDeployTask.title"),
description: t("certd.guide.addDeployTask.description"),
items: [
{
image: "/static/doc/images/5-1-add-host.png",
title: t("guide.addDeployTask.items.addTaskTitle"),
descriptions: [t("guide.addDeployTask.items.addTaskDesc1"), t("guide.addDeployTask.items.addTaskDesc2")],
title: t("certd.guide.addDeployTask.items.addTaskTitle"),
descriptions: [t("certd.guide.addDeployTask.items.addTaskDesc1"), t("certd.guide.addDeployTask.items.addTaskDesc2")],
},
{
image: "/static/doc/images/5-2-add-host.png",
title: t("guide.addDeployTask.items.fillParamsTitle"),
descriptions: [t("guide.addDeployTask.items.fillParamsDesc1"), t("guide.addDeployTask.items.fillParamsDesc2")],
title: t("certd.guide.addDeployTask.items.fillParamsTitle"),
descriptions: [t("certd.guide.addDeployTask.items.fillParamsDesc1"), t("certd.guide.addDeployTask.items.fillParamsDesc2")],
},
{
image: "/static/doc/images/5-3-add-host.png",
title: t("guide.addDeployTask.items.activateCertTitle"),
descriptions: [t("guide.addDeployTask.items.activateCertDesc1"), t("guide.addDeployTask.items.activateCertDesc2")],
title: t("certd.guide.addDeployTask.items.activateCertTitle"),
descriptions: [t("certd.guide.addDeployTask.items.activateCertDesc1"), t("certd.guide.addDeployTask.items.activateCertDesc2")],
},
{
image: "/static/doc/images/5-4-add-host.png",
title: t("guide.addDeployTask.items.taskSuccessTitle"),
descriptions: [t("guide.addDeployTask.items.taskSuccessDesc")],
title: t("certd.guide.addDeployTask.items.taskSuccessTitle"),
descriptions: [t("certd.guide.addDeployTask.items.taskSuccessDesc")],
},
{
image: "/static/doc/images/5-5-plugin-list.png",
title: t("guide.addDeployTask.items.pluginsTitle"),
descriptions: [t("guide.addDeployTask.items.pluginsDesc")],
title: t("certd.guide.addDeployTask.items.pluginsTitle"),
descriptions: [t("certd.guide.addDeployTask.items.pluginsDesc")],
},
],
},
{
title: t("guide.runAndTestTask.runAndTestTitle"),
description: t("guide.runAndTestTask.runAndTestDescription"),
title: t("certd.guide.runAndTestTask.runAndTestTitle"),
description: t("certd.guide.runAndTestTask.runAndTestDescription"),
items: [
{
image: "/static/doc/images/9-start.png",
title: t("guide.runAndTestTask.runTestOnce"),
descriptions: [t("guide.runAndTestTask.clickManualTriggerToTest")],
title: t("certd.guide.runAndTestTask.runTestOnce"),
descriptions: [t("certd.guide.runAndTestTask.clickManualTriggerToTest")],
},
{
image: "/static/doc/images/10-1-log.png",
title: t("guide.runAndTestTask.viewLogs"),
descriptions: [t("guide.runAndTestTask.clickTaskToViewStatusAndLogs")],
title: t("certd.guide.runAndTestTask.viewLogs"),
descriptions: [t("certd.guide.runAndTestTask.clickTaskToViewStatusAndLogs")],
},
{
image: "/static/doc/images/11-1-error.png",
title: t("guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("guide.runAndTestTask.viewErrorLogs")],
title: t("certd.guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("certd.guide.runAndTestTask.viewErrorLogs")],
},
{
image: "/static/doc/images/11-2-error.png",
title: t("guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("guide.runAndTestTask.viewErrorLogs"), t("guide.runAndTestTask.nginxContainerNotExistFix")],
title: t("certd.guide.runAndTestTask.howToTroubleshootFailure"),
descriptions: [t("certd.guide.runAndTestTask.viewErrorLogs"), t("certd.guide.runAndTestTask.nginxContainerNotExistFix")],
},
{
image: "/static/doc/images/12-1-log-success.png",
title: t("guide.runAndTestTask.executionSuccess"),
descriptions: [t("guide.runAndTestTask.retryAfterFix")],
title: t("certd.guide.runAndTestTask.executionSuccess"),
descriptions: [t("certd.guide.runAndTestTask.retryAfterFix")],
},
{
image: "/static/doc/images/12-2-skip-log.png",
title: t("guide.runAndTestTask.autoSkipAfterSuccess"),
descriptions: [t("guide.runAndTestTask.successSkipExplanation")],
title: t("certd.guide.runAndTestTask.autoSkipAfterSuccess"),
descriptions: [t("certd.guide.runAndTestTask.successSkipExplanation")],
},
{
image: "/static/doc/images/13-1-result.png",
title: t("guide.runAndTestTask.viewCertDeploymentSuccess"),
descriptions: [t("guide.runAndTestTask.visitNginxToSeeCert")],
title: t("certd.guide.runAndTestTask.viewCertDeploymentSuccess"),
descriptions: [t("certd.guide.runAndTestTask.visitNginxToSeeCert")],
},
{
image: "/static/doc/images/13-3-download.png",
title: t("guide.runAndTestTask.downloadCertManualDeploy"),
descriptions: [t("guide.runAndTestTask.downloadIfNoAutoDeployPlugin")],
title: t("certd.guide.runAndTestTask.downloadCertManualDeploy"),
descriptions: [t("certd.guide.runAndTestTask.downloadIfNoAutoDeployPlugin")],
},
],
},
{
title: t("guide.scheduleAndEmailTask.title"),
description: t("guide.scheduleAndEmailTask.description"),
title: t("certd.guide.scheduleAndEmailTask.title"),
description: t("certd.guide.scheduleAndEmailTask.description"),
items: [
{
image: "/static/doc/images/14-timer.png",
title: t("guide.scheduleAndEmailTask.setSchedule"),
descriptions: [t("guide.scheduleAndEmailTask.pipelineSuccessThenSchedule"), t("guide.scheduleAndEmailTask.recommendDailyRun")],
title: t("certd.guide.scheduleAndEmailTask.setSchedule"),
descriptions: [t("certd.guide.scheduleAndEmailTask.pipelineSuccessThenSchedule"), t("certd.guide.scheduleAndEmailTask.recommendDailyRun")],
},
{
image: "/static/doc/images/15-1-email.png",
title: t("guide.scheduleAndEmailTask.setEmailNotification"),
descriptions: [t("guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails")],
title: t("certd.guide.scheduleAndEmailTask.setEmailNotification"),
descriptions: [t("certd.guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails")],
},
{
title: t("guide.scheduleAndEmailTask.tutorialEndTitle"),
descriptions: [t("guide.scheduleAndEmailTask.thanksForWatching")],
title: t("certd.guide.scheduleAndEmailTask.tutorialEndTitle"),
descriptions: [t("certd.guide.scheduleAndEmailTask.thanksForWatching")],
},
],
},
@@ -48,56 +48,56 @@ const text = computed<Text>(() => {
const map = {
isComm: {
comm: {
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
button: {
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
icon: {
name: "",
title: t("vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.name", { vipLabel }),
},
nav: {
name: t("vip.comm.nav", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.nav", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
},
isPlus: {
comm: {
name: t("vip.plus.name"),
title: t("vip.plus.title"),
name: t("certd.vip.plus.name"),
title: t("certd.vip.plus.title"),
},
button: {
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
icon: {
name: "",
title: t("vip.comm.name", { vipLabel }),
title: t("certd.vip.comm.name", { vipLabel }),
},
nav: {
name: t("vip.comm.nav", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
name: t("certd.vip.comm.nav", { vipLabel }),
title: t("certd.vip.comm.title", { expire: expireTime.value }),
},
},
free: {
comm: {
name: t("vip.free.comm.name"),
title: t("vip.free.comm.title"),
name: t("certd.vip.free.comm.name"),
title: t("certd.vip.free.comm.title"),
},
button: {
name: t("vip.free.button.name"),
title: t("vip.free.button.title"),
name: t("certd.vip.free.button.name"),
title: t("certd.vip.free.button.title"),
},
icon: {
name: "",
title: t("vip.free.button.name"),
title: t("certd.vip.free.button.name"),
},
nav: {
name: t("vip.free.nav.name"),
title: plusMessage || t("vip.free.nav.title"),
name: t("certd.vip.free.nav.name"),
title: plusMessage || t("certd.vip.free.nav.title"),
},
},
};
@@ -131,7 +131,7 @@ const userStore = useUserStore();
async function getVipTrial(vipType = "plus") {
const res = await api.getVipTrial(vipType);
notification.success({ message: t("vip.congratulations_vip_trial", { duration: res.duration }) });
notification.success({ message: t("certd.vip.congratulations_vip_trial", { duration: res.duration }) });
await settingStore.init();
}
@@ -139,8 +139,8 @@ function openTrialModal(vipType = "plus") {
Modal.destroyAll();
modal.confirm({
title: t("vip.trial_modal_title"),
okText: t("vip.trial_modal_ok_text"),
title: t("certd.vip.trial_modal_title"),
okText: t("certd.vip.trial_modal_ok_text"),
onOk() {
getVipTrial(vipType);
},
@@ -148,8 +148,8 @@ function openTrialModal(vipType = "plus") {
content: () => {
return (
<div class="flex-col mt-10 mb-10">
<div>{t("vip.trial_modal_thanks")}</div>
<div>{t("vip.trial_modal_click_confirm", { vipType })}</div>
<div>{t("certd.vip.trial_modal_thanks")}</div>
<div>{t("certd.vip.trial_modal_click_confirm", { vipType })}</div>
</div>
);
},
@@ -158,7 +158,7 @@ function openTrialModal(vipType = "plus") {
function openStarModal(vipType: string) {
if (settingStore.isPlus) {
notification.error({ message: t("vip.already_vip") });
notification.error({ message: t("certd.vip.already_vip") });
return;
}
Modal.destroyAll();
@@ -170,8 +170,8 @@ function openStarModal(vipType: string) {
// };
// modal.confirm({
// title: t("vip.get_7_day_pro_trial"),
// okText: t("vip.star_now"),
// title: t("certd.vip.get_7_day_pro_trial"),
// okText: t("certd.vip.star_now"),
// onOk() {
// goGithub();
// openTrialModal(vipType);
@@ -180,7 +180,7 @@ function openStarModal(vipType: string) {
// content: () => {
// return (
// <div class="flex mt-10 mb-10">
// <div>{t("vip.please_help_star")}</div>
// <div>{t("certd.vip.please_help_star")}</div>
// <img class="ml-5" src="https://img.shields.io/github/stars/certd/certd?logo=github" />
// </div>
// );
@@ -190,16 +190,16 @@ function openStarModal(vipType: string) {
function openUpgrade() {
if (!userStore.isAdmin) {
notification.info({ message: t("vip.admin_only_operation") });
notification.info({ message: t("certd.vip.admin_only_operation") });
return;
}
const placeholder = t("vip.enter_activation_code");
const placeholder = t("certd.vip.enter_activation_code");
const isPlus = settingStore.isPlus;
let title = t("vip.activate_pro_business");
let title = t("certd.vip.activate_pro_business");
if (settingStore.isComm) {
title = t("vip.renew_business");
title = t("certd.vip.renew_business");
} else if (settingStore.isPlus) {
title = t("vip.renew_pro_upgrade_business");
title = t("certd.vip.renew_pro_upgrade_business");
}
// const goBuyUrl = "https://afdian.com/a/greper"
@@ -214,10 +214,10 @@ function openUpgrade() {
function checkPerpetualPlus() {
if (settingStore.isPerpetual) {
Modal.warn({
title: t("vip.already_perpetual_plus"),
okText: t("vip.confirm"),
title: t("certd.vip.already_perpetual_plus"),
okText: t("certd.vip.confirm"),
});
throw new Error(t("vip.already_perpetual_plus"));
throw new Error(t("certd.vip.already_perpetual_plus"));
}
}
@@ -225,8 +225,8 @@ function openUpgrade() {
checkPerpetualPlus();
if (settingStore.isComm) {
Modal.warn({
title: t("vip.already_comm"),
okText: t("vip.confirm"),
title: t("certd.vip.already_comm"),
okText: t("certd.vip.confirm"),
});
return;
}
@@ -237,8 +237,8 @@ function openUpgrade() {
checkPerpetualPlus();
if (settingStore.isPlus && !settingStore.isComm) {
Modal.confirm({
title: t("vip.already_plus"),
okText: t("vip.confirm"),
title: t("certd.vip.already_plus"),
okText: t("certd.vip.confirm"),
onOk() {
window.open(goBuyCommUrl);
},
@@ -251,7 +251,7 @@ function openUpgrade() {
title,
class: "vip-modal",
maskClosable: true,
okText: t("vip.close"),
okText: t("certd.vip.close"),
width: 1180,
content: () => {
return <VipModalContent placeholder={placeholder} isPlus={isPlus} productInfo={productInfo} goBuyPlusPage={goBuyPlusPage} goBuyCommPage={goBuyCommPage} openStarModal={openStarModal} modalRef={modalRef} />;
@@ -34,7 +34,7 @@
</a-tooltip>
</span>
<span v-else>
<span class="price-text">{{ t("vip.freee") }}</span>
<span class="price-text">{{ t("certd.vip.freee") }}</span>
</span>
</div>
<div class="get-show">
@@ -44,15 +44,15 @@
<span class="color-red"> {{ todayOrderCount.vipTotal }}</span>
位伙伴支持
</span>
<a-tooltip :title="t('vip.afdian_support_vip')">
<a-tooltip :title="t('certd.vip.afdian_support_vip')">
<a-button size="small" type="primary" @click="goBuyPlusPage">
{{ t("vip.get_after_support") }}
{{ t("certd.vip.get_after_support") }}
</a-button>
</a-tooltip>
</template>
<template v-else-if="item.type === 'comm'">
<a-button size="small" type="primary" @click="goBuyCommPage">
{{ t("vip.buy") }}
{{ t("certd.vip.buy") }}
</a-button>
</template>
</div>
@@ -63,36 +63,36 @@
</div>
<div>
<a href="https://certd.docmirror.cn/guide/donate/#相关问题" target="_blank">
{{ t("vip.question") }}
{{ t("certd.vip.question") }}
</a>
</div>
<div class="mt-10">
<div class="w-100 flex-col md:flex-row">
<span>{{ t("vip.site_id") }}:</span>
<span>{{ t("certd.vip.site_id") }}:</span>
<fs-copyable v-model="computedSiteId" class="mr-2 inline-flex"></fs-copyable>
<a @click="goBindAccount">{{ t("vip.not_effective") }}</a>
<a @click="goBindAccount">{{ t("certd.vip.not_effective") }}</a>
</div>
</div>
<div v-if="isPlus" class="mt-10 flex flex-col md:flex-row">
<span class="mr-2"> {{ t("vip.current") }} {{ vipLabel }} {{ t("vip.activated_expire_time") }} {{ settingStore.expiresText }} </span>
<span class="mr-2"> {{ t("certd.vip.current") }} {{ vipLabel }} {{ t("certd.vip.activated_expire_time") }} {{ settingStore.expiresText }} </span>
<a href="https://app.handfree.work/subject/#/page/detail/1" target="_blank">
{{ t("vip.learn_more") }}
{{ t("certd.vip.learn_more") }}
</a>
</div>
<div class="mt-10">
<span class="mr-2">{{ t("vip.have_activation_code") }}</span>
<span class="mr-2">{{ t("certd.vip.have_activation_code") }}</span>
<span>
<a @click="showManualActivation">{{ t("vip.manual_activation") }}</a>
<a @click="showManualActivation">{{ t("certd.vip.manual_activation") }}</a>
</span>
</div>
<div v-if="manualActiveFlag" class="mt-10">
<div class="mt-10">
<a-input-search v-model:value="formState.code" class="w-2/6" :placeholder="placeholder" :enter-button="t('vip.activate')" @search="doActive"></a-input-search>
<a-input-search v-model:value="formState.code" class="w-2/6" :placeholder="placeholder" :enter-button="t('certd.vip.activate')" @search="doActive"></a-input-search>
</div>
<div class="mt-10">
{{ t("vip.activation_code_one_use") }}
<a @click="goAccount">{{ t("vip.bind_account") }}</a
>,{{ t("vip.transfer_vip") }}
{{ t("certd.vip.activation_code_one_use") }}
<a @click="goAccount">{{ t("certd.vip.bind_account") }}</a
>,{{ t("certd.vip.transfer_vip") }}
</div>
</div>
</div>
@@ -128,24 +128,24 @@ const formState = reactive({
async function doActive() {
if (!formState.code) {
notification.error({ message: t("vip.enterCode") });
throw new Error(t("vip.enterCode"));
notification.error({ message: t("certd.vip.enterCode") });
throw new Error(t("certd.vip.enterCode"));
}
const res = await api.doActive(formState);
if (res) {
await settingStore.init();
const vipLabel = settingStore.vipLabel;
Modal.success({
title: t("vip.successTitle"),
content: t("vip.successContent", {
title: t("certd.vip.successTitle"),
content: t("certd.vip.successContent", {
vipLabel,
expireDate: settingStore.plusInfo.expireTime === -1 ? t("vip.permanent") : dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
expireDate: settingStore.plusInfo.expireTime === -1 ? t("certd.vip.permanent") : dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
}),
onOk() {
if (!(settingStore.installInfo.bindUserId > 0)) {
Modal.confirm({
title: t("vip.bindAccountTitle"),
content: t("vip.bindAccountContent"),
title: t("certd.vip.bindAccountTitle"),
content: t("certd.vip.bindAccountContent"),
onOk() {
router.push("/sys/account");
},
@@ -179,39 +179,39 @@ function goBindAccount() {
const vipTypeDefine: any = {
free: {
title: t("vip.basic_edition"),
desc: t("vip.community_free_version"),
title: t("certd.vip.basic_edition"),
desc: t("certd.vip.community_free_version"),
type: "free",
icon: "lucide:package-open",
privilege: t("vip.free_privilege").split("\n"),
privilege: t("certd.vip.free_privilege").split("\n"),
},
plus: {
title: t("vip.professional_edition"),
desc: t("vip.open_source_support"),
title: t("certd.vip.professional_edition"),
desc: t("certd.vip.open_source_support"),
type: "plus",
privilege: t("vip.plus_privilege").split("\n"),
privilege: t("certd.vip.plus_privilege").split("\n"),
trial: {
title: t("vip.click_to_get_7_day_trial"),
title: t("certd.vip.click_to_get_7_day_trial"),
click: () => {
props.openStarModal("plus");
},
},
icon: "stash:thumb-up",
priceText: props.productInfo.plus.priceText || `¥${props.productInfo.plus.price}/${t("vip.years")}`,
discountText: props.productInfo.plus.discountText || `¥${props.productInfo.plus.price3}/3${t("vip.years")}`,
priceText: props.productInfo.plus.priceText || `¥${props.productInfo.plus.price}/${t("certd.vip.years")}`,
discountText: props.productInfo.plus.discountText || `¥${props.productInfo.plus.price3}/3${t("certd.vip.years")}`,
tooltip: props.productInfo.plus.tooltip,
},
comm: {
title: t("vip.business_edition"),
desc: t("vip.commercial_license"),
title: t("certd.vip.business_edition"),
desc: t("certd.vip.commercial_license"),
type: "comm",
icon: "vaadin:handshake",
privilege: t("vip.comm_privilege").split("\n"),
priceText: props.productInfo.comm.priceText || `¥${props.productInfo.comm.price}/${t("vip.years")}`,
discountText: props.productInfo.comm.discountText || `¥${props.productInfo.comm.price3}/3${t("vip.years")}`,
privilege: t("certd.vip.comm_privilege").split("\n"),
priceText: props.productInfo.comm.priceText || `¥${props.productInfo.comm.price}/${t("certd.vip.years")}`,
discountText: props.productInfo.comm.discountText || `¥${props.productInfo.comm.price3}/3${t("certd.vip.years")}`,
tooltip: props.productInfo.comm.tooltip,
trial: {
title: t("vip.click_to_get_7_day_trial"),
title: t("certd.vip.click_to_get_7_day_trial"),
click: () => {
props.openStarModal("comm");
},
@@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" setup>
import { BasicLayout, LockScreen, UserDropdown } from "/@/vben/layouts";
import { computed, onErrorCaptured, onMounted, provide, ref } from "vue";
@@ -23,14 +23,14 @@ const menus = computed(() => [
router.push("/cert/mine/user-profile");
},
icon: "fa-solid:book",
text: t("certd.accountInfo"),
text: t("certd.mine.accountInfo"),
},
{
handler: () => {
router.push("/cert/mine/security");
},
icon: "fluent:shield-keyhole-16-regular",
text: t("certd.securitySettings"),
text: t("certd.mine.securitySettings"),
},
]);
@@ -109,7 +109,7 @@ const projectStore = useProjectStore();
<vip-button class="flex-center header-btn" mode="nav" />
</div>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<fs-button shape="circle" type="text" icon="clarity:host-solid-badged" :text="null" :tooltip="{ title: t('certd.client') }" @click="goCertdClient" />
<fs-button shape="circle" type="text" icon="clarity:host-solid-badged" :text="null" :tooltip="{ title: t('certd.navigation.client') }" @click="goCertdClient" />
</div>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<fs-button shape="circle" type="text" icon="ion:logo-github" :text="null" @click="goGithub" />
@@ -13,36 +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,
...client,
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",
@@ -1,20 +1,18 @@
export default {
clientManager: "Client Manager",
client: {
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",
},
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",
};
@@ -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",
};
@@ -6,6 +6,10 @@ export default {
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