mirror of
https://github.com/certd/certd.git
synced 2026-10-25 10:04:07 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f15bb8a21 | ||
|
|
6bbaee0d99 | ||
|
|
554a20e1fc | ||
|
|
60e00f8f20 | ||
|
|
62751bee7a | ||
|
|
1b012c791c | ||
|
|
5f83693423 | ||
|
|
cfd384485f | ||
|
|
61ef971518 | ||
|
|
60d0872ba1 | ||
|
|
a67d5bca2d | ||
|
|
1e1e76b4a8 | ||
|
|
9d02cd84f0 | ||
|
|
a9806315f1 | ||
|
|
42b743d5bb | ||
|
|
e9b9839d79 | ||
|
|
12ef05c528 | ||
|
|
21b88ff017 | ||
|
|
8e169be562 | ||
|
|
6b6e684c88 | ||
|
|
3c98b974f7 | ||
|
|
e2aff952f9 | ||
|
|
9d9ae64aff | ||
|
|
22715a430e | ||
|
|
949a69f0dc | ||
|
|
0874cb2223 | ||
|
|
53db97b6f2 | ||
|
|
cd2dcaa859 | ||
|
|
d0463abc26 | ||
|
|
8f1b38ad7d | ||
|
|
e11349b8cc | ||
|
|
2394cc6dd9 | ||
|
|
8209c3d6e1 | ||
|
|
3e001b8ba4 | ||
|
|
41ca198116 | ||
|
|
6eca5dad9b | ||
|
|
c285522500 | ||
|
|
b12f7c54d4 | ||
|
|
b055bbecfc | ||
|
|
a52a6ce8f6 | ||
|
|
fcbf1045fe | ||
|
|
f5cfe054b7 | ||
|
|
5ded5ff2b6 | ||
|
|
8d31346cd3 | ||
|
|
44b41b6a08 | ||
|
|
4420472f8f | ||
|
|
c4497a7018 | ||
|
|
03f1a7bd0e | ||
|
|
97d2b50074 | ||
|
|
2d0b85b305 | ||
|
|
28052c9387 | ||
|
|
c4277350bc | ||
|
|
97160373d6 | ||
|
|
0ce24618a1 | ||
|
|
f60e63fbaf | ||
|
|
2735feb330 | ||
|
|
b6381d79fc | ||
|
|
241b2e5584 | ||
|
|
bb1f232f41 | ||
|
|
f82d3a5fcc | ||
|
|
bbeaf617d1 | ||
|
|
ea7d14c289 | ||
|
|
3272a68ff6 | ||
|
|
b5715d99ff | ||
|
|
3f5ba14fde | ||
|
|
07f0d3b4d6 | ||
|
|
3dbbe72fc1 | ||
|
|
7f006d1359 | ||
|
|
d30f1c4785 | ||
|
|
96e0122b93 | ||
|
|
7786a87b21 | ||
|
|
34476ba14f | ||
|
|
4b942385a8 | ||
|
|
14809127d6 | ||
|
|
bd8b251d6e | ||
|
|
5563abb13a | ||
|
|
51e83e4c38 | ||
|
|
2b1a5296fe | ||
|
|
bded368cbb | ||
|
|
6a649aaca0 | ||
|
|
e971145686 | ||
|
|
bbca449f94 | ||
|
|
e6852c2715 | ||
|
|
47cb523307 | ||
|
|
cbf46b5c48 | ||
|
|
7ed3105d97 | ||
|
|
61b945c08f | ||
|
|
cc60cd4cd1 | ||
|
|
dade599acb | ||
|
|
1070417a36 | ||
|
|
81f676f1ca | ||
|
|
0cf0608d3f | ||
|
|
dac94b64f0 | ||
|
|
8c88092235 | ||
|
|
2a2e963914 | ||
|
|
1b882a2ca4 | ||
|
|
bd9433478a | ||
|
|
32ade8e4a3 | ||
|
|
f2bcdeeb33 | ||
|
|
4099af7f5d |
@@ -3,7 +3,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "版本号(如 v1.42.5)"
|
||||
description: "版本号(如 1.42.5)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
+2
-1
@@ -42,4 +42,5 @@ output/
|
||||
.uploads/
|
||||
.certd-plugin-history/
|
||||
.tmp
|
||||
.plugin-dev/
|
||||
.plugin-dev/
|
||||
.tmp_chunks/
|
||||
@@ -23,6 +23,8 @@ version: 1.0.0
|
||||
|
||||
- 配置插件的唯一标识、标题、图标和描述
|
||||
- 继承 `BaseAccess` 类
|
||||
- 插件默认文案使用中文;需要英文时,在注解参数对象中添加同级 `i18n: { "en-US": { ... } }`,不要写成 `metadata.i18n`。`define` 本身就是插件 metadata。
|
||||
- `i18n["en-US"]` 仅配置要翻译的展示字段,如 `title`、`desc`、`input`;输入字段可翻译 `title`、`helper`、组件展示文案和 `optionsMapping`。缺少英文翻译时回退默认中文。
|
||||
|
||||
3. **定义授权属性**
|
||||
|
||||
@@ -392,6 +394,66 @@ export class OAuthDemoAccess extends BaseAccess {
|
||||
}
|
||||
```
|
||||
|
||||
## 字段动态显隐(mergeScript)
|
||||
|
||||
多种鉴权方式共用一个授权时,用 `@AccessInput` 上的 `mergeScript` 按所选方式显隐字段:
|
||||
|
||||
```typescript
|
||||
@AccessInput({
|
||||
title: '鉴权方式',
|
||||
value: 'apikey',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
options: [
|
||||
{ label: 'AccessKey签名', value: 'aksk' },
|
||||
{ label: 'ApiKey', value: 'apikey' },
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
authType = 'apikey';
|
||||
|
||||
@AccessInput({
|
||||
title: 'accessKeyId',
|
||||
component: { name: 'a-input', vModel: 'value' },
|
||||
required: true,
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return !form.access.authType || form.access.authType === 'aksk';
|
||||
})
|
||||
}
|
||||
`,
|
||||
})
|
||||
accessKeyId = '';
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- `mergeScript` 返回的对象会深合并进本字段定义,可动态化的属性有 `show`、`required`、`helper`、
|
||||
`component` 下的属性(`options`、`placeholder` 等)以及 `component.on` 事件。
|
||||
- **动态判断必须写在 `ctx.compute(({form})=>{ ... })` 回调里**。`mergeScript` 本身在插件类
|
||||
定义阶段就被拼成字符串,那时表单还没打开,用参数在服务端判断字段值没有意义。
|
||||
- 授权表单字段值在 `form.access.xxx`(任务表单才是 `form.xxx`)。
|
||||
- 历史授权没有新字段,条件里要用 `!form.access.authType || ...` 兜底走旧逻辑,保证老授权还能编辑保存。
|
||||
- 联动其他字段用 `component.on` 事件回调,回调参数是 `{ form }`:
|
||||
|
||||
```typescript
|
||||
mergeScript: `
|
||||
return {
|
||||
component: {
|
||||
on: {
|
||||
selectedChange: (scope) => {
|
||||
const form = scope.form;
|
||||
form.acmeAccountAccessId = null;
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`,
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **插件命名**:插件名称应简洁明了,反映其功能。
|
||||
|
||||
@@ -9,7 +9,7 @@ description: 用于通过 Certd API 开发、修改、读取、保存和恢复
|
||||
|
||||
本 Skill 使用 Certd HTTP API,不使用 WebSocket,也不依赖浏览器传递代码草稿。
|
||||
|
||||
- 前端只生成包含需求、API 地址和认证 Token 的启动提示词。
|
||||
- 前端只生成包含需求、插件作者、API 地址和认证 Token 的启动提示词。
|
||||
- Agent 直接调用 Certd API 读取和保存插件 YAML。
|
||||
- Agent 的进度、日志和代码修改在 Codex/Trae 中查看。
|
||||
- 插件开发临时文件和修改历史统一保存在 Agent 工作区的 `.plugin-dev/` 下,不保存到浏览器或 Certd 后端。
|
||||
@@ -43,15 +43,13 @@ Certd 插件按来源分为三类:
|
||||
|
||||
## API 认证
|
||||
|
||||
提示词会提供 Certd API 地址和仅限 AI 插件开发接口的受限 Token。调用 API 时使用:
|
||||
提示词会提供 Certd API 地址和仅限 AI 插件开发接口的受限的短时效的 Token。调用 API 时使用:
|
||||
|
||||
```http
|
||||
Authorization: <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
不要把 Token 写入代码、历史摘要、日志、提交信息或插件 YAML。
|
||||
|
||||
所有 Certd API 请求统一使用 Node.js 的 `fetch`。不要使用 PowerShell 的 `Invoke-RestMethod`、`Invoke-WebRequest` 或 .NET HTTP 客户端发送插件 YAML/JSON;它们在 Windows 上可能造成中文乱码或使完整 YAML 导入请求长时间无响应。
|
||||
|
||||
Node 请求须直接读取 UTF-8 文件或在 Node 内构造 JSON,并使用 `JSON.stringify`:
|
||||
@@ -118,23 +116,56 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
|
||||
- 修改后保存修改摘要。
|
||||
- 恢复前再次备份当前版本。
|
||||
- 不上传历史文件,不保存 Token、证书、私钥或真实授权值。
|
||||
- 修改完一版后立即提交更新到certd,但不要发布到市场。
|
||||
|
||||
详细格式见 `references/local-history.md`。
|
||||
|
||||
## YAML 和脚本规范
|
||||
|
||||
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。
|
||||
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。`content` 必须使用 YAML 字面块 `|` 保留 JavaScript 换行,禁止使用折叠块 `>`,避免语句拼接导致 `Unexpected token 'const'` 等解析错误。
|
||||
|
||||
- 新插件的 `author` 必须使用启动提示词中给出的插件作者,不要自行编造作者名;修改已有插件时保留原 `author`,不要改成其他作者。
|
||||
- 统一使用 `await _ctx.import(...)` 引用模块。
|
||||
- `"/@/..."` 表示以绝对路径引用 `server/src/` 下的模块。
|
||||
- 最后返回继承目标基类的 class。
|
||||
- 不使用 `import`、`export`、装饰器或独立源码文件语法。
|
||||
- 使用 `this.logger` 打印插件执行日志。
|
||||
- 使用 `this.ctx.http` 访问 HTTP 能力。
|
||||
- 使用目标基类提供的 logger 打印插件执行日志;在线 Access 使用 `this.ctx.logger`。
|
||||
- 使用 `this.ctx.http` 访问 HTTP 能力;默认返回响应体 `response.data`,需要响应头、状态码或 Cookie 时必须设置 `returnOriginRes: true`,再从 `response.data` 读取响应体。详见 `references/context-utils.md`。
|
||||
- 优先复用 `this.ctx.utils` 中的现成工具,尤其是 `this.ctx.utils.hash` 的摘要和编码方法;完整分类见 `references/context-utils.md`。
|
||||
- 失败时抛出 `Error`。
|
||||
- 日志打印对象时必须使用 `JSON.stringify(value)`,禁止直接字符串拼接对象导致输出 `[object Object]`;需要可读日志时先构造明确的 JSON 字符串。
|
||||
|
||||
### 证书域名匹配与列表组件
|
||||
|
||||
- 需要从前置证书获取域名时,优先在 `input` 前置配置 `certDomains` 字段,使用 `cert-domains-getter`,并通过 `inputKey` 指向证书字段。
|
||||
- 远程证书或资源列表如果需要按证书域名匹配,必须为每个选项提供 `domain` 字段;`domain` 可以是单个域名字符串,也可以是域名数组。只有满足此前提,才能使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)` 返回分组后的列表。
|
||||
- 如果服务商接口不支持分页或服务端搜索,列表组件不要设置 `pager: true` 或 `search: true`;确需搜索时在插件中获取完整列表后执行本地过滤。
|
||||
|
||||
需要字段格式时读取 `references/online-yaml-format.md`。
|
||||
需要组件示例时读取 `references/component-examples.md`。
|
||||
需要按其他字段动态显隐/必填/联动时读取 `references/merge-script.md`。
|
||||
需要查找 `this.ctx.utils` 工具或确认 HTTP 返回结构时读取 `references/context-utils.md`。
|
||||
|
||||
### 字段动态显隐(mergeScript)
|
||||
|
||||
字段定义支持 `mergeScript`:脚本 `return` 的对象会合并进本字段,用来做「选了 A 才显示 B」这类联动。
|
||||
|
||||
```yaml
|
||||
mergeScript: |2-
|
||||
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.access.authType === 'apikey';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- 动态逻辑必须写在 `ctx.compute(({form})=>{ ... })` 回调里;`mergeScript` 本身在插件类定义阶段就
|
||||
被拼成字符串,那时表单还没打开,用参数在 node 侧判断字段值没有任何意义。
|
||||
- 授权表单字段值在 `form.access.xxx`,任务表单字段值在 `form.xxx`。
|
||||
- 新增可选字段时要给历史数据兜底,例如 `!form.access.authType || form.access.authType === 'aksk'`。
|
||||
|
||||
完整用法(能力、可动态化的属性、事件联动、常见错误、验证方式)见 `references/merge-script.md`。
|
||||
|
||||
## 示例插件
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: DemoAccess
|
||||
icon: logos:airflow-icon
|
||||
title: Demo-授权插件示例 # 模块-插件名
|
||||
title: Demo-授权插件示例 # 格式:平台-功能 ,例如: 网宿-授权 ; 阿里云-授权 ; 天翼云-授权
|
||||
group: null
|
||||
desc: 这只是一个示例
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: DemoDeploy
|
||||
icon: logos:amp-icon
|
||||
title: Demo-部署插件示例 # 模块-插件名
|
||||
title: Demo-部署插件示例 # 格式:平台-功能 ,例如: 网宿-更新CDNpro证书 ; 阿里云-上传到CAS证书管理服务 ; 天翼云-更新CDN证书
|
||||
group: cdn
|
||||
desc: 这仅仅是一个示例
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: DemoDnsProvider
|
||||
icon: fa-solid:frog
|
||||
title: Demo-Dns提供商插件示例 # 模块-插件名
|
||||
title: Demo-Dns提供商插件示例 # 格式:平台-功能 ,例如: 阿里云-DNS提供商 ;腾讯云-DNS提供商
|
||||
desc: 这只是一个示例
|
||||
type: custom
|
||||
version: 1.0.0
|
||||
|
||||
@@ -17,3 +17,61 @@ Access 插件负责保存授权配置,也负责封装平台 API/SDK,供 Task
|
||||
- 业务插件通过 `dependPlugins` 依赖 Access。
|
||||
- `editable: false` 时不要尝试修改 Access,在业务插件内部实现必要的调用。
|
||||
- 不要在日志中打印完整授权配置。
|
||||
|
||||
## 地址与请求复用
|
||||
|
||||
- `BaseAccess` 已提供通用的 `normalizeEndpoint(endpoint)`,用于校验地址并清理 URL 末尾的 `/`。Access 插件直接调用 `this.normalizeEndpoint(this.endpoint)`,不要在插件中重复实现同名方法。
|
||||
- Cookie 解析优先使用 `this.ctx.http.getCookies(response)` 或 `this.ctx.http.getCookie(response, name)`;需要原始响应时为请求设置 `returnOriginRes: true`。
|
||||
|
||||
## TLS 证书校验
|
||||
|
||||
需要兼容自签名或过期 HTTPS 证书时,在 Access 表单中使用 `skipSslVerify` 开关,并将其显式传给 `this.ctx.http.request`。默认关闭校验跳过,仅用户主动开启后才跳过:
|
||||
|
||||
```yaml
|
||||
input:
|
||||
skipSslVerify:
|
||||
title: 忽略 HTTPS 证书校验
|
||||
required: false
|
||||
component:
|
||||
name: a-switch
|
||||
vModel: checked
|
||||
```
|
||||
|
||||
```js
|
||||
const response = await this.ctx.http.request({
|
||||
url: `${baseUrl}/api`,
|
||||
method: "POST",
|
||||
skipSslVerify: this.skipSslVerify === true,
|
||||
});
|
||||
```
|
||||
|
||||
## 测试按钮
|
||||
|
||||
通过 `api-test` 输入项绑定 `onTestRequest`,用户点击按钮后 Certd 会调用该方法。测试方法应执行轻量、只读的连通性请求,并在失败时抛出有意义的错误:
|
||||
|
||||
```yaml
|
||||
input:
|
||||
testRequest:
|
||||
title: 测试
|
||||
component:
|
||||
name: api-test
|
||||
action: onTestRequest
|
||||
helper: 点击测试接口是否正常
|
||||
```
|
||||
|
||||
```js
|
||||
async onTestRequest() {
|
||||
const baseUrl = this.normalizeEndpoint(this.endpoint);
|
||||
const response = await this.ctx.http.request({
|
||||
url: `${baseUrl}/api/status`,
|
||||
method: "GET",
|
||||
skipSslVerify: this.skipSslVerify === true,
|
||||
});
|
||||
|
||||
if (!response || response.code !== 0) {
|
||||
throw new Error(`连接测试失败:${response?.message || "接口返回异常"}`);
|
||||
}
|
||||
|
||||
return "连接成功";
|
||||
}
|
||||
```
|
||||
|
||||
@@ -70,29 +70,8 @@ name: "a-auto-complete",
|
||||
options: [
|
||||
{ value: "cn-qingdao", label: "华北1(青岛)" },
|
||||
{ value: "cn-beijing", label: "华北2(北京)" },
|
||||
{ value: "cn-zhangjiakou", label: "华北3(张家口)" },
|
||||
{ value: "cn-huhehaote", label: "华北5(呼和浩特)" },
|
||||
{ value: "cn-wulanchabu", label: "华北6(乌兰察布)" },
|
||||
{ value: "cn-hangzhou", label: "华东1(杭州)" },
|
||||
{ value: "cn-shanghai", label: "华东2(上海)" },
|
||||
{ value: "cn-shenzhen", label: "华南1(深圳)" },
|
||||
{ value: "cn-guangzhou", label: "华南3(广州)" },
|
||||
{ value: "ap-southeast-2", label: "澳大利亚(悉尼)" },
|
||||
{ value: "ap-southeast-3", label: "马来西亚(吉隆坡)" },
|
||||
{ value: "ap-northeast-1", label: "日本(东京)" },
|
||||
{ value: "cn-chengdu", label: "西南1(成都)" },
|
||||
{ value: "ap-southeast-1", label: "新加坡" },
|
||||
{ value: "ap-southeast-5", label: "印度尼西亚(雅加达)" },
|
||||
{ value: "cn-hongkong", label: "中国香港" },
|
||||
{ value: "eu-central-1", label: "德国(法兰克福)" },
|
||||
{ value: "us-east-1", label: "美国(弗吉尼亚)" },
|
||||
{ value: "us-west-1", label: "美国(硅谷)" },
|
||||
{ value: "eu-west-1", label: "英国(伦敦)" },
|
||||
{ value: "me-east-1", label: "阿联酋(迪拜)" },
|
||||
//金融云
|
||||
...
|
||||
{ value: "cn-beijing-finance-1", label: "华北2 金融云(邀测)" },
|
||||
{ value: "cn-hangzhou-finance", label: "华东1 金融云" },
|
||||
{ value: "cn-shanghai-finance-1", label: "华东2 金融云" },
|
||||
{ value: "cn-shenzhen-finance-1", label: "华南1 金融云" },
|
||||
],
|
||||
placeholder: "集群所属大区",
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# 在线插件运行时工具
|
||||
|
||||
在线插件实例可以通过 `this.ctx.utils` 复用 Certd 已有工具。开发前按分类查找现成方法,避免重复实现。
|
||||
|
||||
## 工具分类
|
||||
|
||||
| 分类 | 调用入口 | 常用方法 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| 哈希与编码 | `this.ctx.utils.hash` | `md5(data, digest?)`、`sha256(data, digest?)`、`base64(data)`、`base64Decode(data)`、`hmacSha256(data, digest?)`、`hmacSha256WithKey(key, data, digest?)`、`toHex(data)`、`hexToStr(data)`、`hexToNumber(data)` | 摘要、签名、Base64/Hex 编解码 |
|
||||
| 字符串 | `this.ctx.utils.string` | 读取 `packages/core/basic/src/utils/util.string.ts` | 转换、截断、格式化 |
|
||||
| 域名 | `this.ctx.utils.domain` | 读取 `packages/core/basic/src/utils/util.domain.ts` | 主域名和子域名处理,域名选项列表,域名匹配分组 |
|
||||
| 请求 | `this.ctx.utils.request` / `this.ctx.http` | `getCookies(response)`、`getCookie(response, name)`、`download(config)` | Cookie、下载和请求辅助 |
|
||||
| 文件 | `this.ctx.utils.file` | 读取 `packages/core/basic/src/utils/util.file.ts` | 文件读写 |
|
||||
| Promise/延迟 | `this.ctx.utils.promises`、`this.ctx.utils.sleep` | 读取对应实现 | 重试、等待、并发 |
|
||||
| 时间 | `this.ctx.utils.dayjs` | Day.js API | 时间处理 |
|
||||
| ID/缓存/合并 | `this.ctx.utils.id`、`nanoid`、`cache`、`mergeUtils` | 读取对应 `util.*.ts` | ID、缓存和配置合并 |
|
||||
|
||||
示例:
|
||||
|
||||
```javascript
|
||||
const passwd = this.ctx.utils.hash.md5(password);
|
||||
const pass = this.ctx.utils.hash.base64(`salt_11${password}`);
|
||||
```
|
||||
|
||||
方法签名以仓库中 `packages/core/basic/src/utils/util.*.ts` 为准,不要凭名称猜测参数顺序。
|
||||
|
||||
## `this.ctx.http.request` 返回结构
|
||||
|
||||
`this.ctx.http.request` 默认直接返回响应体,也就是 Axios 的 `response.data`:
|
||||
|
||||
```javascript
|
||||
const body = await this.ctx.http.request({ url: "https://api.example.com/status", method: "GET" });
|
||||
```
|
||||
|
||||
此时 `body` 不能再读取 `body.data`。如果需要响应头、状态码或 `Set-Cookie`,必须设置 `returnOriginRes: true`:
|
||||
|
||||
```javascript
|
||||
const response = await this.ctx.http.request({
|
||||
url: "https://api.example.com/login",
|
||||
method: "POST",
|
||||
returnOriginRes: true,
|
||||
data: { username, password },
|
||||
});
|
||||
const body = response.data;
|
||||
const setCookie = response.headers?.["set-cookie"];
|
||||
const cookies = this.ctx.http.getCookies(response);
|
||||
const sessionId = this.ctx.http.getCookie(response, "session_id");
|
||||
```
|
||||
|
||||
规则:普通 API 直接使用 `response` 作为 body;需要 headers/status/Cookie 时使用 `returnOriginRes: true`,再从 `response.data` 读取 body。首页预请求和登录请求都需要 Cookie 时,两次请求都要设置该选项。`skipSslVerify: true` 仅在用户明确配置忽略证书校验时传入。
|
||||
|
||||
## `HttpRequestConfig` 请求参数
|
||||
|
||||
`this.ctx.http.request` 接受以下 Certd 扩展参数,同时兼容 Axios 的标准请求配置(如 `url`、`method`、`headers`、`params`、`data`、`timeout`、`responseType` 等):
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `skipSslVerify` | `boolean` | 为 `true` 时关闭 HTTPS 证书校验。仅在授权表单提供了明确开关且用户主动启用时传入。 |
|
||||
| `skipCheckRes` | `boolean` | Certd 内部兼容参数,在线插件通常不需要设置。 |
|
||||
| `logReq` | `boolean` | 是否打印请求地址和方法。默认开启;敏感请求可设为 `false`。 |
|
||||
| `logParams` | `boolean` | 是否打印 URL 查询参数。默认关闭。参数中含密钥、Token 或隐私数据时保持关闭。 |
|
||||
| `logRes` | `boolean` | 是否打印响应状态和响应体摘要。默认关闭。 |
|
||||
| `logData` | `boolean` | 是否打印请求体。默认关闭;禁止对包含密码、Token、Cookie 或证书私钥的请求开启。 |
|
||||
| `httpProxy` | `string` | 本次请求使用的 HTTP 代理地址。没有明确代理需求时不要设置。 |
|
||||
| `returnOriginRes` | `boolean` | 默认返回响应体 `response.data`;设为 `true` 时返回 Axios 原始响应对象,可读取 `response.data`、`response.headers` 和 `response.status`。获取 `Set-Cookie` 时必须开启。 |
|
||||
|
||||
示例:
|
||||
|
||||
```javascript
|
||||
const response = await this.ctx.http.request({
|
||||
url: `${baseUrl}/Action/login`,
|
||||
method: "POST",
|
||||
returnOriginRes: true,
|
||||
skipSslVerify: this.ignoreTls === true,
|
||||
logData: false,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: requestData,
|
||||
});
|
||||
const body = response.data;
|
||||
const cookies = response.headers?.["set-cookie"];
|
||||
```
|
||||
|
||||
## Cookie 和文件下载辅助
|
||||
|
||||
原始响应可传给 HTTP 客户端提供的 Cookie 辅助方法:
|
||||
|
||||
```javascript
|
||||
const cookies = this.ctx.http.getCookies(response);
|
||||
const sessionId = this.ctx.http.getCookie(response, "session_id");
|
||||
```
|
||||
|
||||
`getCookies(response)` 返回 `{ name, value }[]`;没有 `set-cookie` 时返回空数组。`getCookie(response, name)` 找不到目标 Cookie 时返回 `null`。
|
||||
|
||||
下载文件可使用 `this.ctx.http.download`,参数为 `{ http, config, savePath, logger }`,`config` 遵循 `HttpRequestConfig`:
|
||||
|
||||
```javascript
|
||||
await this.ctx.http.download({
|
||||
http: this.ctx.http,
|
||||
config: { url: fileUrl, method: "GET", logRes: false },
|
||||
savePath,
|
||||
logger: this.ctx.logger,
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,181 @@
|
||||
# mergeScript 用法
|
||||
|
||||
来源:前端 `packages/ui/certd-client/src/use/use-refrence.tsx`,类型定义来自 `@fast-crud/fast-crud`。
|
||||
|
||||
`mergeScript` 用来让输入字段的定义**依赖当前表单里的其他字段值动态变化**,例如:
|
||||
选了某个鉴权方式才显示对应字段、切换类型后清空已选值、下拉选项跟着另一个字段变。
|
||||
|
||||
它写成字符串放在字段定义上(内置插件放在 `@AccessInput` / `@TaskInput` 里,在线插件放在 `input.<字段>` 下):
|
||||
|
||||
```ts
|
||||
@AccessInput({
|
||||
title: "API Key",
|
||||
component: { name: "a-input", vModel: "value" },
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
//access里面 要用 form.access.[字段名]
|
||||
return form.access.authType === 'apikey';
|
||||
//deploy插件里面要用form.[字段名]
|
||||
return form.authType === 'apikey';
|
||||
})
|
||||
}
|
||||
`,
|
||||
})
|
||||
apiKey = "";
|
||||
```
|
||||
|
||||
## 执行时机
|
||||
|
||||
字段定义被前端加载时,对**每一个字段**执行一次:
|
||||
|
||||
```js
|
||||
const func = new Function('ctx', formItem.mergeScript);
|
||||
const merged = func(ctx); // 字段脚本只执行这一次
|
||||
merge(formItem, merged); // 返回值深合并进该字段的定义
|
||||
delete formItem.mergeScript; // 执行完就删掉,最终定义里不再有 mergeScript
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- 脚本体内**必须 `return` 一个对象**,返回的对象会深合并到本字段的定义上。
|
||||
- 脚本本身只在加载字段时跑一次,**拿不到、也不应该判断用户填了什么值**。
|
||||
- 真正的动态逻辑写在返回对象里的 `ctx.compute(...)` 回调中,由 fast-crud 在渲染时求值。
|
||||
|
||||
## ctx 提供的能力
|
||||
|
||||
| 能力 | 说明 |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ctx.compute(fn)` | 最常用。返回一个响应式的计算值:`fn` 里读到的表单字段会被自动追踪,字段变化时重新计算,视图跟着刷新 |
|
||||
|
||||
`ctx.compute` 的回调参数是一个 context 对象,常用字段:
|
||||
|
||||
- `form`:当前表单数据。**这是唯一常用的一个**。
|
||||
- `row`:行数据。
|
||||
- `mode`:表单模式 `add` / `edit` / `view`。
|
||||
- `getComponentRef(key)`:取其他字段组件的 ref。
|
||||
|
||||
注意:Certd 在把 `form` 传进回调前做过一次兼容处理(`form.input || form.body || form`),
|
||||
所以两种表单取值路径不同:
|
||||
|
||||
- **授权(Access)表单**:字段值在 `form.access.xxx`(字段 key 会被加上 `access.` 前缀)。
|
||||
- **任务(Task)表单**:字段值直接是 `form.xxx`。
|
||||
|
||||
## 可以动态化的属性
|
||||
|
||||
返回的对象里,任何字段定义属性都可以用 `ctx.compute`,常用的有:
|
||||
|
||||
| 属性 | 用途 |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `show` | 显隐(最常用) |
|
||||
| `required` | 动态必填,例如 `required: ctx.compute(({form}) => form.targetType === 'ingress')` |
|
||||
| `disabled` | 动态禁用,例如账号已创建后不允许再改:`disabled: ctx.compute(({form}) => !!form.access?.account)` |
|
||||
| `helper` | 动态帮助文案,回调里按条件 `return` 不同字符串 |
|
||||
| `value` | 动态默认值 |
|
||||
| `component` 下的属性 | 动态 `options`、`placeholder`、`inputKey`、`type`、`disabled` 等 |
|
||||
| `component.on` | 事件处理,见下面「联动」 |
|
||||
| `col` / `order` | 布局与排序 |
|
||||
|
||||
只写了一层 `show` 时最简形式就是本文开头的例子。
|
||||
|
||||
## 联动(监听事件并改动其他字段)
|
||||
|
||||
`component.on` 的事件回调**不**用 `ctx.compute` 包,它本身就是回调,参数是
|
||||
`{ form, $event, ... }`;`form` 同样做过 `form.input || form.body || form` 的兼容处理。
|
||||
事件名按组件而定,例如 `selectedChange`。
|
||||
|
||||
```ts
|
||||
mergeScript: `
|
||||
return {
|
||||
component: {
|
||||
on: {
|
||||
selectedChange: (scope) => {
|
||||
const form = scope.form;
|
||||
// 切换颁发机构后清空 ACME 账号,避免账号与机构不匹配
|
||||
form.acmeAccountAccessId = null;
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`,
|
||||
```
|
||||
|
||||
另一种写法是把回调也做成计算值(需要用到组件上配置的 `onSelectedChange` / `onChange` 属性):
|
||||
|
||||
```ts
|
||||
mergeScript: `
|
||||
return {
|
||||
component: {
|
||||
onSelectedChange: ctx.compute(({form}) => {
|
||||
return ($event) => {
|
||||
form.dnsProviderAccessType = $event.accessType;
|
||||
};
|
||||
}),
|
||||
},
|
||||
}
|
||||
`,
|
||||
```
|
||||
|
||||
两种写法都能用,优先参考同目录已有插件的写法,保持一致。
|
||||
|
||||
## 常见错误
|
||||
|
||||
- **不要用 node 侧变量决定条件**。`mergeScript` 是在插件类定义阶段(node 侧)拼出来的字符串,
|
||||
此时表单还没打开,拿不到任何用户输入;用参数拼条件等于把逻辑写死在服务端。
|
||||
条件必须原样落到 `ctx.compute` 回调里。
|
||||
- **不要用 `if` 直接判断字段值**。回调必须 `return` 一个值,写成
|
||||
`show: ctx.compute(({form}) => { if (...) { return true } })` 也可以,但不要漏掉 `return`。
|
||||
- **不要漏掉历史数据兼容**。新增可选字段时,历史数据里没有这个字段(值为 `undefined`),
|
||||
条件要按旧行为兜底,例如 `!form.access.authType || form.access.authType === 'aksk'`。
|
||||
- **不要访问 `form.access` 以外的路径**去读授权字段,也不要在授权表单里按任务表单的路径取值。
|
||||
- **不要依赖脚本执行时的副作用**(打印日志、改全局状态),它在每次打开表单时都会重新执行。
|
||||
|
||||
## YAML 写法(在线插件)
|
||||
|
||||
`mergeScript` 是含换行的脚本字符串,必须用 YAML 块标量:
|
||||
|
||||
```yaml
|
||||
input:
|
||||
apiKey:
|
||||
title: API Key
|
||||
component:
|
||||
name: a-input
|
||||
vModel: value
|
||||
mergeScript: |2-
|
||||
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.access.authType === 'apikey';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
导出生成的缩进比较特殊(块标量内容统一缩进),**不要手工调整缩进**:
|
||||
用脚本生成 YAML,并保证导出后再导入内容一致。转换成 block scalar 时注意:
|
||||
|
||||
- `|` / `|2-` 等块标量不需要转义换行,不要把脚本写成带 `\n` 的单行。
|
||||
- 脚本里不要出现 `??` 这类容易被误判为乱码的写法,用显式 `if (x == null)` 代替。
|
||||
|
||||
## 验证方式
|
||||
|
||||
前端表单没法在插件开发流程里直接断言,可以在后端加一个 `src/**/*.test.ts`,
|
||||
按前端 `useReference` 的路径执行字段上的 `mergeScript`:
|
||||
|
||||
```ts
|
||||
const computedGetters = [];
|
||||
const ctx = {
|
||||
compute: getter => {
|
||||
computedGetters.push(getter);
|
||||
return getter;
|
||||
},
|
||||
asyncCompute: () => undefined,
|
||||
computed: () => undefined,
|
||||
};
|
||||
new Function('ctx', mergeScript)(ctx);
|
||||
// 用假的 form 求值,断言显隐结果
|
||||
const show = computedGetters[0]({ form: { access: { authType: 'apikey' } } });
|
||||
```
|
||||
|
||||
参考实现:`packages/ui/certd-server/src/plugins/plugin-wangsu/access-input.test.ts`。
|
||||
要点是断言「同一个 show 函数换 `authType` 结果会变」,证明显隐确实是运行时算的,
|
||||
而不是在 node 侧定死的。
|
||||
@@ -7,7 +7,7 @@
|
||||
```yaml
|
||||
name: DemoTask
|
||||
author: demo
|
||||
title: Demo 任务
|
||||
title: Demo-插件名称 # 格式:平台-功能 ,例如: 网宿-更新CDNpro证书 ; 阿里云-上传到CAS证书管理服务 ; 天翼云-更新CDN证书
|
||||
desc: 插件说明
|
||||
icon: clarity:plugin-line
|
||||
pluginType: task
|
||||
@@ -47,20 +47,32 @@ content: |
|
||||
|
||||
### `content` 规则
|
||||
|
||||
- `content` 必须使用 YAML 字面块 `|` 保留脚本换行,禁止使用折叠块 `>`;折叠换行可能导致多个 JavaScript 语句拼接,出现 `Unexpected token 'const'` 等解析错误。
|
||||
|
||||
- 统一使用 `await _ctx.import(...)` 加载模块。
|
||||
- 使用 `_ctx.import("/@/...")` 以绝对路径加载 `certd-server/src/` 下的模块,`/@` 代表 `certd-server/src` 根路径。
|
||||
- 需要确认模块时使用 `_ctx.logger`,插件执行过程使用 `this.logger`。
|
||||
- 最后返回继承目标基类的 class。
|
||||
- 不使用 `import`、`export`、装饰器或独立源码文件语法。
|
||||
- 输入字段在 class 中声明为同名属性,并与 YAML 的 `input` 配置保持一致。
|
||||
- HTTP 使用 `this.ctx.http`;
|
||||
- HTTP 使用 `this.ctx.http`。默认 `request()` 返回响应体 `response.data`;需要 headers、status 或 Set-Cookie 时设置 `returnOriginRes: true`,此时返回原始响应对象,响应体从 `response.data` 读取。请求扩展参数 `skipSslVerify`、`skipCheckRes`、`logReq`、`logParams`、`logRes`、`logData`、`httpProxy`、`returnOriginRes` 的说明见 `context-utils.md`。
|
||||
- 优先使用 `this.ctx.utils` 中已有的哈希、编码、字符串、域名和请求工具,详见 `context-utils.md`。
|
||||
- 不要使用 `console.log`。
|
||||
- 日志打印对象时使用 `JSON.stringify(value)`,不要直接拼接对象输出 `[object Object]`。
|
||||
- 读取授权使用 `await this.getAccess(accessId)` 或目标基类规定的授权方式。
|
||||
- 失败时抛出 `Error`,不要吞掉错误。
|
||||
|
||||
### 证书域名与列表选项
|
||||
|
||||
- 需要使用前置证书域名时,在列表字段之前配置 `certDomains`,组件使用 `cert-domains-getter`,并通过 `inputKey` 绑定证书字段。
|
||||
- 列表选项可按证书域名匹配时,必须为每个选项设置 `domain`;该字段支持单个域名字符串或域名数组。只有配置该字段后,才使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)`。
|
||||
- 服务商接口不支持翻页或服务端搜索时,远程选择组件不要配置 `pager: true`、`search: true`;本地搜索可在获取完整列表后过滤。
|
||||
|
||||
### 编辑规则
|
||||
|
||||
- 修改已有插件时保留 `name`、`author`、`pluginType` 和已有兼容字段。
|
||||
- 新插件的 `author` 使用启动提示词中给出的插件作者,不要自行编造作者名;启动提示词未给出作者时先向用户确认。
|
||||
- `fullName` 由 `author/name` 组成,作者变化会导致插件身份变化,不要随意修改。
|
||||
- 只修改需求涉及的字段,避免删除未知的 YAML 字段。
|
||||
- 脚本过长时仍放在同一个 `content` block scalar 中。
|
||||
- 提交前检查 YAML 可解析、`content` 非空、版本和插件类型没有被意外修改。
|
||||
|
||||
@@ -14,6 +14,42 @@ description: 用于开发 Certd 在线 Access 插件。输出完整 YAML,conte
|
||||
- 敏感字段在 input 中设置加密或密码类组件。
|
||||
- `content` 中实现授权 class 和 API 方法。
|
||||
|
||||
## Metadata 示例
|
||||
|
||||
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
|
||||
|
||||
```yaml
|
||||
name: DemoAccess
|
||||
author: certd
|
||||
fullName: certd/DemoAccess
|
||||
title: Demo-授权
|
||||
desc: Demo 平台授权配置
|
||||
icon: clarity:plugin-line
|
||||
group: other
|
||||
version: 1.0.0
|
||||
pluginType: access
|
||||
input:
|
||||
demoKeyId:
|
||||
title: Key ID
|
||||
required: true
|
||||
component:
|
||||
name: a-input
|
||||
vModel: value
|
||||
demoKeySecret:
|
||||
title: Key Secret
|
||||
required: true
|
||||
encrypt: true
|
||||
component:
|
||||
name: a-input-password
|
||||
vModel: value
|
||||
testRequest:
|
||||
title: 测试
|
||||
component:
|
||||
name: api-test
|
||||
action: onTestRequest
|
||||
helper: 点击测试接口是否正常
|
||||
```
|
||||
|
||||
## `content` 模板
|
||||
|
||||
```javascript
|
||||
@@ -29,7 +65,7 @@ return class DemoAccess extends BaseAccess {
|
||||
}
|
||||
|
||||
async getDomainList(req) {
|
||||
this.logger.info("获取域名列表", { searchKey: req.searchKey })
|
||||
this.ctx.logger.info("获取域名列表", { searchKey: req.searchKey })
|
||||
const res = await this.ctx.http.request({
|
||||
url: "https://api.example.com/domains",
|
||||
method: "GET",
|
||||
@@ -56,5 +92,42 @@ return class DemoAccess extends BaseAccess {
|
||||
- 所有敏感授权值只通过 `this` 和 Certd 授权上下文使用,不打印真实值。
|
||||
- `onTestRequest` 应调用实际 API 方法并在失败时抛出异常。
|
||||
- 对外 API 方法应统一处理分页、错误和返回字段。
|
||||
- 使用 `this.logger` 或框架提供的 logger,禁止 `console.log`。
|
||||
- 一般将API接口方法封装到Access中,其他plugin调用access的方法当做client sdk使用
|
||||
- 在线 Access 使用 `this.ctx.logger` 打印日志,禁止使用未注入的 `this.logger` 和 `console.log`。
|
||||
- 优先复用 `this.ctx.utils` 中的现成工具,例如 `this.ctx.utils.hash.md5` 和 `this.ctx.utils.hash.base64`;HTTP 返回值和 Cookie 处理规则见父 Skill 的 `references/context-utils.md`。
|
||||
- 一般将API接口方法封装到Access中,其他plugin调用access的方法当做client sdk使用
|
||||
|
||||
## 字段动态显隐
|
||||
|
||||
多种鉴权方式(例如「ApiKey」和「AccessKey签名」)共用一个授权时,
|
||||
用字段上的 `mergeScript` 按所选方式显隐对应字段,只让用户看到当前方式需要的字段:
|
||||
|
||||
```yaml
|
||||
input:
|
||||
authType:
|
||||
title: 鉴权方式
|
||||
value: apikey
|
||||
component:
|
||||
name: a-select
|
||||
vModel: value
|
||||
options:
|
||||
- { label: AccessKey签名, value: aksk }
|
||||
- { label: ApiKey, value: apikey }
|
||||
accessKeyId:
|
||||
title: accessKeyId
|
||||
component:
|
||||
name: a-input
|
||||
vModel: value
|
||||
mergeScript: |2-
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return !form.access.authType || form.access.authType === 'aksk';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- 判断必须写在 `ctx.compute(({form})=>{ ... })` 回调里;脚本本身在插件定义阶段就拼好了,
|
||||
那时表单还没打开,用参数在服务端判断字段值没有意义。
|
||||
- 授权表单的字段值在 `form.access.xxx`(任务表单才是 `form.xxx`)。
|
||||
- 历史授权没有 `authType` 字段,条件里要用 `!form.access.authType || ...` 兜底走旧逻辑。
|
||||
|
||||
完整用法见父 Skill 的 `references/merge-script.md`。
|
||||
|
||||
@@ -13,6 +13,22 @@ description: 用于开发 Certd 在线 DNS Provider 插件。输出完整 YAML
|
||||
- `input` 中配置授权选择、域名或平台所需的参数。
|
||||
- `content` 中实现创建和删除 DNS 记录的 class。
|
||||
|
||||
## Metadata 示例
|
||||
|
||||
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
|
||||
|
||||
```yaml
|
||||
name: DemoDnsProvider
|
||||
author: certd
|
||||
fullName: certd/DemoDnsProvider
|
||||
title: Demo-DNS 解析
|
||||
desc: Demo 平台 DNS-01 验证
|
||||
icon: clarity:plugin-line
|
||||
group: dns
|
||||
version: 1.0.0
|
||||
pluginType: dnsProvider
|
||||
```
|
||||
|
||||
## `content` 模板
|
||||
|
||||
```javascript
|
||||
@@ -21,10 +37,10 @@ const { DemoAccess } = await _ctx.import("/@/plugins/plugin-lib/demo/access/inde
|
||||
_ctx.logger.info("DemoAccess:", DemoAccess)
|
||||
|
||||
return class DemoDnsProvider extends AbstractDnsProvider {
|
||||
accessId
|
||||
access!: DemoAccess;
|
||||
|
||||
async onInstance() {
|
||||
this.access = await this.getAccess(this.accessId)
|
||||
this.access = this.ctx.access as DemoAccess;
|
||||
}
|
||||
|
||||
async createRecord(options) {
|
||||
|
||||
@@ -14,6 +14,38 @@ description: 用于开发 Certd 在线 Task 插件。输出完整 YAML,脚本
|
||||
- 输入配置放在 YAML 的 `input` 字段。
|
||||
- 执行脚本放在 YAML 顶层 `content` 字段。
|
||||
|
||||
## Metadata 示例
|
||||
|
||||
顶层 metadata 描述插件身份和展示信息。新插件应填写 `name`、`author`、`title`、`desc`、`icon`、`group`、`version` 和 `pluginType`;`fullName` 按 `author/name` 生成:
|
||||
|
||||
```yaml
|
||||
name: DemoTask
|
||||
author: certd
|
||||
fullName: certd/DemoTask
|
||||
title: Demo-执行任务
|
||||
desc: 调用 Demo 平台 API 执行操作
|
||||
icon: clarity:plugin-line
|
||||
group: other
|
||||
version: 1.0.0
|
||||
pluginType: task
|
||||
input:
|
||||
cert:
|
||||
title: 前置任务证书
|
||||
required: true
|
||||
component:
|
||||
name: cert-select
|
||||
certDomains:
|
||||
title: 当前证书域名
|
||||
required: true
|
||||
component:
|
||||
name: cert-domains-getter
|
||||
accessId:
|
||||
title: Demo 授权
|
||||
required: true
|
||||
component:
|
||||
name: access-selector
|
||||
type: demo/DemoAccess # 如果是在线Access插件,要写全称, author/name, 如果是本地插件,只需要写插件名即可
|
||||
```
|
||||
|
||||
## runStrategy
|
||||
|
||||
@@ -26,10 +58,11 @@ description: 用于开发 Certd 在线 Task 插件。输出完整 YAML,脚本
|
||||
- 0 表示成功后跳过,1 表示正常运行。
|
||||
- 设置任务的默认运行策略runStrategy。
|
||||
|
||||
当证书申请任务,申请证书成功后,会清空后续任务状态。此时后续`runStrategy = 0` 的任务,会重新执行。
|
||||
这样可以保证相同的证书不会重复部署。
|
||||
|
||||
在其他场景的部署任务,可能希望每次运行都检查是否需要部署,如果即将过期就部署,没有过期就跳过。
|
||||
有如下三种场景:
|
||||
1、证书申请任务,设置为 `default.strategy.runStrategy = 1`,表示正常运行,此时executor每次都会执行plugin的execute方法,当无需更新证书时,返回 `"skip"`。
|
||||
当申请证书成功后,调用`this.clearLastStatus();`清空后续任务的成功状态,后续任务就都会重新执行。
|
||||
2、直接部署任务,建议设置为 `default.strategy.runStrategy = 0`,表示成功后跳过,executor参数与状态,如果参数没有变化,且上次的状态是成功或跳过,则直接跳过该任务,这样可以保证相同的证书不会重复部署。
|
||||
3、检查后再部署的任务,可能希望每次运行都检查是否需要部署,如果即将过期就部署,没有过期就跳过。
|
||||
此时可以设置 `default.strategy.runStrategy = 1`,然后在`execute`方法中,如果没有过期,就返回 `"skip"`。
|
||||
|
||||
## `content` 模板
|
||||
|
||||
@@ -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 方法**
|
||||
|
||||
|
||||
@@ -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. **定义任务输入参数**
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ Certd 是可私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web
|
||||
- 只有真正机械化的大批量替换、格式化或生成任务才考虑脚本/工具。若必须使用临时脚本,应放在临时目录并在同一个受控步骤内完成创建、执行、删除;不要把临时脚本落在仓库里跨多步工具调用执行。
|
||||
- 不要主动运行 `pnpm install`;缺依赖、TTY、网络导致安装或测试失败时,停止尝试并说明环境问题。
|
||||
- 优先沿用现有模块、插件、service、页面模式;不要为形式上的复用制造过度抽象。
|
||||
- 代码可读性优先于短写法。尽量少用三元表达式,优先使用完整的 `if/else`;复杂条件、链式调用、内联对象和多层 helper 调用要拆成命名清晰的中间变量或小方法。
|
||||
- 【重要重要重要】代码可读性优先于短写法。尽量少用三元表达式,优先使用完整的 `if/else`【其他技能与此条要求冲突的,以此条要求为准】;复杂条件、链式调用、内联对象和多层 helper 调用要拆成命名清晰的中间变量或小方法。
|
||||
- 方法调用链不要直接塞进另一个方法参数;先用有意义的局部变量承接返回值,再传入下一步。
|
||||
- 不要在单一表达式内嵌套分支、对象构造与方法调用。优先使用清晰的 `if/else` 分支;仅在确实能降低复杂度时才提取有意义的中间变量,避免为拆分而增加阅读跳转。
|
||||
- 注释优先使用中文,尤其是业务规则、兼容逻辑、协议细节和隐藏风险;文件已有英文风格或引用外部术语时可保持一致。
|
||||
|
||||
@@ -3,6 +3,81 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
|
||||
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
|
||||
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
|
||||
* **baota:** 宝塔站点全自动部署插件支持部署docker站点 ([61ef971](https://github.com/certd/certd/commit/61ef971518eeccbc31b09c06ec1b48589929d6bb))
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
|
||||
* **aliyun-cert:** 阿里云订阅证书增加申请请求接口 ([949a69f](https://github.com/certd/certd/commit/949a69f0dcf929980363fc2b3d772edae696dcaf))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
|
||||
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
|
||||
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
|
||||
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
|
||||
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [BUG]使用天翼云CDN部署证书时无法匹配域名 ([bb1f232](https://github.com/certd/certd/commit/bb1f232f415c522851dede979f5d609dda844fd3))
|
||||
* 缺失运行时嵌套依赖时添加清除缓存提示 ([2d0b85b](https://github.com/certd/certd/commit/2d0b85b3054fc1aa82b3ab5db34a2a56308f71f2))
|
||||
* 支持google persist校验方式 ([97d2b50](https://github.com/certd/certd/commit/97d2b5007409ec79ea864d437c473a2f9b5dfae4))
|
||||
* **config:** 修复环境变量加载逻辑并更新配置文件 ([07f0d3b](https://github.com/certd/certd/commit/07f0d3b4d626628a0e3dfc7e5c52e8c4a98bad47))
|
||||
* **ctyun-cdn:** 修复天翼云CDN部署插件的域名选择问题 ([ea7d14c](https://github.com/certd/certd/commit/ea7d14c289b8720781bf101bfef59eac7bb03765))
|
||||
* **webhook:** 修复模版转义换行符,避免手动上传的证书报错 ([2735feb](https://github.com/certd/certd/commit/2735feb3302f8308708dafc8b295adcd4b91d7f0))
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 宝塔站点列表支持搜索和排序 ([4420472](https://github.com/certd/certd/commit/4420472f8fac272d30fb8ccb1cc2ee9763989001))
|
||||
* 网宿插件双鉴权支持 ([241b2e5](https://github.com/certd/certd/commit/241b2e55848471e61b5d2be0c87a7dc9a0d29557))
|
||||
* **monitor:** 站点证书监控支持手动选中触发检查 ([f60e63f](https://github.com/certd/certd/commit/f60e63fbaf00460d5050cd7705bd75d57dec202d))
|
||||
* **open-api:** openkey增加用途备注 ([bbeaf61](https://github.com/certd/certd/commit/bbeaf617d19750515f6eca51863e74e23bc1de50))
|
||||
* **tencent clb:** 修改所有CLB地域的endpoint为就近接入 ([c427735](https://github.com/certd/certd/commit/c4277350bcc8cf034cf4e76db2a6daa1842bc5d7))
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复添加步骤会修改所有步骤标题的bug ([51e83e4](https://github.com/certd/certd/commit/51e83e4c38f7f319314fdc38ef33cf5720b2dde0))
|
||||
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 新增余额管理,管理员充值功能 ([bded368](https://github.com/certd/certd/commit/bded368cbb8974b6937ae260e448c25fae5c33ad))
|
||||
* **acme-account-access:** AcmeAccount授权允许修改eab,重新生成account ([6a649aa](https://github.com/certd/certd/commit/6a649aaca083ccd3b517f7d676a58220aa33b37d))
|
||||
* **ui:** 商业版套餐购买不要求设置支付方式 ([e971145](https://github.com/certd/certd/commit/e9711456860d7db40914a0c295534f7bca56f83c))
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **plugin-service:** 修复同步自己开发的云端插件不显示已安装状态问题 ([dac94b6](https://github.com/certd/certd/commit/dac94b64f0ceb7176e2cf07ce220be0aa5ed98d2))
|
||||
* **plugin:** 修复插件配置保存与加载的相关问题 ([bd94334](https://github.com/certd/certd/commit/bd9433478a43d5d64ce6370386d0fe0aaff07d2e))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **plugin:** add Fastly custom TLS certificates integration ([#797](https://github.com/certd/certd/issues/797)) [@szhu25](https://github.com/szhu25) ([2a2e963](https://github.com/certd/certd/commit/2a2e963914c4dfc192cbe0a4bbb9c115e626da01))
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -105,14 +105,17 @@ https://certd.handfree.work/
|
||||
|
||||
#### Docker镜像说明:
|
||||
|
||||
##### 1. 镜像地址格式:
|
||||
##### 1. 镜像地址:
|
||||
|
||||
```
|
||||
registry.cn-shenzhen.aliyuncs.com/certd/certd:[version-][system-][latest/stable]
|
||||
------------ ↑ 镜像地址 ------------- ↑ 镜像名 -- ↑指定版本- ↑基础系统- ↑最新版本类型 (中括号表示可选)
|
||||
| 镜像仓库 | 最新预览版 | 稳定版 | slim版 | armv7版 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 阿里云【默认】 | `registry.cn-shenzhen.aliyuncs.com/certd/certd:latest` | `certd:stable` | `certd:slim` | `certd:armv7` |
|
||||
| Docker Hub | `certd/certd:latest` | `certd:stable` | `certd:slim` | `certd:armv7` |
|
||||
| GitHub Packages | `ghcr.io/certd/certd:latest` | `certd:stable` | `certd:slim` | `certd:armv7` |
|
||||
|
||||
```
|
||||
##### 2. 版本标签:
|
||||
> 后面的各个版本省略了镜像地址,使用时需要将镜像地址拼接完整。
|
||||
|
||||
##### 2. 版本标签说明:
|
||||
|
||||
**最新版本标签:**
|
||||
|
||||
@@ -131,16 +134,8 @@ registry.cn-shenzhen.aliyuncs.com/certd/certd:[version-][system-][latest/stable]
|
||||
| slim | `certd:slim` | Debian slim | 基于glibc,dns解析兼容性好 | `certd:slim-stable` | `certd:1.43.0-slim` |
|
||||
| armv7 | `certd:armv7` | Alpine Linux | ARMv7 架构专用版本 | `certd:armv7-stable` | `certd:1.43.0-armv7` |
|
||||
|
||||
##### 3. 镜像地址:
|
||||
|
||||
| 镜像仓库 | 最新预览版 | slim | armv7 |
|
||||
| --- | --- | --- | --- |
|
||||
| 阿里云【默认】 | `registry.cn-shenzhen.aliyuncs.com/certd/certd:latest` | `certd:slim` | `certd:armv7` |
|
||||
| Docker Hub | `certd/certd:latest` | `certd:slim` |
|
||||
| GitHub Packages | `ghcr.io/certd/certd:latest` | `certd:slim` | `certd:armv7` |
|
||||
|
||||
> 1. 后面的各个版本省略了镜像地址,使用时需要将镜像地址拼接完整。
|
||||
> 2. 如果您不确定使用哪个版本,请使用默认的 `certd:latest` 版本。
|
||||
> 如果您不确定使用哪个版本,请使用默认的 `certd:latest` 版本。
|
||||
|
||||
最新版本号:
|
||||

|
||||
|
||||
@@ -3,6 +3,75 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
|
||||
* **aliyun-cert:** 阿里云订阅证书增加申请请求接口 ([949a69f](https://github.com/certd/certd/commit/949a69f0dcf929980363fc2b3d772edae696dcaf))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
|
||||
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
|
||||
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
|
||||
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
|
||||
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [BUG]使用天翼云CDN部署证书时无法匹配域名 ([bb1f232](https://github.com/certd/certd/commit/bb1f232f415c522851dede979f5d609dda844fd3))
|
||||
* 缺失运行时嵌套依赖时添加清除缓存提示 ([2d0b85b](https://github.com/certd/certd/commit/2d0b85b3054fc1aa82b3ab5db34a2a56308f71f2))
|
||||
* 支持google persist校验方式 ([97d2b50](https://github.com/certd/certd/commit/97d2b5007409ec79ea864d437c473a2f9b5dfae4))
|
||||
* **config:** 修复环境变量加载逻辑并更新配置文件 ([07f0d3b](https://github.com/certd/certd/commit/07f0d3b4d626628a0e3dfc7e5c52e8c4a98bad47))
|
||||
* **ctyun-cdn:** 修复天翼云CDN部署插件的域名选择问题 ([ea7d14c](https://github.com/certd/certd/commit/ea7d14c289b8720781bf101bfef59eac7bb03765))
|
||||
* **webhook:** 修复模版转义换行符,避免手动上传的证书报错 ([2735feb](https://github.com/certd/certd/commit/2735feb3302f8308708dafc8b295adcd4b91d7f0))
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 宝塔站点列表支持搜索和排序 ([4420472](https://github.com/certd/certd/commit/4420472f8fac272d30fb8ccb1cc2ee9763989001))
|
||||
* 网宿插件双鉴权支持 ([241b2e5](https://github.com/certd/certd/commit/241b2e55848471e61b5d2be0c87a7dc9a0d29557))
|
||||
* **monitor:** 站点证书监控支持手动选中触发检查 ([f60e63f](https://github.com/certd/certd/commit/f60e63fbaf00460d5050cd7705bd75d57dec202d))
|
||||
* **open-api:** openkey增加用途备注 ([bbeaf61](https://github.com/certd/certd/commit/bbeaf617d19750515f6eca51863e74e23bc1de50))
|
||||
* **tencent clb:** 修改所有CLB地域的endpoint为就近接入 ([c427735](https://github.com/certd/certd/commit/c4277350bcc8cf034cf4e76db2a6daa1842bc5d7))
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复添加步骤会修改所有步骤标题的bug ([51e83e4](https://github.com/certd/certd/commit/51e83e4c38f7f319314fdc38ef33cf5720b2dde0))
|
||||
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 新增余额管理,管理员充值功能 ([bded368](https://github.com/certd/certd/commit/bded368cbb8974b6937ae260e448c25fae5c33ad))
|
||||
* **acme-account-access:** AcmeAccount授权允许修改eab,重新生成account ([6a649aa](https://github.com/certd/certd/commit/6a649aaca083ccd3b517f7d676a58220aa33b37d))
|
||||
* **ui:** 商业版套餐购买不要求设置支付方式 ([e971145](https://github.com/certd/certd/commit/e9711456860d7db40914a0c295534f7bca56f83c))
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **plugin-service:** 修复同步自己开发的云端插件不显示已安装状态问题 ([dac94b6](https://github.com/certd/certd/commit/dac94b64f0ceb7176e2cf07ce220be0aa5ed98d2))
|
||||
* **plugin:** 修复插件配置保存与加载的相关问题 ([bd94334](https://github.com/certd/certd/commit/bd9433478a43d5d64ce6370386d0fe0aaff07d2e))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **plugin:** add Fastly custom TLS certificates integration ([#797](https://github.com/certd/certd/issues/797)) [@szhu25](https://github.com/szhu25) ([2a2e963](https://github.com/certd/certd/commit/2a2e963914c4dfc192cbe0a4bbb9c115e626da01))
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复插件市场插件安装无效的bug ([15d2bd8](https://github.com/certd/certd/commit/15d2bd8484a9ddd142bf4ef03d839c74405f9e6d))
|
||||
* preserve Route53 TXT record values ([#791](https://github.com/certd/certd/issues/791)) ([f5430a8](https://github.com/certd/certd/commit/f5430a8925cf6cf4e16c75f2358fbd78437e6705))
|
||||
|
||||
## [1.44.1](https://github.com/certd/certd/compare/v1.44.0...v1.44.1) (2026-09-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -6,13 +6,17 @@
|
||||
|
||||
Certd 提供多种 Docker 镜像版本,您可以根据需要选择:
|
||||
|
||||
#### 1. 镜像地址格式:
|
||||
#### 1. 镜像地址:
|
||||
|
||||
```
|
||||
registry.cn-shenzhen.aliyuncs.com/certd/certd:[version-][system-][latest/stable]
|
||||
------------ ↑ 镜像地址 ------------- ↑ 镜像名 -- ↑指定版本- ↑基础系统- ↑最新版本类型
|
||||
```
|
||||
#### 2. 版本标签:
|
||||
| 镜像仓库 | 最新预览版 | 稳定版 | slim版 | armv7版 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 阿里云【默认】 | `registry.cn-shenzhen.aliyuncs.com/certd/certd:latest` | `certd:stable` | `certd:slim` | `certd:armv7` |
|
||||
| Docker Hub | `certd/certd:latest` | `certd:stable` | `certd:slim` | `certd:armv7` |
|
||||
| GitHub Packages | `ghcr.io/certd/certd:latest` | `certd:stable` | `certd:slim` | `certd:armv7` |
|
||||
|
||||
> 后面的各个版本省略了镜像地址,使用时需要将镜像地址拼接完整。
|
||||
|
||||
#### 2. 版本标签说明:
|
||||
|
||||
**最新版本标签:**
|
||||
|
||||
@@ -25,22 +29,14 @@ registry.cn-shenzhen.aliyuncs.com/certd/certd:[version-][system-][latest/stable]
|
||||
|
||||
> 根据基础镜像不同,分为如下三个分支版本,没有特殊需求选择默认的即可(他们功能是一样的)
|
||||
|
||||
| 系统版本 | 版本标签 | 基础系统 | 说明 | 稳定版标签 | 指定版本 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| alpine【默认】 | `certd:latest` | Alpine Linux | 默认版本,镜像体积小,支持x86、ARM架构 | `certd:stable` | `certd:1.43.0` |
|
||||
| 系统版本 | 版本标签 | 基础系统 | 说明 | 稳定版标签 | 指定版本 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| alpine【默认】 | `certd:latest` | Alpine Linux | 默认版本,镜像体积小 | `certd:stable` | `certd:1.43.0` |
|
||||
| slim | `certd:slim` | Debian slim | 基于glibc,dns解析兼容性好 | `certd:slim-stable` | `certd:1.43.0-slim` |
|
||||
| armv7 | `certd:armv7` | Alpine Linux | ARMv7 架构专用版本 | `certd:armv7-stable` | `certd:1.43.0-armv7` |
|
||||
|
||||
#### 3. 镜像地址:
|
||||
|
||||
| 镜像仓库 | 最新预览版 | slim版 | armv7版 |
|
||||
| --- | --- | --- | --- |
|
||||
| 阿里云【默认】 | `registry.cn-shenzhen.aliyuncs.com/certd/certd:latest` | `certd:slim` | `certd:armv7` |
|
||||
| Docker Hub | `certd/certd:latest` | `certd:slim` | `certd:armv7` |
|
||||
| GitHub Packages | `ghcr.io/certd/certd:latest` | `certd:slim` | `certd:armv7` |
|
||||
|
||||
> 1. 后面的各个版本省略了镜像地址,使用时需要将镜像地址拼接完整。
|
||||
> 2. 如果您不确定使用哪个版本,请使用默认的 `certd:latest` 版本。
|
||||
> 如果您不确定使用哪个版本,请使用默认的 `certd:latest` 版本。
|
||||
|
||||
最新版本号:
|
||||

|
||||
|
||||
@@ -36,52 +36,53 @@
|
||||
| 32.| **Dokploy授权** | |
|
||||
| 33.| **Dynadot授权** | |
|
||||
| 34.| **farcdn授权** | |
|
||||
| 35.| **FlexCDN授权** | |
|
||||
| 36.| **Gcore** | Gcore |
|
||||
| 37.| **Github授权** | |
|
||||
| 38.| **godaddy授权** | |
|
||||
| 39.| **HiPM DNSMgr** | HiPM DNSMgr API Token 授权 |
|
||||
| 40.| **金山云授权** | |
|
||||
| 41.| **FTP授权** | |
|
||||
| 42.| **七牛OSS授权** | |
|
||||
| 43.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
|
||||
| 44.| **s3/minio授权** | S3/minio oss授权 |
|
||||
| 45.| **namesilo授权** | |
|
||||
| 46.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
|
||||
| 47.| **Nginx Proxy Manager 授权** | 用于登录 Nginx Proxy Manager,并为代理主机证书部署提供授权。 |
|
||||
| 48.| **1panel授权** | 账号和密码 |
|
||||
| 49.| **支付宝** | |
|
||||
| 50.| **白山云授权** | |
|
||||
| 51.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
|
||||
| 52.| **cdnfly授权** | |
|
||||
| 53.| **k8s授权** | |
|
||||
| 54.| **括彩云cdn授权** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 55.| **LeCDN授权** | |
|
||||
| 56.| **lucky** | |
|
||||
| 57.| **猫云授权** | |
|
||||
| 58.| **plesk授权** | |
|
||||
| 59.| **长亭雷池授权** | |
|
||||
| 60.| **群晖登录授权** | |
|
||||
| 61.| **uniCloud** | unicloud授权 |
|
||||
| 62.| **微信支付** | |
|
||||
| 63.| **易盾rcdn授权** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 64.| **易发云短信** | sms.yfyidc.cn/ |
|
||||
| 65.| **易盾DCDN授权** | https://user.yiduncdn.com |
|
||||
| 66.| **易支付** | |
|
||||
| 67.| **proxmox** | |
|
||||
| 68.| **Spaceship.com 授权** | Spaceship.com API 授权插件 |
|
||||
| 69.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器授权 |
|
||||
| 70.| **UCloud授权** | 优刻得授权 |
|
||||
| 71.| **又拍云** | |
|
||||
| 72.| **网宿授权** | |
|
||||
| 73.| **西部数码授权** | |
|
||||
| 74.| **我爱云授权** | 我爱云CDN |
|
||||
| 75.| **新网授权(代理方式)** | |
|
||||
| 76.| **新网授权** | |
|
||||
| 77.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 |
|
||||
| 78.| **Zenlayer授权** | Zenlayer授权 |
|
||||
| 79.| **GoEdge授权** | |
|
||||
| 80.| **雨云授权** | https://app.rainyun.com/ |
|
||||
| 35.| **Fastly授权** | Fastly CDN / TLS Custom Certificates API 授权 |
|
||||
| 36.| **FlexCDN授权** | |
|
||||
| 37.| **Gcore** | Gcore |
|
||||
| 38.| **Github授权** | |
|
||||
| 39.| **godaddy授权** | |
|
||||
| 40.| **HiPM DNSMgr** | HiPM DNSMgr API Token 授权 |
|
||||
| 41.| **金山云授权** | |
|
||||
| 42.| **FTP授权** | |
|
||||
| 43.| **七牛OSS授权** | |
|
||||
| 44.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
|
||||
| 45.| **s3/minio授权** | S3/minio oss授权 |
|
||||
| 46.| **namesilo授权** | |
|
||||
| 47.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
|
||||
| 48.| **Nginx Proxy Manager 授权** | 用于登录 Nginx Proxy Manager,并为代理主机证书部署提供授权。 |
|
||||
| 49.| **1panel授权** | 账号和密码 |
|
||||
| 50.| **支付宝** | |
|
||||
| 51.| **白山云授权** | |
|
||||
| 52.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
|
||||
| 53.| **cdnfly授权** | |
|
||||
| 54.| **k8s授权** | |
|
||||
| 55.| **括彩云cdn授权** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 56.| **LeCDN授权** | |
|
||||
| 57.| **lucky** | |
|
||||
| 58.| **猫云授权** | |
|
||||
| 59.| **plesk授权** | |
|
||||
| 60.| **长亭雷池授权** | |
|
||||
| 61.| **群晖登录授权** | |
|
||||
| 62.| **uniCloud** | unicloud授权 |
|
||||
| 63.| **微信支付** | |
|
||||
| 64.| **易盾rcdn授权** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 65.| **易发云短信** | sms.yfyidc.cn/ |
|
||||
| 66.| **易盾DCDN授权** | https://user.yiduncdn.com |
|
||||
| 67.| **易支付** | |
|
||||
| 68.| **proxmox** | |
|
||||
| 69.| **Spaceship.com 授权** | Spaceship.com API 授权插件 |
|
||||
| 70.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器授权 |
|
||||
| 71.| **UCloud授权** | 优刻得授权 |
|
||||
| 72.| **又拍云** | |
|
||||
| 73.| **网宿授权** | |
|
||||
| 74.| **西部数码授权** | |
|
||||
| 75.| **我爱云授权** | 我爱云CDN |
|
||||
| 76.| **新网授权(代理方式)** | |
|
||||
| 77.| **新网授权** | |
|
||||
| 78.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 |
|
||||
| 79.| **Zenlayer授权** | Zenlayer授权 |
|
||||
| 80.| **GoEdge授权** | |
|
||||
| 81.| **雨云授权** | https://app.rainyun.com/ |
|
||||
|
||||
<style module>
|
||||
table th:first-of-type {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 任务插件
|
||||
共 `135` 款任务插件
|
||||
共 `139` 款任务插件
|
||||
## 1. 证书申请
|
||||
|
||||
| 序号 | 名称 | 说明 |
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
| 序号 | 名称 | 说明 |
|
||||
|-----|-----|-----|
|
||||
| 1.| **主机-复制到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 |
|
||||
| 1.| **主机-复制证书到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 |
|
||||
| 2.| **主机-执行远程主机脚本命令** | 可以执行重启nginx等操作让证书生效 |
|
||||
| 3.| **IIS-部署到IIS站点** | |
|
||||
| 4.| **上传证书到对象存储OSS** | 支持阿里云OSS、腾讯云COS、七牛云KODO、S3、MinIO、FTP、SFTP |
|
||||
@@ -31,26 +31,30 @@
|
||||
| 4.| **中国移动-部署证书到CDN** | 中国移动自动部署证书到CDN |
|
||||
| 5.| **多吉云-部署到多吉云CDN** | |
|
||||
| 6.| **farcdn-更新证书** | www.farcdn.net |
|
||||
| 7.| **FlexCDN-更新证书** | |
|
||||
| 8.| **Gcore-刷新Gcore证书** | 刷新现有的证书 |
|
||||
| 9.| **Gcore-部署证书到Gcore** | 仅上传 并不会部署到cdn |
|
||||
| 10.| **GoEdge-更新证书** | GoEdge |
|
||||
| 11.| **金山云-更新CDN证书** | 金山云自动更新CDN证书 |
|
||||
| 12.| **白山云-更新证书** | |
|
||||
| 13.| **cdnfly-部署证书到cdnfly** | cdnfly |
|
||||
| 14.| **天翼云-部署证书到CDN** | 部署证书到天翼云CDN和全站加速 |
|
||||
| 15.| **括彩云-部署到括彩云CDN** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 16.| **LeCDN-更新证书V2** | 支持新版本LeCDN |
|
||||
| 17.| **LeCDN-更新证书** | |
|
||||
| 18.| **Maoyun-更新猫云CDN证书** | |
|
||||
| 19.| **易盾-部署到易盾DCDN** | 主要是防御,http://user.yiduncdn.com/ |
|
||||
| 20.| **易盾-部署到易盾RCDN** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 21.| **雨云-更新证书** | app.rainyun.com |
|
||||
| 22.| **又拍云-部署证书到CDN/USS** | 支持又拍云CDN,又拍云云存储USS |
|
||||
| 23.| **网宿-更新证书** | 网宿证书自动更新 |
|
||||
| 24.| **西数-部署到虚拟主机** | 西部数码部署证书到虚拟主机 |
|
||||
| 25.| **我爱云-部署证书到我爱云CDN** | 部署证书到我爱云CDN |
|
||||
| 26.| **Zenlayer-刷新证书** | 刷新Zenlayer CDN证书 |
|
||||
| 7.| **Fastly-部署TLS激活** | 部署 Fastly 证书 (创建 TLS Activation 绑定证书到域名) |
|
||||
| 8.| **Fastly-清除缓存** | 清除 Fastly 指定 Service 的所有缓存 (Purge All) |
|
||||
| 9.| **Fastly-更新证书** | 自动更新 Fastly CDN 上的已有自定义证书 |
|
||||
| 10.| **Fastly-上传证书到Fastly** | 部署 / 上传 SSL/TLS 自定义证书到 Fastly CDN |
|
||||
| 11.| **FlexCDN-更新证书** | |
|
||||
| 12.| **Gcore-刷新Gcore证书** | 刷新现有的证书 |
|
||||
| 13.| **Gcore-部署证书到Gcore** | 仅上传 并不会部署到cdn |
|
||||
| 14.| **GoEdge-更新证书** | GoEdge |
|
||||
| 15.| **金山云-更新CDN证书** | 金山云自动更新CDN证书 |
|
||||
| 16.| **白山云-更新证书** | |
|
||||
| 17.| **cdnfly-部署证书到cdnfly** | cdnfly |
|
||||
| 18.| **天翼云-部署证书到CDN** | 部署证书到天翼云CDN和全站加速 |
|
||||
| 19.| **括彩云-部署到括彩云CDN** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 20.| **LeCDN-更新证书V2** | 支持新版本LeCDN |
|
||||
| 21.| **LeCDN-更新证书** | |
|
||||
| 22.| **Maoyun-更新猫云CDN证书** | |
|
||||
| 23.| **易盾-部署到易盾DCDN** | 主要是防御,http://user.yiduncdn.com/ |
|
||||
| 24.| **易盾-部署到易盾RCDN** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 25.| **雨云-更新证书** | app.rainyun.com |
|
||||
| 26.| **又拍云-部署证书到CDN/USS** | 支持又拍云CDN,又拍云云存储USS |
|
||||
| 27.| **网宿-更新证书** | 网宿证书自动更新,注意:仅支持普通CDN证书更新,CDN pro的证书请安装在线插件“网宿-CDNPro更新证书” |
|
||||
| 28.| **西数-部署到虚拟主机** | 西部数码部署证书到虚拟主机 |
|
||||
| 29.| **我爱云-部署证书到我爱云CDN** | 部署证书到我爱云CDN |
|
||||
| 30.| **Zenlayer-刷新证书** | 刷新Zenlayer CDN证书 |
|
||||
## 4. 面板
|
||||
|
||||
| 序号 | 名称 | 说明 |
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
`Certd`默认启用https,监听7002端口
|
||||
如果你想关闭https,或者修改端口,可以在环境变量中配置
|
||||
```shell
|
||||
CERTD_HTTPS_ENABLE=true
|
||||
CERTD_HTTPS_port=7002
|
||||
certd_https_enabled=true
|
||||
certd_https_port=7002
|
||||
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.44.2"
|
||||
"version": "1.45.2"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/publishlab/node-acme-client/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.45.1](https://github.com/publishlab/node-acme-client/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
# [1.45.0](https://github.com/publishlab/node-acme-client/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 支持google persist校验方式 ([97d2b50](https://github.com/publishlab/node-acme-client/commit/97d2b5007409ec79ea864d437c473a2f9b5dfae4))
|
||||
|
||||
## [1.44.4](https://github.com/publishlab/node-acme-client/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.44.3](https://github.com/publishlab/node-acme-client/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.44.2](https://github.com/publishlab/node-acme-client/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"type": "module",
|
||||
"module": "./dist/index.js",
|
||||
"main": "./dist/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.44.2",
|
||||
"@certd/basic": "^1.45.2",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.9.0",
|
||||
@@ -27,6 +27,7 @@
|
||||
"https-proxy-agent": "^7.0.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"node-forge": "^1.3.1",
|
||||
"psl": "^1.15.0",
|
||||
"punycode.js": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -76,5 +77,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// @ts-ignore
|
||||
import psl from "psl";
|
||||
|
||||
export function getDnsPersistIssuer(directoryUrl?: string) {
|
||||
if (!directoryUrl) {
|
||||
return "letsencrypt.org";
|
||||
}
|
||||
|
||||
let directory: URL;
|
||||
try {
|
||||
directory = new URL(directoryUrl);
|
||||
} catch {
|
||||
throw new Error(`ACME Directory URL无效,无法生成DNS持久验证Issuer:${directoryUrl}`);
|
||||
}
|
||||
|
||||
const issuer = psl.get(directory.hostname);
|
||||
if (!issuer) {
|
||||
throw new Error(`无法从ACME Directory URL获取可注册主域名:${directory.hostname}`);
|
||||
}
|
||||
return issuer;
|
||||
}
|
||||
@@ -79,3 +79,4 @@ export * from './verify.js'
|
||||
export * from './error.js'
|
||||
|
||||
export * from './util.js'
|
||||
export * from './dns-persist.js'
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **webhook:** 修复模版转义换行符,避免手动上传的证书报错 ([2735feb](https://github.com/certd/certd/commit/2735feb3302f8308708dafc8b295adcd4b91d7f0))
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
@@ -1 +1 @@
|
||||
21:57
|
||||
21:07
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -54,5 +54,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios, { AxiosHeaders, AxiosRequestConfig } from "axios";
|
||||
import axios, { AxiosHeaders, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import { ILogger, logger } from "./util.log.js";
|
||||
import { HttpProxyAgent } from "http-proxy-agent";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
@@ -118,7 +118,7 @@ export function getGlobalHeaders() {
|
||||
/**
|
||||
* @description 创建请求实例
|
||||
*/
|
||||
export function createAxiosService({ logger }: { logger: ILogger }) {
|
||||
export function createAxiosService({ logger }: { logger: ILogger }): HttpClient {
|
||||
// 创建一个 axios 实例
|
||||
const service = axios.create();
|
||||
|
||||
@@ -344,14 +344,24 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
return service;
|
||||
|
||||
return {
|
||||
...service,
|
||||
request: service.request,
|
||||
getCookies,
|
||||
getCookie,
|
||||
download,
|
||||
};
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
export const http = createAxiosService({ logger }) as HttpClient;
|
||||
|
||||
//默认返回 response.data 。 可以通过 returnOriginRes 来返回原始响应。
|
||||
export type HttpClientResponse<R> = any;
|
||||
export type HttpRequestConfig<D = any> = {
|
||||
skipSslVerify?: boolean;
|
||||
skipCheckRes?: boolean;
|
||||
skipCheckRes?: boolean; //非标参数,不需要让用户知道
|
||||
logReq?: boolean;
|
||||
logParams?: boolean;
|
||||
logRes?: boolean;
|
||||
@@ -359,8 +369,18 @@ export type HttpRequestConfig<D = any> = {
|
||||
httpProxy?: string;
|
||||
returnOriginRes?: boolean;
|
||||
} & AxiosRequestConfig<D>;
|
||||
|
||||
export type DownloadReq = {
|
||||
http: HttpClient;
|
||||
config: HttpRequestConfig;
|
||||
savePath: string;
|
||||
logger: ILogger;
|
||||
};
|
||||
export type HttpClient = {
|
||||
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;
|
||||
getCookies: (response: AxiosResponse) => { name: string; value: string }[];
|
||||
getCookie: (response: AxiosResponse, name: string) => string | null;
|
||||
download: (config: DownloadReq) => Promise<boolean>;
|
||||
};
|
||||
|
||||
// const http_proxy_backup = process.env.HTTP_PROXY || process.env.http_proxy;
|
||||
@@ -540,7 +560,7 @@ function splitNoProxyRules(noProxy?: string) {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export async function download(req: { http: HttpClient; config: HttpRequestConfig; savePath: string; logger: ILogger }) {
|
||||
export async function download(req: DownloadReq): Promise<boolean> {
|
||||
const { http, config, savePath, logger } = req;
|
||||
return safePromise((resolve, reject) => {
|
||||
http
|
||||
@@ -581,13 +601,44 @@ export async function download(req: { http: HttpClient; config: HttpRequestConfi
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析响应头中的 set-cookie
|
||||
*
|
||||
* Node 环境下 set-cookie 是字符串数组,每个元素是一个完整 Cookie:
|
||||
* `name=value; Path=/; HttpOnly; Expires=Wed, 21 Oct 2026 07:28:00 GMT`。
|
||||
* 只取分号前的 name=value,避免把 Path、Expires 等属性误解析成 Cookie;
|
||||
* value 本身可能包含 "="(如 base64),所以按第一个 "=" 切分。
|
||||
*/
|
||||
export function getCookies(response: any): { name: string; value: string }[] {
|
||||
const setCookie = response?.headers?.["set-cookie"];
|
||||
if (!setCookie) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const cookieList: string[] = Array.isArray(setCookie) ? setCookie : [setCookie];
|
||||
const cookies: { name: string; value: string }[] = [];
|
||||
for (const cookieItem of cookieList) {
|
||||
const cookiePair = String(cookieItem).split(";")[0].trim();
|
||||
if (!cookiePair) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const equalIndex = cookiePair.indexOf("=");
|
||||
if (equalIndex <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = cookiePair.substring(0, equalIndex).trim();
|
||||
const value = cookiePair.substring(equalIndex + 1).trim();
|
||||
cookies.push({ name, value });
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
export function getCookie(response: any, name: string) {
|
||||
const cookies = response.headers["set-cookie"];
|
||||
//根据name 返回对应的cookie
|
||||
const found = cookies.find((cookie: any) => cookie.includes(name));
|
||||
const cookies = getCookies(response);
|
||||
const found = cookies.find((cookie: any) => cookie.name === name);
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
const cookie = found.split(";")[0];
|
||||
return cookie.substring(cookie.indexOf("=") + 1);
|
||||
return found.value;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const stringUtils = {
|
||||
replaceTemplate(target: string, body: Record<string, unknown>, urlEncode = false) {
|
||||
let bodyStr = target;
|
||||
for (const key of Object.keys(body)) {
|
||||
const rawValue = body[key] == null ? "" : String(body[key]);
|
||||
let value = urlEncode ? encodeURIComponent(rawValue) : rawValue;
|
||||
value = value.replace(/[\u0000-\u001f]/g, char => {
|
||||
const escapes: Record<string, string> = { "\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r", "\t": "\\t" };
|
||||
return escapes[char] ?? `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
||||
});
|
||||
bodyStr = bodyStr.replaceAll(`\${${key}}`, value);
|
||||
bodyStr = bodyStr.replaceAll(`{${key}}`, value);
|
||||
}
|
||||
return bodyStr;
|
||||
},
|
||||
|
||||
maxLength(str?: string, length = 100) {
|
||||
if (str) {
|
||||
return str.length > length ? str.slice(0, length) + "..." : str;
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
|
||||
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
|
||||
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 缺失运行时嵌套依赖时添加清除缓存提示 ([2d0b85b](https://github.com/certd/certd/commit/2d0b85b3054fc1aa82b3ab5db34a2a56308f71f2))
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -21,8 +21,8 @@
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.44.2",
|
||||
"@certd/plus-core": "^1.44.2",
|
||||
"@certd/basic": "^1.45.2",
|
||||
"@certd/plus-core": "^1.45.2",
|
||||
"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": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -43,6 +43,7 @@ export type TaskInputDefine = {
|
||||
} & FormItemProps;
|
||||
|
||||
export type PluginDefine = Registrable & {
|
||||
i18n?: Record<string, any>;
|
||||
default?: any;
|
||||
group?: string;
|
||||
icon?: string;
|
||||
@@ -97,6 +98,7 @@ export type CertTargetItem = {
|
||||
|
||||
export type PipelineEvent<T = any> = {
|
||||
cert: T;
|
||||
certPassword?: string;
|
||||
runnableId: string;
|
||||
pipelineId: number;
|
||||
};
|
||||
|
||||
@@ -64,6 +64,28 @@ describe("RuntimeDepsService", () => {
|
||||
assert.equal(mod.value, 42);
|
||||
});
|
||||
|
||||
it("logs a cache clearing hint when a nested runtime dependency is missing", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-missing-nested-"));
|
||||
const packageDir = path.join(rootDir, "node_modules", "runtime-only");
|
||||
fs.mkdirSync(packageDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(rootDir, "package.json"), JSON.stringify({ name: "runtime-root", type: "module" }), "utf8");
|
||||
fs.writeFileSync(path.join(packageDir, "package.json"), JSON.stringify({ name: "runtime-only", main: "index.js" }), "utf8");
|
||||
fs.writeFileSync(path.join(packageDir, "index.js"), "module.exports = require('missing-nested');\n", "utf8");
|
||||
const logs: string[] = [];
|
||||
const logger = {
|
||||
info() {},
|
||||
warn() {},
|
||||
error(message: string, ...args: any[]) {
|
||||
logs.push([message, ...args.map(item => item?.message || String(item))].join(" "));
|
||||
},
|
||||
};
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
|
||||
await assert.rejects(() => service.importRuntime("runtime-only", logger));
|
||||
assert.ok(logs.some(message => message.includes("Cannot find module 'missing-nested'")));
|
||||
assert.ok(logs.some(message => message.includes("插件管理页面->清除缓存依赖->重启certd")));
|
||||
});
|
||||
|
||||
it("installs configured lazy dependency when import target is missing", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-"));
|
||||
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "lazy-pkg": "^1.2.3" } }, null);
|
||||
|
||||
@@ -251,6 +251,7 @@ export class RuntimeDepsService {
|
||||
}
|
||||
|
||||
let resolved: string;
|
||||
let fromRuntimeDeps = false;
|
||||
try {
|
||||
//尝试加载本项目依赖
|
||||
resolved = this.resolveProjectSpecifier(specifier).resolved;
|
||||
@@ -258,13 +259,22 @@ export class RuntimeDepsService {
|
||||
if (!this.isModuleNotFoundError(error)) {
|
||||
throw error;
|
||||
}
|
||||
fromRuntimeDeps = true;
|
||||
resolved = await this.resolveLazyOrInstallSpecifier(specifier, logger);
|
||||
}
|
||||
if (!resolved) {
|
||||
throw new Error(`依赖未安装成功: ${specifier}`);
|
||||
}
|
||||
|
||||
return await import(pathToFileURL(resolved).href);
|
||||
try {
|
||||
return await import(pathToFileURL(resolved).href);
|
||||
} catch (error: any) {
|
||||
if (fromRuntimeDeps && this.isModuleNotFoundError(error)) {
|
||||
logger.error?.("动态依赖加载失败:", error);
|
||||
logger.error?.("动态依赖加载失败,请前往插件管理页面->清除缓存依赖->重启certd后重试");
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//动态获取模块解析
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"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": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -37,5 +37,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
@@ -63,5 +63,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -21,7 +21,7 @@
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.44.2",
|
||||
"@certd/basic": "^1.45.2",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -38,5 +38,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,32 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 完善插件翻译 ([d0463ab](https://github.com/certd/certd/commit/d0463abc2607d22aba50ae3acc6584bbed6e3a0e))
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -29,11 +29,11 @@
|
||||
],
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.44.2",
|
||||
"@certd/basic": "^1.44.2",
|
||||
"@certd/pipeline": "^1.44.2",
|
||||
"@certd/plugin-lib": "^1.44.2",
|
||||
"@certd/plus-core": "^1.44.2",
|
||||
"@certd/acme-client": "^1.45.2",
|
||||
"@certd/basic": "^1.45.2",
|
||||
"@certd/pipeline": "^1.45.2",
|
||||
"@certd/plugin-lib": "^1.45.2",
|
||||
"@certd/plus-core": "^1.45.2",
|
||||
"@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": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -133,12 +133,12 @@ export class SysPrivateSettings extends BaseSettings {
|
||||
// 内置项不在此配置 Directory URL:其端点由运行时按加密算法(pkType)通过 acme.getDirectoryUrl 获取,
|
||||
// 例如 ZeroSSL、SSL.com 的 RSA 与 EC 证书使用不同的端点,写死单一 URL 会导致 EC 申请走错端点。
|
||||
customAcmeProviders?: CustomAcmeProvider[] = [
|
||||
{ sslProvider: "letsencrypt", title: "Let's Encrypt", directoryUrl: "", needEAB: false, builtIn: true },
|
||||
{ sslProvider: "letsencrypt_staging", title: "Let's Encrypt测试环境", directoryUrl: "", needEAB: false, builtIn: true },
|
||||
{ sslProvider: "letsencrypt", title: "Let's Encrypt(支持7天IP证书)", directoryUrl: "", needEAB: false, builtIn: true },
|
||||
{ sslProvider: "google", title: "Google", directoryUrl: "", needEAB: true, builtIn: true },
|
||||
{ sslProvider: "zerossl", title: "ZeroSSL", directoryUrl: "", needEAB: true, builtIn: true },
|
||||
{ sslProvider: "litessl", title: "litessl", directoryUrl: "", needEAB: true, builtIn: true },
|
||||
{ sslProvider: "litessl", title: "Litessl", directoryUrl: "", needEAB: true, builtIn: true },
|
||||
{ sslProvider: "sslcom", title: "SSL.com", directoryUrl: "", needEAB: true, builtIn: true },
|
||||
{ sslProvider: "letsencrypt_staging", title: "Let's Encrypt测试环境(只能生成测试证书)", directoryUrl: "", needEAB: false, builtIn: true },
|
||||
];
|
||||
|
||||
removeSecret() {
|
||||
|
||||
@@ -174,11 +174,11 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
throw new ValidateException("userId不能为空");
|
||||
}
|
||||
if (userId !== entity.userId) {
|
||||
throw new PermissionException("您对该Access授权无访问权限");
|
||||
throw new PermissionException(`您对该Access授权${entity.name}(${entity.id})无访问权限`);
|
||||
}
|
||||
}
|
||||
if (projectId != null && projectId !== entity.projectId) {
|
||||
throw new PermissionException("您对该Access授权无访问权限");
|
||||
throw new PermissionException(`您对该Access授权${entity.name}(${entity.id})无访问权限`);
|
||||
}
|
||||
|
||||
// const access = accessRegistry.get(entity.type);
|
||||
|
||||
@@ -26,6 +26,7 @@ export type AddonDefine = Registrable & {
|
||||
};
|
||||
showTest?: boolean;
|
||||
icon?: string;
|
||||
i18n?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type AddonInstanceConfig = {
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -52,5 +52,5 @@
|
||||
"typeorm": "^0.3.20",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -20,7 +20,7 @@
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/plugin-lib": "^1.44.2"
|
||||
"@certd/plugin-lib": "^1.45.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.12",
|
||||
@@ -38,5 +38,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -17,9 +17,9 @@
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.44.2",
|
||||
"@certd/basic": "^1.44.2",
|
||||
"@certd/pipeline": "^1.44.2",
|
||||
"@certd/acme-client": "^1.45.2",
|
||||
"@certd/basic": "^1.45.2",
|
||||
"@certd/pipeline": "^1.45.2",
|
||||
"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": "fe738fa7d4622016ed7d506c501df85393b6c857"
|
||||
"gitHead": "e9b9839d79532e4961dc0de5c097f81235e557f6"
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -3,6 +3,60 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.45.2](https://github.com/certd/certd/compare/v1.45.1...v1.45.2) (2026-10-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复腾讯云CLB部署证书逻辑 ([a67d5bc](https://github.com/certd/certd/commit/a67d5bca2dd12e157e45ec1b8abff54efcbdfa84))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 为PEM输入组件添加文件拖拽上传功能 ([cfd3844](https://github.com/certd/certd/commit/cfd384485f212cdea0b08e4b35a3f19a1acfe026))
|
||||
* 新增更新金山云KCM SLB证书插件 ([62751be](https://github.com/certd/certd/commit/62751bee7a5e00245bb4dd840a5f4ea35565616a))
|
||||
* 支持ikuai部署证书 ([5f83693](https://github.com/certd/certd/commit/5f83693423ec5ccd08d7bb03e4fb8315dbb8b931))
|
||||
|
||||
## [1.45.1](https://github.com/certd/certd/compare/v1.45.0...v1.45.1) (2026-10-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复批量修改证书申请参数,无法选择证书颁发机构的bug ([9d9ae64](https://github.com/certd/certd/commit/9d9ae64aff76a09d64a5be04968ff0cc3ad097a4))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件市场新增同步按钮并调整搜索区域布局 ([41ca198](https://github.com/certd/certd/commit/41ca198116e1890e65973c4317acfecb32126951))
|
||||
* 多语言国际化优化,插件支持国际化配置 ([8f1b38a](https://github.com/certd/certd/commit/8f1b38ad7d445473ae71d537ad8e465f2e55ebc7))
|
||||
* 开放接口支持返回证书密码,需要在openKey主动开启该设置,客户端部署证书时可以直接获取密码部署 ([3e001b8](https://github.com/certd/certd/commit/3e001b8ba431eb9af8c4ea1b20118bcf5e7ebce4))
|
||||
* 证书申请成功通知支持批量修改 ([22715a4](https://github.com/certd/certd/commit/22715a430e24a6132482cadb6b217acc283f270c))
|
||||
|
||||
# [1.45.0](https://github.com/certd/certd/compare/v1.44.4...v1.45.0) (2026-10-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** 新增客户端管理功能 ([3dbbe72](https://github.com/certd/certd/commit/3dbbe72fc15c114ca6b5e8ff45cb21f258b80650))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **monitor:** 站点证书监控支持手动选中触发检查 ([f60e63f](https://github.com/certd/certd/commit/f60e63fbaf00460d5050cd7705bd75d57dec202d))
|
||||
* **open-api:** openkey增加用途备注 ([bbeaf61](https://github.com/certd/certd/commit/bbeaf617d19750515f6eca51863e74e23bc1de50))
|
||||
|
||||
## [1.44.4](https://github.com/certd/certd/compare/v1.44.3...v1.44.4) (2026-09-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复添加步骤会修改所有步骤标题的bug ([51e83e4](https://github.com/certd/certd/commit/51e83e4c38f7f319314fdc38ef33cf5720b2dde0))
|
||||
* 修复证书插件配置无法打开的bug ([5563abb](https://github.com/certd/certd/commit/5563abb13a62140f4dee668ab768cf4ff9371ddb))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 新增余额管理,管理员充值功能 ([bded368](https://github.com/certd/certd/commit/bded368cbb8974b6937ae260e448c25fae5c33ad))
|
||||
* **ui:** 商业版套餐购买不要求设置支付方式 ([e971145](https://github.com/certd/certd/commit/e9711456860d7db40914a0c295534f7bca56f83c))
|
||||
|
||||
## [1.44.3](https://github.com/certd/certd/compare/v1.44.2...v1.44.3) (2026-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **plugin:** 修复插件配置保存与加载的相关问题 ([bd94334](https://github.com/certd/certd/commit/bd9433478a43d5d64ce6370386d0fe0aaff07d2e))
|
||||
|
||||
## [1.44.2](https://github.com/certd/certd/compare/v1.44.1...v1.44.2) (2026-09-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -13,7 +13,7 @@ const { packages } = getPackagesSync(process.cwd());
|
||||
|
||||
const tailwindPackages = [];
|
||||
|
||||
packages.forEach((pkg) => {
|
||||
packages.forEach(pkg => {
|
||||
// apps目录下和 @vben-core/tailwind-ui 包需要使用到 tailwindcss ui
|
||||
// if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) {
|
||||
tailwindPackages.push(pkg.dir);
|
||||
@@ -25,96 +25,96 @@ const shadcnUiColors = {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
hover: "hsl(var(--accent-hover))",
|
||||
lighter: "has(val(--accent-lighter))"
|
||||
lighter: "has(val(--accent-lighter))",
|
||||
},
|
||||
background: {
|
||||
deep: "hsl(var(--background-deep))",
|
||||
DEFAULT: "hsl(var(--background))"
|
||||
DEFAULT: "hsl(var(--background))",
|
||||
},
|
||||
border: {
|
||||
DEFAULT: "hsl(var(--border))"
|
||||
DEFAULT: "hsl(var(--border))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))"
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
...createColorsPalette("destructive"),
|
||||
DEFAULT: "hsl(var(--destructive))"
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
},
|
||||
|
||||
foreground: {
|
||||
DEFAULT: "hsl(var(--foreground))"
|
||||
DEFAULT: "hsl(var(--foreground))",
|
||||
},
|
||||
|
||||
input: {
|
||||
background: "hsl(var(--input-background))",
|
||||
DEFAULT: "hsl(var(--input))"
|
||||
DEFAULT: "hsl(var(--input))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))"
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))"
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
primary: {
|
||||
...createColorsPalette("primary"),
|
||||
DEFAULT: "hsl(var(--primary))"
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
},
|
||||
|
||||
ring: "hsl(var(--ring))",
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
desc: "hsl(var(--secondary-desc))",
|
||||
foreground: "hsl(var(--secondary-foreground))"
|
||||
}
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
};
|
||||
|
||||
const customColors = {
|
||||
green: {
|
||||
...createColorsPalette("green"),
|
||||
foreground: "hsl(var(--success-foreground))"
|
||||
foreground: "hsl(var(--success-foreground))",
|
||||
},
|
||||
header: {
|
||||
DEFAULT: "hsl(var(--header))"
|
||||
DEFAULT: "hsl(var(--header))",
|
||||
},
|
||||
heavy: {
|
||||
DEFAULT: "hsl(var(--heavy))",
|
||||
foreground: "hsl(var(--heavy-foreground))"
|
||||
foreground: "hsl(var(--heavy-foreground))",
|
||||
},
|
||||
main: {
|
||||
DEFAULT: "hsl(var(--main))"
|
||||
DEFAULT: "hsl(var(--main))",
|
||||
},
|
||||
overlay: {
|
||||
content: "hsl(var(--overlay-content))",
|
||||
DEFAULT: "hsl(var(--overlay))"
|
||||
DEFAULT: "hsl(var(--overlay))",
|
||||
},
|
||||
red: {
|
||||
...createColorsPalette("red"),
|
||||
foreground: "hsl(var(--destructive-foreground))"
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
sidebar: {
|
||||
deep: "hsl(var(--sidebar-deep))",
|
||||
DEFAULT: "hsl(var(--sidebar))"
|
||||
DEFAULT: "hsl(var(--sidebar))",
|
||||
},
|
||||
success: {
|
||||
...createColorsPalette("success"),
|
||||
DEFAULT: "hsl(var(--success))"
|
||||
DEFAULT: "hsl(var(--success))",
|
||||
},
|
||||
warning: {
|
||||
...createColorsPalette("warning"),
|
||||
DEFAULT: "hsl(var(--warning))"
|
||||
DEFAULT: "hsl(var(--warning))",
|
||||
},
|
||||
yellow: {
|
||||
...createColorsPalette("yellow"),
|
||||
foreground: "hsl(var(--warning-foreground))"
|
||||
}
|
||||
foreground: "hsl(var(--warning-foreground))",
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
content: ["./index.html", ...tailwindPackages.map((item) => path.join(item, "src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}"))],
|
||||
content: ["./index.html", ...tailwindPackages.map(item => path.join(item, "src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}"))],
|
||||
darkMode: "selector",
|
||||
plugins: [animate, typographyPlugin, addDynamicIconSelectors(), enterAnimationPlugin],
|
||||
prefix: "",
|
||||
@@ -123,8 +123,8 @@ export default {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px"
|
||||
}
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
animation: {
|
||||
@@ -132,64 +132,67 @@ export default {
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"collapsible-down": "collapsible-down 0.2s ease-in-out",
|
||||
"collapsible-up": "collapsible-up 0.2s ease-in-out",
|
||||
float: "float 5s linear 0ms infinite"
|
||||
float: "float 5s linear 0ms infinite",
|
||||
},
|
||||
flex: {
|
||||
2: "2 1 0%",
|
||||
},
|
||||
|
||||
animationDuration: {
|
||||
2000: "2000ms",
|
||||
3000: "3000ms"
|
||||
3000: "3000ms",
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
xl: "calc(var(--radius) + 4px)"
|
||||
xl: "calc(var(--radius) + 4px)",
|
||||
},
|
||||
boxShadow: {
|
||||
float: `0 6px 16px 0 rgb(0 0 0 / 8%),
|
||||
0 3px 6px -4px rgb(0 0 0 / 12%),
|
||||
0 9px 28px 8px rgb(0 0 0 / 5%)`
|
||||
0 9px 28px 8px rgb(0 0 0 / 5%)`,
|
||||
},
|
||||
colors: {
|
||||
...customColors,
|
||||
...shadcnUiColors
|
||||
...shadcnUiColors,
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
"var(--font-family)"
|
||||
"var(--font-family)",
|
||||
// ...defaultTheme.fontFamily.sans
|
||||
]
|
||||
],
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" }
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" }
|
||||
to: { height: "0" },
|
||||
},
|
||||
"collapsible-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-collapsible-content-height)" }
|
||||
to: { height: "var(--radix-collapsible-content-height)" },
|
||||
},
|
||||
"collapsible-up": {
|
||||
from: { height: "var(--radix-collapsible-content-height)" },
|
||||
to: { height: "0" }
|
||||
to: { height: "0" },
|
||||
},
|
||||
float: {
|
||||
"0%": { transform: "translateY(0)" },
|
||||
"50%": { transform: "translateY(-20px)" },
|
||||
"100%": { transform: "translateY(0)" }
|
||||
}
|
||||
"100%": { transform: "translateY(0)" },
|
||||
},
|
||||
},
|
||||
zIndex: {
|
||||
100: "100",
|
||||
1000: "1000"
|
||||
}
|
||||
}
|
||||
1000: "1000",
|
||||
},
|
||||
},
|
||||
},
|
||||
safelist: ["dark"]
|
||||
safelist: ["dark"],
|
||||
};
|
||||
|
||||
function createColorsPalette(name) {
|
||||
@@ -249,6 +252,6 @@ function createColorsPalette(name) {
|
||||
// 主色文本激活态
|
||||
"text-active": `hsl(var(--${name}-700))`,
|
||||
// 主色文本悬浮态
|
||||
"text-hover": `hsl(var(--${name}-600))`
|
||||
"text-hover": `hsl(var(--${name}-600))`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ export default {
|
||||
"postcss-import": {},
|
||||
"postcss-preset-env": {},
|
||||
tailwindcss: { config },
|
||||
"tailwindcss/nesting": {}
|
||||
}
|
||||
"tailwindcss/nesting": {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.44.2",
|
||||
"version": "1.45.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -34,11 +34,11 @@
|
||||
"@aws-sdk/s3-request-presigner": "^3.964.0",
|
||||
"@certd/vue-js-cron-light": "^4.0.14",
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@fast-crud/editor-code": "^1.28.7",
|
||||
"@fast-crud/fast-crud": "^1.28.7",
|
||||
"@fast-crud/fast-extends": "^1.28.7",
|
||||
"@fast-crud/ui-antdv4": "^1.28.7",
|
||||
"@fast-crud/ui-interface": "^1.28.7",
|
||||
"@fast-crud/editor-code": "^1.29.0",
|
||||
"@fast-crud/fast-crud": "^1.29.0",
|
||||
"@fast-crud/fast-extends": "^1.29.0",
|
||||
"@fast-crud/ui-antdv4": "^1.29.0",
|
||||
"@fast-crud/ui-interface": "^1.29.0",
|
||||
"@iconify/tailwind": "^1.2.0",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@manypkg/get-packages": "^2.2.2",
|
||||
@@ -104,8 +104,8 @@
|
||||
"zod-defaults": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/lib-iframe": "^1.44.2",
|
||||
"@certd/pipeline": "^1.44.2",
|
||||
"@certd/lib-iframe": "^1.45.2",
|
||||
"@certd/pipeline": "^1.45.2",
|
||||
"@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>
|
||||
|
||||
+7
-5
@@ -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;
|
||||
|
||||
+11
-11
@@ -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();
|
||||
|
||||
+7
-7
@@ -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>
|
||||
|
||||
+5
-5
@@ -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">
|
||||
|
||||
+5
-5
@@ -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>
|
||||
|
||||
+12
-12
@@ -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",
|
||||
},
|
||||
]);
|
||||
|
||||
+6
-6
@@ -14,7 +14,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
||||
for (const subDomain of subDomains) {
|
||||
const plan = value[domain].cnameVerifyPlan[subDomain];
|
||||
if (plan.status !== "valid") {
|
||||
throw new Error($t("certd.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
|
||||
throw new Error($t("certd.certdomain.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
||||
for (const item of domains) {
|
||||
//如果有通配符域名则不允许使用http校验
|
||||
if (item.startsWith("*.")) {
|
||||
throw new Error($t("certd.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
|
||||
throw new Error($t("certd.certdomain.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,19 +32,19 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
||||
for (const subDomain of subDomains) {
|
||||
const plan = value[domain].httpVerifyPlan[subDomain];
|
||||
if (!plan.httpUploaderType) {
|
||||
throw new Error($t("certd.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
|
||||
throw new Error($t("certd.certdomain.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
|
||||
}
|
||||
if (!plan.httpUploaderAccess) {
|
||||
throw new Error($t("certd.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
|
||||
throw new Error($t("certd.certdomain.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
|
||||
}
|
||||
if (!plan.httpUploadRootDir) {
|
||||
throw new Error($t("certd.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
|
||||
throw new Error($t("certd.certdomain.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type === "dns") {
|
||||
if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) {
|
||||
throw new Error($t("certd.verifyPlan.errors.dnsProviderRequired", { domain }));
|
||||
throw new Error($t("certd.certdomain.verifyPlan.errors.dnsProviderRequired", { domain }));
|
||||
}
|
||||
} else if (type === "dns-persist") {
|
||||
const subDomains = Object.keys(value[domain].dnsPersistVerifyPlan || {});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="api-test">
|
||||
<div>
|
||||
<fs-button :loading="loading" type="primary" :text="t('certd.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
|
||||
<fs-button :loading="loading" type="primary" :text="t('certd.common.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
|
||||
</div>
|
||||
|
||||
<div class="helper" :class="{ error: hasError }">
|
||||
@@ -64,14 +64,14 @@ const doTest = async () => {
|
||||
{
|
||||
onError(err: any) {
|
||||
hasError.value = true;
|
||||
message.value = t("certd.pluginCommon.errorWithMessage", { message: err.message });
|
||||
message.value = t("certd.common.pluginCommon.errorWithMessage", { message: err.message });
|
||||
},
|
||||
showErrorNotify: false,
|
||||
}
|
||||
);
|
||||
message.value = t("certd.pluginCommon.testRequestSuccess");
|
||||
message.value = t("certd.common.pluginCommon.testRequestSuccess");
|
||||
if (res) {
|
||||
message.value += t("certd.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
|
||||
message.value += t("certd.common.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
||||
@@ -42,7 +42,7 @@ function findStepFromPipeline(targetStepId: string) {
|
||||
const errorRef = ref("");
|
||||
function getStepIdFromInputKey(inputKey: string) {
|
||||
if (!inputKey) {
|
||||
errorRef.value = t("certd.pluginCommon.selectCertFirst");
|
||||
errorRef.value = t("certd.common.pluginCommon.selectCertFirst");
|
||||
return;
|
||||
}
|
||||
return inputKey.split(".")[1];
|
||||
@@ -51,7 +51,7 @@ function getDomainFromPipeline(inputKey: string) {
|
||||
let targetStepId = getStepIdFromInputKey(inputKey);
|
||||
let certStep = findStepFromPipeline(targetStepId);
|
||||
if (!certStep) {
|
||||
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
|
||||
errorRef.value = t("certd.common.pluginCommon.targetStepNotFound");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ function getDomainFromPipeline(inputKey: string) {
|
||||
targetStepId = getStepIdFromInputKey(firstLevelValue);
|
||||
certStep = findStepFromPipeline(targetStepId);
|
||||
if (!certStep) {
|
||||
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
|
||||
errorRef.value = t("certd.common.pluginCommon.targetStepNotFound");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="domain-select">
|
||||
<div class="flex flex-row">
|
||||
<a-select
|
||||
@@ -18,12 +18,12 @@
|
||||
<template v-if="search">
|
||||
<div class="flex w-full items-center justify-between flex-wrap" style="padding: 4px 8px">
|
||||
<div class="flex-1 flex flex-row items-center">
|
||||
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
|
||||
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.pluginCommon.search") }}</fs-button>
|
||||
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.common.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
|
||||
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.common.pluginCommon.search") }}</fs-button>
|
||||
</div>
|
||||
<div class="manager flex flex-row items-center">
|
||||
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.pluginCommon.importDomain") }}</fs-button>
|
||||
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.pluginCommon.manageDomain") }}</fs-button>
|
||||
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.common.pluginCommon.importDomain") }}</fs-button>
|
||||
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.common.pluginCommon.manageDomain") }}</fs-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
|
||||
@@ -49,7 +49,7 @@
|
||||
</a-select>
|
||||
<div class="ml-5 flex flex-row items-center">
|
||||
<fs-button title="复制全部域名" icon="ion:copy-outline" class="domain-select-copy-icon mr-5" @click="copySelectedDomains" />
|
||||
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="helper" :class="{ error: hasError }">
|
||||
|
||||
@@ -8,6 +8,7 @@ import { usePluginStore } from "/@/store/plugin";
|
||||
|
||||
export default {
|
||||
name: "OutputSelector",
|
||||
// inheritAttrs: false,
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
@@ -30,6 +31,9 @@ export default {
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
|
||||
// const attrs = ctx.attrs;
|
||||
// console.log(attrs);
|
||||
|
||||
async function onCreate() {
|
||||
await pluginStore.init();
|
||||
options.value = pluginStore.group.getPreStepOutputOptions({
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a-auto-complete class="remote-auto-complete-input" :filter-option="filterOption" :options="optionsRef" :value="value" v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)">
|
||||
</a-auto-complete>
|
||||
<div class="ml-5">
|
||||
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="helper" :class="{ error: hasError }">
|
||||
@@ -96,16 +96,16 @@ const getOptions = async () => {
|
||||
{
|
||||
onError(err: any) {
|
||||
hasError.value = true;
|
||||
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
|
||||
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
|
||||
},
|
||||
showErrorNotify: false,
|
||||
}
|
||||
);
|
||||
const list = res?.list || res || [];
|
||||
if (list.length > 0) {
|
||||
message.value = t("certd.pluginCommon.getDataSuccessSelect");
|
||||
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
|
||||
} else {
|
||||
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
|
||||
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
|
||||
}
|
||||
optionsRef.value = list;
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ async function openDialog() {
|
||||
saveRemind: false,
|
||||
},
|
||||
afterSubmit() {
|
||||
notification.success({ message: t("certd.operationSuccess") });
|
||||
notification.success({ message: t("certd.common.operationSuccess") });
|
||||
},
|
||||
async doSubmit({ form }: any) {
|
||||
return await doPluginFormSubmit(form);
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<template #dropdownRender="{ menuNode: menu }">
|
||||
<template v-if="search">
|
||||
<div class="flex w-full" style="padding: 4px 8px">
|
||||
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.searchKeyword')" @keydown.enter="doSearch" />
|
||||
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.common.pluginCommon.searchKeyword')" @keydown.enter="doSearch" />
|
||||
<a-button class="ml-2" :loading="loading" type="text" @click="doSearch">
|
||||
<template #icon>
|
||||
<search-outlined />
|
||||
</template>
|
||||
{{ t("certd.pluginCommon.search") }}
|
||||
{{ t("certd.common.pluginCommon.search") }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
|
||||
@@ -26,7 +26,7 @@
|
||||
</template>
|
||||
</a-select>
|
||||
<div class="ml-5 flex flex-row no-wrap">
|
||||
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
<UploadCert v-if="uploadCert" class="ml-5" v-bind="uploadCert" @submit="refreshOptions"></UploadCert>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
||||
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
|
||||
import { defineComponent, inject, ref, useAttrs, watch, Ref, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { PluginDefine } from "@certd/pipeline";
|
||||
import { getInputFromForm } from "./utils";
|
||||
@@ -81,6 +81,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
function updateValue(value: any) {
|
||||
debugger;
|
||||
if (props.single === true) {
|
||||
const last = value?.[value.length - 1];
|
||||
emit("update:value", last);
|
||||
@@ -103,8 +104,18 @@ function emitSelectedChange(value: any) {
|
||||
emit("selected-change", props.single === true ? options[0] : options);
|
||||
}
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const originalAttrs = useAttrs();
|
||||
const attrs = computed(() => {
|
||||
const tags = {
|
||||
mode: "tags",
|
||||
allowClear: true,
|
||||
single: false,
|
||||
};
|
||||
return {
|
||||
...tags,
|
||||
...originalAttrs,
|
||||
};
|
||||
});
|
||||
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => {
|
||||
return {};
|
||||
});
|
||||
@@ -177,7 +188,7 @@ const getOptions = async () => {
|
||||
{
|
||||
onError(err: any) {
|
||||
hasError.value = true;
|
||||
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
|
||||
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
|
||||
optionsRef.value = [];
|
||||
},
|
||||
showErrorNotify: false,
|
||||
@@ -185,9 +196,9 @@ const getOptions = async () => {
|
||||
);
|
||||
let list = res?.list || res || [];
|
||||
if (list.length > 0) {
|
||||
message.value = t("certd.pluginCommon.getDataSuccessSelect");
|
||||
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
|
||||
} else {
|
||||
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
|
||||
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
|
||||
}
|
||||
list = list.map((item: any) => {
|
||||
return {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex flex-row">
|
||||
<a-tree-select class="remote-tree-select-input" :tree-data="optionsRef" :value="value" tree-checkable allow-clear v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)"> </a-tree-select>
|
||||
<div class="ml-5">
|
||||
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
<fs-button :loading="loading" :title="t('certd.common.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="helper" :class="{ error: hasError }">
|
||||
@@ -107,16 +107,16 @@ const getOptions = async () => {
|
||||
{
|
||||
onError(err: any) {
|
||||
hasError.value = true;
|
||||
message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
|
||||
message.value = t("certd.common.pluginCommon.getOptionsError", { message: err.message });
|
||||
},
|
||||
showErrorNotify: false,
|
||||
}
|
||||
);
|
||||
const list = res?.list || res || [];
|
||||
if (list.length > 0) {
|
||||
message.value = t("certd.pluginCommon.getDataSuccessSelect");
|
||||
message.value = t("certd.common.pluginCommon.getDataSuccessSelect");
|
||||
} else {
|
||||
message.value = t("certd.pluginCommon.getDataSuccessEmpty");
|
||||
message.value = t("certd.common.pluginCommon.getDataSuccessEmpty");
|
||||
}
|
||||
optionsRef.value = list;
|
||||
pagerRef.value.total = list.length;
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="template-textarea">
|
||||
<a-textarea :value="value" v-bind="$attrs" @update:value="emit('update:value', $event)" />
|
||||
<div class="template-textarea__toolbar">
|
||||
<a-dropdown trigger="click">
|
||||
<a-button>
|
||||
<span class="template-textarea__selected">{{ selectedName }}</span>
|
||||
<fs-icon icon="ion:chevron-down" class="ml-1" />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="handleMenuClick">
|
||||
<a-menu-item v-for="item in templates" :key="item.id">
|
||||
<div class="template-textarea__menu-item">
|
||||
<span>{{ item.name }}</span
|
||||
><a-button type="link" danger size="small" @click.stop="deleteTemplate(item.id)">删除</a-button>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="templates.length === 0" disabled>暂无模板</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="save">保存当前脚本为模板</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { notification } from "ant-design-vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { request } from "/@/api/service";
|
||||
import { useProjectStore } from "/@/store/project";
|
||||
import { useFormDialog } from "/@/use/use-dialog";
|
||||
import { nanoid } from "nanoid";
|
||||
defineOptions({ name: "TemplateTextarea", inheritAttrs: false });
|
||||
const props = withDefaults(defineProps<{ value?: string; templateType?: string }>(), { value: "", templateType: "script" });
|
||||
const emit = defineEmits<{ "update:value": [value: string] }>();
|
||||
const templates = ref<Array<{ id: string; name: string; content: string }>>([]);
|
||||
const selected = ref<string>();
|
||||
const key = "script_templates";
|
||||
const { openFormDialog } = useFormDialog();
|
||||
const selectedName = ref("选择脚本模板");
|
||||
async function load() {
|
||||
const entity = await request({ url: "/user/settings/get", method: "post", params: { key } });
|
||||
try {
|
||||
templates.value = entity?.setting ? JSON.parse(entity.setting)[props.templateType] || [] : [];
|
||||
} catch {
|
||||
templates.value = [];
|
||||
}
|
||||
}
|
||||
async function persist() {
|
||||
const entity = await request({ url: "/user/settings/get", method: "post", params: { key } });
|
||||
let setting: Record<string, unknown[]> = {};
|
||||
try {
|
||||
setting = entity?.setting ? JSON.parse(entity.setting) : {};
|
||||
} catch {
|
||||
setting = {};
|
||||
}
|
||||
setting[props.templateType] = templates.value;
|
||||
await request({
|
||||
url: "/user/settings/save",
|
||||
method: "post",
|
||||
data: {
|
||||
key,
|
||||
setting: JSON.stringify(setting),
|
||||
},
|
||||
});
|
||||
}
|
||||
function handleMenuClick({ key: id }: { key: string }) {
|
||||
if (id === "save") {
|
||||
saveTemplate();
|
||||
return;
|
||||
}
|
||||
const item = templates.value.find(template => template.id === id);
|
||||
if (item) {
|
||||
selected.value = item.id;
|
||||
selectedName.value = item.name;
|
||||
emit("update:value", item.content);
|
||||
}
|
||||
}
|
||||
async function saveTemplate() {
|
||||
if (!props.value?.trim()) {
|
||||
notification.warning({ message: "请输入脚本内容后再保存模板" });
|
||||
return;
|
||||
}
|
||||
await openFormDialog({
|
||||
title: "保存脚本模板",
|
||||
initialForm: { name: "" },
|
||||
columns: { name: { title: "模板名称", type: "text", form: { rules: [{ required: true, message: "请输入模板名称" }] } } },
|
||||
async onSubmit(form: { name: string }) {
|
||||
const name = form.name?.trim();
|
||||
if (!name) {
|
||||
notification.warning({ message: "请输入模板名称" });
|
||||
throw new Error("模板名称不能为空");
|
||||
}
|
||||
const id = nanoid();
|
||||
templates.value.push({ id, name, content: props.value });
|
||||
await persist();
|
||||
selected.value = id;
|
||||
selectedName.value = name;
|
||||
notification.success({ message: "模板保存成功" });
|
||||
},
|
||||
});
|
||||
}
|
||||
async function deleteTemplate(id: string) {
|
||||
templates.value = templates.value.filter(item => item.id !== id);
|
||||
if (selected.value === id) {
|
||||
selected.value = undefined;
|
||||
selectedName.value = "选择脚本模板";
|
||||
}
|
||||
await persist();
|
||||
notification.success({ message: "模板删除成功" });
|
||||
}
|
||||
watch(() => props.templateType, load);
|
||||
onMounted(load);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.template-textarea__toolbar {
|
||||
display: flex;
|
||||
margin-top: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
.template-textarea__selected {
|
||||
display: inline-block;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.template-textarea__menu-item {
|
||||
display: flex;
|
||||
min-width: 220px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="upload-cert">
|
||||
<fs-button v-model:loading="loading" type="primary" :text="t('certd.pluginCommon.upload')" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
|
||||
<fs-button v-model:loading="loading" type="primary" :text="t('certd.common.pluginCommon.upload')" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -30,18 +30,18 @@ const getScope: any = inject("get:scope", () => {
|
||||
const getPluginType: any = inject("get:plugin:type", () => {
|
||||
return "plugin";
|
||||
});
|
||||
const title = computed(() => props.title || t("certd.pluginCommon.uploadCert"));
|
||||
const title = computed(() => props.title || t("certd.common.pluginCommon.uploadCert"));
|
||||
function openUploadCertDialog() {
|
||||
const columns = merge(
|
||||
{
|
||||
certName: {
|
||||
title: t("certd.pluginCommon.certName"),
|
||||
title: t("certd.common.pluginCommon.certName"),
|
||||
form: {
|
||||
component: {
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
helper: t("certd.pluginCommon.certNameHelper"),
|
||||
helper: t("certd.common.pluginCommon.certNameHelper"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -51,7 +51,7 @@ function openUploadCertDialog() {
|
||||
title: title.value,
|
||||
columns: {
|
||||
certName: {
|
||||
title: t("certd.pluginCommon.certName"),
|
||||
title: t("certd.common.pluginCommon.certName"),
|
||||
form: {
|
||||
component: {
|
||||
name: "a-input",
|
||||
@@ -86,7 +86,7 @@ function openUploadCertDialog() {
|
||||
showErrorNotify: true,
|
||||
}
|
||||
);
|
||||
notification.success({ message: t("certd.pluginCommon.uploadSuccess") });
|
||||
notification.success({ message: t("certd.common.pluginCommon.uploadSuccess") });
|
||||
emit("submit");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
||||
@@ -14,6 +14,7 @@ import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
|
||||
import ApiTest from "./common/api-test.vue";
|
||||
import RefreshInput from "./common/refresh-input.vue";
|
||||
import ParamsShow from "./common/params-show.vue";
|
||||
import TemplateTextarea from "./common/template-textarea.vue";
|
||||
export * from "./cert/index.js";
|
||||
export default {
|
||||
install(app: any) {
|
||||
@@ -35,5 +36,6 @@ export default {
|
||||
app.component("DomainSelector", DomainSelector);
|
||||
app.component("InputPassword", InputPassword);
|
||||
app.component("ParamsShow", ParamsShow);
|
||||
app.component("TemplateTextarea", TemplateTextarea);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,10 +4,10 @@ import { $t } from "/@/locales";
|
||||
function createChallengeTypeDict() {
|
||||
return dict({
|
||||
data: [
|
||||
{ value: "dns", label: $t("certd.verifyPlan.dnsChallenge"), color: "green" },
|
||||
{ value: "dns", label: $t("certd.certdomain.verifyPlan.dnsChallenge"), color: "green" },
|
||||
{ value: "dns-persist", label: "DNS持久验证", color: "cyan" },
|
||||
{ value: "cname", label: $t("certd.verifyPlan.cnameProxyChallenge"), color: "blue" },
|
||||
{ value: "http", label: $t("certd.verifyPlan.httpChallenge"), color: "yellow" },
|
||||
{ value: "cname", label: $t("certd.certdomain.verifyPlan.cnameProxyChallenge"), color: "blue" },
|
||||
{ value: "http", label: $t("certd.certdomain.verifyPlan.httpChallenge"), color: "yellow" },
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -18,11 +18,11 @@ function createUploaderTypeDict() {
|
||||
{ label: "SFTP", value: "sftp" },
|
||||
{ label: "SCP", value: "scp" },
|
||||
{ label: "FTP", value: "ftp" },
|
||||
{ label: $t("certd.verifyPlan.uploader.aliyunOss"), value: "alioss" },
|
||||
{ label: $t("certd.verifyPlan.uploader.tencentCos"), value: "tencentcos" },
|
||||
{ label: $t("certd.verifyPlan.uploader.qiniuOss"), value: "qiniuoss" },
|
||||
{ label: $t("certd.certdomain.verifyPlan.uploader.aliyunOss"), value: "alioss" },
|
||||
{ label: $t("certd.certdomain.verifyPlan.uploader.tencentCos"), value: "tencentcos" },
|
||||
{ label: $t("certd.certdomain.verifyPlan.uploader.qiniuOss"), value: "qiniuoss" },
|
||||
{ label: "S3/Minio", value: "s3" },
|
||||
{ label: $t("certd.verifyPlan.uploader.sshDeprecated"), value: "ssh", disabled: true },
|
||||
{ label: $t("certd.certdomain.verifyPlan.uploader.sshDeprecated"), value: "ssh", disabled: true },
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -30,8 +30,8 @@ function createUploaderTypeDict() {
|
||||
function createDomainFromTypeDict() {
|
||||
return dict({
|
||||
data: [
|
||||
{ value: "manual", label: $t("certd.verifyPlan.domainFrom.manual") },
|
||||
{ value: "auto", label: $t("certd.verifyPlan.domainFrom.auto") },
|
||||
{ value: "manual", label: $t("certd.certdomain.verifyPlan.domainFrom.manual") },
|
||||
{ value: "auto", label: $t("certd.certdomain.verifyPlan.domainFrom.auto") },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ const { t } = useI18n();
|
||||
<div class="tutorial-button pointer" @click="open">
|
||||
<template v-if="!slots.default">
|
||||
<fs-icon v-if="showIcon === false" icon="ant-design:question-circle-outlined" class="mr-0.5"></fs-icon>
|
||||
<div class="hidden md:block">{{ t("tutorial.title") }}</div>
|
||||
<div class="hidden md:block">{{ t("certd.tutorial.title") }}</div>
|
||||
</template>
|
||||
<slot></slot>
|
||||
<a-modal v-model:open="openedRef" class="tutorial-modal" width="90%">
|
||||
<template #title>{{ t("tutorial.title") }}</template>
|
||||
<template #title>{{ t("certd.tutorial.title") }}</template>
|
||||
<tutorial-steps v-if="openedRef" />
|
||||
<template #footer></template>
|
||||
</a-modal>
|
||||
|
||||
@@ -18,9 +18,9 @@ import { ref } from "vue";
|
||||
import { mitter } from "/@/utils/util.mitt";
|
||||
|
||||
const steps = ref<Step[]>([
|
||||
{ title: t("certd.steps.createPipeline"), icon: <fs-icon icon="tabler:circle-number-1-filled"></fs-icon> },
|
||||
{ title: t("certd.steps.addTask"), icon: <fs-icon icon="tabler:circle-number-2-filled"></fs-icon> },
|
||||
{ title: t("certd.steps.scheduledRun"), icon: <fs-icon icon="tabler:circle-number-3-filled"></fs-icon> },
|
||||
{ title: t("certd.tutorial.steps.createPipeline"), icon: <fs-icon icon="tabler:circle-number-1-filled"></fs-icon> },
|
||||
{ title: t("certd.tutorial.steps.addTask"), icon: <fs-icon icon="tabler:circle-number-2-filled"></fs-icon> },
|
||||
{ title: t("certd.tutorial.steps.scheduledRun"), icon: <fs-icon icon="tabler:circle-number-3-filled"></fs-icon> },
|
||||
]);
|
||||
|
||||
function goPipeline() {
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-center actions">
|
||||
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()">{{ t("guide.buttons.prev") }}</fs-button>
|
||||
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()">{{ t("guide.buttons.next") }}</fs-button>
|
||||
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()">{{ t("certd.guide.buttons.prev") }}</fs-button>
|
||||
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()">{{ t("certd.guide.buttons.next") }}</fs-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -52,126 +52,126 @@ import { computed, nextTick, ref } from "vue";
|
||||
|
||||
const steps = ref<Step[]>([
|
||||
{
|
||||
title: t("guide.createCertPipeline.title"),
|
||||
description: t("guide.createCertPipeline.description"),
|
||||
title: t("certd.guide.createCertPipeline.title"),
|
||||
description: t("certd.guide.createCertPipeline.description"),
|
||||
items: [
|
||||
{
|
||||
title: t("guide.createCertPipeline.items.tutorialTitle"),
|
||||
descriptions: [t("guide.createCertPipeline.items.tutorialDesc1"), t("guide.createCertPipeline.items.tutorialDesc2")],
|
||||
title: t("certd.guide.createCertPipeline.items.tutorialTitle"),
|
||||
descriptions: [t("certd.guide.createCertPipeline.items.tutorialDesc1"), t("certd.guide.createCertPipeline.items.tutorialDesc2")],
|
||||
body: () => {
|
||||
return <SimpleSteps></SimpleSteps>;
|
||||
},
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/1-add.png",
|
||||
title: t("guide.createCertPipeline.items.createTitle"),
|
||||
descriptions: [t("guide.createCertPipeline.items.createDesc")],
|
||||
title: t("certd.guide.createCertPipeline.items.createTitle"),
|
||||
descriptions: [t("certd.guide.createCertPipeline.items.createDesc")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/3-add-success.png",
|
||||
title: t("guide.createCertPipeline.items.successTitle"),
|
||||
descriptions: [t("guide.createCertPipeline.items.successDesc")],
|
||||
title: t("certd.guide.createCertPipeline.items.successTitle"),
|
||||
descriptions: [t("certd.guide.createCertPipeline.items.successDesc")],
|
||||
},
|
||||
// {
|
||||
// title: t("guide.createCertPipeline.items.nextTitle"),
|
||||
// descriptions: [t("guide.createCertPipeline.items.nextDesc")],
|
||||
// title: t("certd.guide.createCertPipeline.items.nextTitle"),
|
||||
// descriptions: [t("certd.guide.createCertPipeline.items.nextDesc")],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("guide.addDeployTask.title"),
|
||||
description: t("guide.addDeployTask.description"),
|
||||
title: t("certd.guide.addDeployTask.title"),
|
||||
description: t("certd.guide.addDeployTask.description"),
|
||||
items: [
|
||||
{
|
||||
image: "/static/doc/images/5-1-add-host.png",
|
||||
title: t("guide.addDeployTask.items.addTaskTitle"),
|
||||
descriptions: [t("guide.addDeployTask.items.addTaskDesc1"), t("guide.addDeployTask.items.addTaskDesc2")],
|
||||
title: t("certd.guide.addDeployTask.items.addTaskTitle"),
|
||||
descriptions: [t("certd.guide.addDeployTask.items.addTaskDesc1"), t("certd.guide.addDeployTask.items.addTaskDesc2")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-2-add-host.png",
|
||||
title: t("guide.addDeployTask.items.fillParamsTitle"),
|
||||
descriptions: [t("guide.addDeployTask.items.fillParamsDesc1"), t("guide.addDeployTask.items.fillParamsDesc2")],
|
||||
title: t("certd.guide.addDeployTask.items.fillParamsTitle"),
|
||||
descriptions: [t("certd.guide.addDeployTask.items.fillParamsDesc1"), t("certd.guide.addDeployTask.items.fillParamsDesc2")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-3-add-host.png",
|
||||
title: t("guide.addDeployTask.items.activateCertTitle"),
|
||||
descriptions: [t("guide.addDeployTask.items.activateCertDesc1"), t("guide.addDeployTask.items.activateCertDesc2")],
|
||||
title: t("certd.guide.addDeployTask.items.activateCertTitle"),
|
||||
descriptions: [t("certd.guide.addDeployTask.items.activateCertDesc1"), t("certd.guide.addDeployTask.items.activateCertDesc2")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-4-add-host.png",
|
||||
title: t("guide.addDeployTask.items.taskSuccessTitle"),
|
||||
descriptions: [t("guide.addDeployTask.items.taskSuccessDesc")],
|
||||
title: t("certd.guide.addDeployTask.items.taskSuccessTitle"),
|
||||
descriptions: [t("certd.guide.addDeployTask.items.taskSuccessDesc")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-5-plugin-list.png",
|
||||
title: t("guide.addDeployTask.items.pluginsTitle"),
|
||||
descriptions: [t("guide.addDeployTask.items.pluginsDesc")],
|
||||
title: t("certd.guide.addDeployTask.items.pluginsTitle"),
|
||||
descriptions: [t("certd.guide.addDeployTask.items.pluginsDesc")],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("guide.runAndTestTask.runAndTestTitle"),
|
||||
description: t("guide.runAndTestTask.runAndTestDescription"),
|
||||
title: t("certd.guide.runAndTestTask.runAndTestTitle"),
|
||||
description: t("certd.guide.runAndTestTask.runAndTestDescription"),
|
||||
items: [
|
||||
{
|
||||
image: "/static/doc/images/9-start.png",
|
||||
title: t("guide.runAndTestTask.runTestOnce"),
|
||||
descriptions: [t("guide.runAndTestTask.clickManualTriggerToTest")],
|
||||
title: t("certd.guide.runAndTestTask.runTestOnce"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.clickManualTriggerToTest")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/10-1-log.png",
|
||||
title: t("guide.runAndTestTask.viewLogs"),
|
||||
descriptions: [t("guide.runAndTestTask.clickTaskToViewStatusAndLogs")],
|
||||
title: t("certd.guide.runAndTestTask.viewLogs"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.clickTaskToViewStatusAndLogs")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/11-1-error.png",
|
||||
title: t("guide.runAndTestTask.howToTroubleshootFailure"),
|
||||
descriptions: [t("guide.runAndTestTask.viewErrorLogs")],
|
||||
title: t("certd.guide.runAndTestTask.howToTroubleshootFailure"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.viewErrorLogs")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/11-2-error.png",
|
||||
title: t("guide.runAndTestTask.howToTroubleshootFailure"),
|
||||
descriptions: [t("guide.runAndTestTask.viewErrorLogs"), t("guide.runAndTestTask.nginxContainerNotExistFix")],
|
||||
title: t("certd.guide.runAndTestTask.howToTroubleshootFailure"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.viewErrorLogs"), t("certd.guide.runAndTestTask.nginxContainerNotExistFix")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/12-1-log-success.png",
|
||||
title: t("guide.runAndTestTask.executionSuccess"),
|
||||
descriptions: [t("guide.runAndTestTask.retryAfterFix")],
|
||||
title: t("certd.guide.runAndTestTask.executionSuccess"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.retryAfterFix")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/12-2-skip-log.png",
|
||||
title: t("guide.runAndTestTask.autoSkipAfterSuccess"),
|
||||
descriptions: [t("guide.runAndTestTask.successSkipExplanation")],
|
||||
title: t("certd.guide.runAndTestTask.autoSkipAfterSuccess"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.successSkipExplanation")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/13-1-result.png",
|
||||
title: t("guide.runAndTestTask.viewCertDeploymentSuccess"),
|
||||
descriptions: [t("guide.runAndTestTask.visitNginxToSeeCert")],
|
||||
title: t("certd.guide.runAndTestTask.viewCertDeploymentSuccess"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.visitNginxToSeeCert")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/13-3-download.png",
|
||||
title: t("guide.runAndTestTask.downloadCertManualDeploy"),
|
||||
descriptions: [t("guide.runAndTestTask.downloadIfNoAutoDeployPlugin")],
|
||||
title: t("certd.guide.runAndTestTask.downloadCertManualDeploy"),
|
||||
descriptions: [t("certd.guide.runAndTestTask.downloadIfNoAutoDeployPlugin")],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("guide.scheduleAndEmailTask.title"),
|
||||
description: t("guide.scheduleAndEmailTask.description"),
|
||||
title: t("certd.guide.scheduleAndEmailTask.title"),
|
||||
description: t("certd.guide.scheduleAndEmailTask.description"),
|
||||
items: [
|
||||
{
|
||||
image: "/static/doc/images/14-timer.png",
|
||||
title: t("guide.scheduleAndEmailTask.setSchedule"),
|
||||
descriptions: [t("guide.scheduleAndEmailTask.pipelineSuccessThenSchedule"), t("guide.scheduleAndEmailTask.recommendDailyRun")],
|
||||
title: t("certd.guide.scheduleAndEmailTask.setSchedule"),
|
||||
descriptions: [t("certd.guide.scheduleAndEmailTask.pipelineSuccessThenSchedule"), t("certd.guide.scheduleAndEmailTask.recommendDailyRun")],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/15-1-email.png",
|
||||
title: t("guide.scheduleAndEmailTask.setEmailNotification"),
|
||||
descriptions: [t("guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails")],
|
||||
title: t("certd.guide.scheduleAndEmailTask.setEmailNotification"),
|
||||
descriptions: [t("certd.guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails")],
|
||||
},
|
||||
{
|
||||
title: t("guide.scheduleAndEmailTask.tutorialEndTitle"),
|
||||
descriptions: [t("guide.scheduleAndEmailTask.thanksForWatching")],
|
||||
title: t("certd.guide.scheduleAndEmailTask.tutorialEndTitle"),
|
||||
descriptions: [t("certd.guide.scheduleAndEmailTask.thanksForWatching")],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -48,56 +48,56 @@ const text = computed<Text>(() => {
|
||||
const map = {
|
||||
isComm: {
|
||||
comm: {
|
||||
name: t("vip.comm.name", { vipLabel }),
|
||||
title: t("vip.comm.title", { expire: expireTime.value }),
|
||||
name: t("certd.vip.comm.name", { vipLabel }),
|
||||
title: t("certd.vip.comm.title", { expire: expireTime.value }),
|
||||
},
|
||||
button: {
|
||||
name: t("vip.comm.name", { vipLabel }),
|
||||
title: t("vip.comm.title", { expire: expireTime.value }),
|
||||
name: t("certd.vip.comm.name", { vipLabel }),
|
||||
title: t("certd.vip.comm.title", { expire: expireTime.value }),
|
||||
},
|
||||
icon: {
|
||||
name: "",
|
||||
title: t("vip.comm.name", { vipLabel }),
|
||||
title: t("certd.vip.comm.name", { vipLabel }),
|
||||
},
|
||||
nav: {
|
||||
name: t("vip.comm.nav", { vipLabel }),
|
||||
title: t("vip.comm.title", { expire: expireTime.value }),
|
||||
name: t("certd.vip.comm.nav", { vipLabel }),
|
||||
title: t("certd.vip.comm.title", { expire: expireTime.value }),
|
||||
},
|
||||
},
|
||||
isPlus: {
|
||||
comm: {
|
||||
name: t("vip.plus.name"),
|
||||
title: t("vip.plus.title"),
|
||||
name: t("certd.vip.plus.name"),
|
||||
title: t("certd.vip.plus.title"),
|
||||
},
|
||||
button: {
|
||||
name: t("vip.comm.name", { vipLabel }),
|
||||
title: t("vip.comm.title", { expire: expireTime.value }),
|
||||
name: t("certd.vip.comm.name", { vipLabel }),
|
||||
title: t("certd.vip.comm.title", { expire: expireTime.value }),
|
||||
},
|
||||
icon: {
|
||||
name: "",
|
||||
title: t("vip.comm.name", { vipLabel }),
|
||||
title: t("certd.vip.comm.name", { vipLabel }),
|
||||
},
|
||||
nav: {
|
||||
name: t("vip.comm.nav", { vipLabel }),
|
||||
title: t("vip.comm.title", { expire: expireTime.value }),
|
||||
name: t("certd.vip.comm.nav", { vipLabel }),
|
||||
title: t("certd.vip.comm.title", { expire: expireTime.value }),
|
||||
},
|
||||
},
|
||||
free: {
|
||||
comm: {
|
||||
name: t("vip.free.comm.name"),
|
||||
title: t("vip.free.comm.title"),
|
||||
name: t("certd.vip.free.comm.name"),
|
||||
title: t("certd.vip.free.comm.title"),
|
||||
},
|
||||
button: {
|
||||
name: t("vip.free.button.name"),
|
||||
title: t("vip.free.button.title"),
|
||||
name: t("certd.vip.free.button.name"),
|
||||
title: t("certd.vip.free.button.title"),
|
||||
},
|
||||
icon: {
|
||||
name: "",
|
||||
title: t("vip.free.button.name"),
|
||||
title: t("certd.vip.free.button.name"),
|
||||
},
|
||||
nav: {
|
||||
name: t("vip.free.nav.name"),
|
||||
title: plusMessage || t("vip.free.nav.title"),
|
||||
name: t("certd.vip.free.nav.name"),
|
||||
title: plusMessage || t("certd.vip.free.nav.title"),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -131,7 +131,7 @@ const userStore = useUserStore();
|
||||
|
||||
async function getVipTrial(vipType = "plus") {
|
||||
const res = await api.getVipTrial(vipType);
|
||||
notification.success({ message: t("vip.congratulations_vip_trial", { duration: res.duration }) });
|
||||
notification.success({ message: t("certd.vip.congratulations_vip_trial", { duration: res.duration }) });
|
||||
await settingStore.init();
|
||||
}
|
||||
|
||||
@@ -139,8 +139,8 @@ function openTrialModal(vipType = "plus") {
|
||||
Modal.destroyAll();
|
||||
|
||||
modal.confirm({
|
||||
title: t("vip.trial_modal_title"),
|
||||
okText: t("vip.trial_modal_ok_text"),
|
||||
title: t("certd.vip.trial_modal_title"),
|
||||
okText: t("certd.vip.trial_modal_ok_text"),
|
||||
onOk() {
|
||||
getVipTrial(vipType);
|
||||
},
|
||||
@@ -148,8 +148,8 @@ function openTrialModal(vipType = "plus") {
|
||||
content: () => {
|
||||
return (
|
||||
<div class="flex-col mt-10 mb-10">
|
||||
<div>{t("vip.trial_modal_thanks")}</div>
|
||||
<div>{t("vip.trial_modal_click_confirm", { vipType })}</div>
|
||||
<div>{t("certd.vip.trial_modal_thanks")}</div>
|
||||
<div>{t("certd.vip.trial_modal_click_confirm", { vipType })}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -158,7 +158,7 @@ function openTrialModal(vipType = "plus") {
|
||||
|
||||
function openStarModal(vipType: string) {
|
||||
if (settingStore.isPlus) {
|
||||
notification.error({ message: t("vip.already_vip") });
|
||||
notification.error({ message: t("certd.vip.already_vip") });
|
||||
return;
|
||||
}
|
||||
Modal.destroyAll();
|
||||
@@ -170,8 +170,8 @@ function openStarModal(vipType: string) {
|
||||
// };
|
||||
|
||||
// modal.confirm({
|
||||
// title: t("vip.get_7_day_pro_trial"),
|
||||
// okText: t("vip.star_now"),
|
||||
// title: t("certd.vip.get_7_day_pro_trial"),
|
||||
// okText: t("certd.vip.star_now"),
|
||||
// onOk() {
|
||||
// goGithub();
|
||||
// openTrialModal(vipType);
|
||||
@@ -180,7 +180,7 @@ function openStarModal(vipType: string) {
|
||||
// content: () => {
|
||||
// return (
|
||||
// <div class="flex mt-10 mb-10">
|
||||
// <div>{t("vip.please_help_star")}</div>
|
||||
// <div>{t("certd.vip.please_help_star")}</div>
|
||||
// <img class="ml-5" src="https://img.shields.io/github/stars/certd/certd?logo=github" />
|
||||
// </div>
|
||||
// );
|
||||
@@ -190,16 +190,16 @@ function openStarModal(vipType: string) {
|
||||
|
||||
function openUpgrade() {
|
||||
if (!userStore.isAdmin) {
|
||||
notification.info({ message: t("vip.admin_only_operation") });
|
||||
notification.info({ message: t("certd.vip.admin_only_operation") });
|
||||
return;
|
||||
}
|
||||
const placeholder = t("vip.enter_activation_code");
|
||||
const placeholder = t("certd.vip.enter_activation_code");
|
||||
const isPlus = settingStore.isPlus;
|
||||
let title = t("vip.activate_pro_business");
|
||||
let title = t("certd.vip.activate_pro_business");
|
||||
if (settingStore.isComm) {
|
||||
title = t("vip.renew_business");
|
||||
title = t("certd.vip.renew_business");
|
||||
} else if (settingStore.isPlus) {
|
||||
title = t("vip.renew_pro_upgrade_business");
|
||||
title = t("certd.vip.renew_pro_upgrade_business");
|
||||
}
|
||||
|
||||
// const goBuyUrl = "https://afdian.com/a/greper"
|
||||
@@ -214,10 +214,10 @@ function openUpgrade() {
|
||||
function checkPerpetualPlus() {
|
||||
if (settingStore.isPerpetual) {
|
||||
Modal.warn({
|
||||
title: t("vip.already_perpetual_plus"),
|
||||
okText: t("vip.confirm"),
|
||||
title: t("certd.vip.already_perpetual_plus"),
|
||||
okText: t("certd.vip.confirm"),
|
||||
});
|
||||
throw new Error(t("vip.already_perpetual_plus"));
|
||||
throw new Error(t("certd.vip.already_perpetual_plus"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,8 +225,8 @@ function openUpgrade() {
|
||||
checkPerpetualPlus();
|
||||
if (settingStore.isComm) {
|
||||
Modal.warn({
|
||||
title: t("vip.already_comm"),
|
||||
okText: t("vip.confirm"),
|
||||
title: t("certd.vip.already_comm"),
|
||||
okText: t("certd.vip.confirm"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -237,8 +237,8 @@ function openUpgrade() {
|
||||
checkPerpetualPlus();
|
||||
if (settingStore.isPlus && !settingStore.isComm) {
|
||||
Modal.confirm({
|
||||
title: t("vip.already_plus"),
|
||||
okText: t("vip.confirm"),
|
||||
title: t("certd.vip.already_plus"),
|
||||
okText: t("certd.vip.confirm"),
|
||||
onOk() {
|
||||
window.open(goBuyCommUrl);
|
||||
},
|
||||
@@ -251,7 +251,7 @@ function openUpgrade() {
|
||||
title,
|
||||
class: "vip-modal",
|
||||
maskClosable: true,
|
||||
okText: t("vip.close"),
|
||||
okText: t("certd.vip.close"),
|
||||
width: 1180,
|
||||
content: () => {
|
||||
return <VipModalContent placeholder={placeholder} isPlus={isPlus} productInfo={productInfo} goBuyPlusPage={goBuyPlusPage} goBuyCommPage={goBuyCommPage} openStarModal={openStarModal} modalRef={modalRef} />;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span class="price-text">{{ t("vip.freee") }}</span>
|
||||
<span class="price-text">{{ t("certd.vip.freee") }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="get-show">
|
||||
@@ -44,15 +44,15 @@
|
||||
<span class="color-red"> {{ todayOrderCount.vipTotal }}</span>
|
||||
位伙伴支持
|
||||
</span>
|
||||
<a-tooltip :title="t('vip.afdian_support_vip')">
|
||||
<a-tooltip :title="t('certd.vip.afdian_support_vip')">
|
||||
<a-button size="small" type="primary" @click="goBuyPlusPage">
|
||||
{{ t("vip.get_after_support") }}
|
||||
{{ t("certd.vip.get_after_support") }}
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'comm'">
|
||||
<a-button size="small" type="primary" @click="goBuyCommPage">
|
||||
{{ t("vip.buy") }}
|
||||
{{ t("certd.vip.buy") }}
|
||||
</a-button>
|
||||
</template>
|
||||
</div>
|
||||
@@ -63,36 +63,36 @@
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://certd.docmirror.cn/guide/donate/#相关问题" target="_blank">
|
||||
{{ t("vip.question") }}
|
||||
{{ t("certd.vip.question") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
<div class="w-100 flex-col md:flex-row">
|
||||
<span>{{ t("vip.site_id") }}:</span>
|
||||
<span>{{ t("certd.vip.site_id") }}:</span>
|
||||
<fs-copyable v-model="computedSiteId" class="mr-2 inline-flex"></fs-copyable>
|
||||
<a @click="goBindAccount">{{ t("vip.not_effective") }}</a>
|
||||
<a @click="goBindAccount">{{ t("certd.vip.not_effective") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isPlus" class="mt-10 flex flex-col md:flex-row">
|
||||
<span class="mr-2"> {{ t("vip.current") }} {{ vipLabel }} {{ t("vip.activated_expire_time") }} {{ settingStore.expiresText }} </span>
|
||||
<span class="mr-2"> {{ t("certd.vip.current") }} {{ vipLabel }} {{ t("certd.vip.activated_expire_time") }} {{ settingStore.expiresText }} </span>
|
||||
<a href="https://app.handfree.work/subject/#/page/detail/1" target="_blank">
|
||||
{{ t("vip.learn_more") }}
|
||||
{{ t("certd.vip.learn_more") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
<span class="mr-2">{{ t("vip.have_activation_code") }}</span>
|
||||
<span class="mr-2">{{ t("certd.vip.have_activation_code") }}</span>
|
||||
<span>
|
||||
<a @click="showManualActivation">{{ t("vip.manual_activation") }}</a>
|
||||
<a @click="showManualActivation">{{ t("certd.vip.manual_activation") }}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="manualActiveFlag" class="mt-10">
|
||||
<div class="mt-10">
|
||||
<a-input-search v-model:value="formState.code" class="w-2/6" :placeholder="placeholder" :enter-button="t('vip.activate')" @search="doActive"></a-input-search>
|
||||
<a-input-search v-model:value="formState.code" class="w-2/6" :placeholder="placeholder" :enter-button="t('certd.vip.activate')" @search="doActive"></a-input-search>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
{{ t("vip.activation_code_one_use") }}
|
||||
<a @click="goAccount">{{ t("vip.bind_account") }}</a
|
||||
>,{{ t("vip.transfer_vip") }}
|
||||
{{ t("certd.vip.activation_code_one_use") }}
|
||||
<a @click="goAccount">{{ t("certd.vip.bind_account") }}</a
|
||||
>,{{ t("certd.vip.transfer_vip") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,24 +128,24 @@ const formState = reactive({
|
||||
|
||||
async function doActive() {
|
||||
if (!formState.code) {
|
||||
notification.error({ message: t("vip.enterCode") });
|
||||
throw new Error(t("vip.enterCode"));
|
||||
notification.error({ message: t("certd.vip.enterCode") });
|
||||
throw new Error(t("certd.vip.enterCode"));
|
||||
}
|
||||
const res = await api.doActive(formState);
|
||||
if (res) {
|
||||
await settingStore.init();
|
||||
const vipLabel = settingStore.vipLabel;
|
||||
Modal.success({
|
||||
title: t("vip.successTitle"),
|
||||
content: t("vip.successContent", {
|
||||
title: t("certd.vip.successTitle"),
|
||||
content: t("certd.vip.successContent", {
|
||||
vipLabel,
|
||||
expireDate: settingStore.plusInfo.expireTime === -1 ? t("vip.permanent") : dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
|
||||
expireDate: settingStore.plusInfo.expireTime === -1 ? t("certd.vip.permanent") : dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
|
||||
}),
|
||||
onOk() {
|
||||
if (!(settingStore.installInfo.bindUserId > 0)) {
|
||||
Modal.confirm({
|
||||
title: t("vip.bindAccountTitle"),
|
||||
content: t("vip.bindAccountContent"),
|
||||
title: t("certd.vip.bindAccountTitle"),
|
||||
content: t("certd.vip.bindAccountContent"),
|
||||
onOk() {
|
||||
router.push("/sys/account");
|
||||
},
|
||||
@@ -179,39 +179,39 @@ function goBindAccount() {
|
||||
|
||||
const vipTypeDefine: any = {
|
||||
free: {
|
||||
title: t("vip.basic_edition"),
|
||||
desc: t("vip.community_free_version"),
|
||||
title: t("certd.vip.basic_edition"),
|
||||
desc: t("certd.vip.community_free_version"),
|
||||
type: "free",
|
||||
icon: "lucide:package-open",
|
||||
privilege: t("vip.free_privilege").split("\n"),
|
||||
privilege: t("certd.vip.free_privilege").split("\n"),
|
||||
},
|
||||
plus: {
|
||||
title: t("vip.professional_edition"),
|
||||
desc: t("vip.open_source_support"),
|
||||
title: t("certd.vip.professional_edition"),
|
||||
desc: t("certd.vip.open_source_support"),
|
||||
type: "plus",
|
||||
privilege: t("vip.plus_privilege").split("\n"),
|
||||
privilege: t("certd.vip.plus_privilege").split("\n"),
|
||||
trial: {
|
||||
title: t("vip.click_to_get_7_day_trial"),
|
||||
title: t("certd.vip.click_to_get_7_day_trial"),
|
||||
click: () => {
|
||||
props.openStarModal("plus");
|
||||
},
|
||||
},
|
||||
icon: "stash:thumb-up",
|
||||
priceText: props.productInfo.plus.priceText || `¥${props.productInfo.plus.price}/${t("vip.years")}`,
|
||||
discountText: props.productInfo.plus.discountText || `¥${props.productInfo.plus.price3}/3${t("vip.years")}`,
|
||||
priceText: props.productInfo.plus.priceText || `¥${props.productInfo.plus.price}/${t("certd.vip.years")}`,
|
||||
discountText: props.productInfo.plus.discountText || `¥${props.productInfo.plus.price3}/3${t("certd.vip.years")}`,
|
||||
tooltip: props.productInfo.plus.tooltip,
|
||||
},
|
||||
comm: {
|
||||
title: t("vip.business_edition"),
|
||||
desc: t("vip.commercial_license"),
|
||||
title: t("certd.vip.business_edition"),
|
||||
desc: t("certd.vip.commercial_license"),
|
||||
type: "comm",
|
||||
icon: "vaadin:handshake",
|
||||
privilege: t("vip.comm_privilege").split("\n"),
|
||||
priceText: props.productInfo.comm.priceText || `¥${props.productInfo.comm.price}/${t("vip.years")}`,
|
||||
discountText: props.productInfo.comm.discountText || `¥${props.productInfo.comm.price3}/3${t("vip.years")}`,
|
||||
privilege: t("certd.vip.comm_privilege").split("\n"),
|
||||
priceText: props.productInfo.comm.priceText || `¥${props.productInfo.comm.price}/${t("certd.vip.years")}`,
|
||||
discountText: props.productInfo.comm.discountText || `¥${props.productInfo.comm.price3}/3${t("certd.vip.years")}`,
|
||||
tooltip: props.productInfo.comm.tooltip,
|
||||
trial: {
|
||||
title: t("vip.click_to_get_7_day_trial"),
|
||||
title: t("certd.vip.click_to_get_7_day_trial"),
|
||||
click: () => {
|
||||
props.openStarModal("comm");
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
<script lang="ts" setup>
|
||||
import { BasicLayout, LockScreen, UserDropdown } from "/@/vben/layouts";
|
||||
|
||||
import { computed, onErrorCaptured, onMounted, provide, ref } from "vue";
|
||||
@@ -23,14 +23,14 @@ const menus = computed(() => [
|
||||
router.push("/cert/mine/user-profile");
|
||||
},
|
||||
icon: "fa-solid:book",
|
||||
text: t("certd.accountInfo"),
|
||||
text: t("certd.mine.accountInfo"),
|
||||
},
|
||||
{
|
||||
handler: () => {
|
||||
router.push("/cert/mine/security");
|
||||
},
|
||||
icon: "fluent:shield-keyhole-16-regular",
|
||||
text: t("certd.securitySettings"),
|
||||
text: t("certd.mine.securitySettings"),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -109,7 +109,7 @@ const projectStore = useProjectStore();
|
||||
<vip-button class="flex-center header-btn" mode="nav" />
|
||||
</div>
|
||||
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
|
||||
<fs-button shape="circle" type="text" icon="clarity:host-solid-badged" :text="null" :tooltip="{ title: t('certd.client') }" @click="goCertdClient" />
|
||||
<fs-button shape="circle" type="text" icon="clarity:host-solid-badged" :text="null" :tooltip="{ title: t('certd.navigation.client') }" @click="goCertdClient" />
|
||||
</div>
|
||||
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
|
||||
<fs-button shape="circle" type="text" icon="ion:logo-github" :text="null" @click="goGithub" />
|
||||
|
||||
@@ -13,34 +13,54 @@ import notification from "./certd/notification";
|
||||
import addon from "./certd/addon";
|
||||
import suite from "./certd/suite";
|
||||
import project from "./certd/project";
|
||||
import syssettings from "./certd/sys-settings";
|
||||
import sysplugin from "./certd/sys-plugin";
|
||||
import sysauthority from "./certd/sys-authority";
|
||||
import syscname from "./certd/sys-cname";
|
||||
import settings from "./certd/sys-settings";
|
||||
import plugin from "./certd/sys-plugin";
|
||||
import authority from "./certd/sys-authority";
|
||||
import cnameSettings from "./certd/sys-cname";
|
||||
import tutorial from "./certd/tutorial";
|
||||
import cron from "./certd/cron";
|
||||
import client from "./certd/client";
|
||||
import wallet from "./certd/wallet";
|
||||
import nettest from "./certd/nettest";
|
||||
import audit from "./certd/audit";
|
||||
import applyTemplate from "./certd/apply-template";
|
||||
import dnsPersist from "./certd/dns-persist";
|
||||
import auth from "./certd/auth";
|
||||
import guide from "./certd/guide";
|
||||
import vip from "./certd/vip";
|
||||
|
||||
// Note: @ is reserved in locale messages; use {'@'} when needed.
|
||||
export default {
|
||||
...common,
|
||||
...navigation,
|
||||
...dashboard,
|
||||
...pipeline,
|
||||
...history,
|
||||
...monitor,
|
||||
...certdomain,
|
||||
...cname,
|
||||
...access,
|
||||
...open,
|
||||
...mine,
|
||||
...notification,
|
||||
...addon,
|
||||
...suite,
|
||||
...project,
|
||||
...syssettings,
|
||||
...sysplugin,
|
||||
...sysauthority,
|
||||
...syscname,
|
||||
...tutorial,
|
||||
...cron,
|
||||
common,
|
||||
navigation,
|
||||
dashboard,
|
||||
pipeline,
|
||||
history,
|
||||
monitor,
|
||||
certdomain,
|
||||
cname,
|
||||
access,
|
||||
open,
|
||||
mine,
|
||||
notification,
|
||||
addon,
|
||||
suite,
|
||||
project,
|
||||
sys: {
|
||||
settings,
|
||||
plugin,
|
||||
authority,
|
||||
cname: cnameSettings,
|
||||
},
|
||||
tutorial,
|
||||
cron,
|
||||
client,
|
||||
wallet,
|
||||
nettest,
|
||||
audit,
|
||||
applyTemplate,
|
||||
dnsPersist,
|
||||
auth,
|
||||
guide,
|
||||
vip,
|
||||
};
|
||||
|
||||
@@ -4,4 +4,11 @@ export default {
|
||||
nameHelper: "Fill in as you like, useful to distinguish when multiple authorizations of the same type exist",
|
||||
level: "Level",
|
||||
system: "System",
|
||||
select: "Select",
|
||||
selectProvider: "Select Authorization Provider",
|
||||
deleteConfirm: "If this authorization is already in use, pipelines may fail. Proceed with caution.",
|
||||
name: "Name",
|
||||
user: "User",
|
||||
type: "Type",
|
||||
selectType: "Please select a type",
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
setSuccess: "Settings saved",
|
||||
editTitle: "Edit Certificate Application Template",
|
||||
addTitle: "Add Certificate Application Template",
|
||||
name: "Template Name",
|
||||
default: "Default",
|
||||
setDefault: "Set as Default",
|
||||
disabled: "Disabled",
|
||||
createTime: "Created At",
|
||||
config: "Configuration",
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user