Compare commits

...

63 Commits

Author SHA1 Message Date
xiaojunnuo
0b152a3cb8 v1.34.3 2025-05-16 00:12:33 +08:00
xiaojunnuo
1a0e096ddb build: prepare to build 2025-05-16 00:08:05 +08:00
xiaojunnuo
bf040d4c42 perf: 添加 FlexCDN 更新证书插件
- 新增 FlexCDNRefreshCert 插件类,实现更新证书功能
- 添加 FlexCDNAccess 授权类和 FlexCDNClient 客户端类
- 实现获取证书列表和更新证书的 API 调用
- 提供插件配置界面和执行逻辑
2025-05-16 00:04:52 +08:00
xiaojunnuo
3e2101aa5b perf: 小助手可以关闭 2025-05-15 23:06:22 +08:00
xiaojunnuo
44f11b38e7 docs: 2025-05-15 22:54:13 +08:00
xiaojunnuo
06f8514bc1 docs(guide): 更新常见问题解答
- 新增 ping 域名的故障排查步骤
- 添加查看容器日志的方法
- 补充 IPv6网络配置的说明
2025-05-15 21:54:20 +08:00
xiaojunnuo
d9a9f1c25c docs: 2025-05-15 21:10:23 +08:00
xiaojunnuo
e77f7244ba chore: 2025-05-15 18:08:50 +08:00
xiaojunnuo
09779cd1e1 chore: 2025-05-15 14:35:45 +08:00
xiaojunnuo
11024168db chore: 2025-05-15 14:08:30 +08:00
xiaojunnuo
304914513e chore: 2025-05-15 14:08:14 +08:00
xiaojunnuo
03d0efcfc6 chore: 2025-05-15 13:16:29 +08:00
xiaojunnuo
0c2bdc9146 chore: 2025-05-15 13:05:07 +08:00
xiaojunnuo
188450b0c0 chore: 2025-05-15 13:03:37 +08:00
xiaojunnuo
ddf6bbfa46 docs: 2025-05-15 12:24:14 +08:00
xiaojunnuo
2c7c98a152 docs: 2025-05-15 12:20:20 +08:00
xiaojunnuo
d31ac75718 docs: 2025-05-15 11:18:31 +08:00
xiaojunnuo
4b28c659de docs: 2025-05-15 09:38:35 +08:00
xiaojunnuo
00b937e52a Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-05-15 09:22:00 +08:00
xiaojunnuo
68f333fb87 perf: 支持部署到maoyun cdn 2025-05-15 01:03:21 +08:00
xiaojunnuo
085b4d9319 docs: 2025-05-14 16:02:49 +08:00
xiaojunnuo
b8edd14f39 refactor(ui): 优化 AI聊天功能
- 在打开聊天时增加对空消息的检查,避免发送无效请求
- 在发送消息时添加来源标识,以便服务端区分消息来源
- 在任务视图中增加对空日志的检查,避免触发空指针异常
2025-05-14 15:46:41 +08:00
xiaojunnuo
61a19d694b docs: ip证书说明 2025-05-14 15:06:58 +08:00
xiaojunnuo
aa96859798 perf: 支持AI分析报错 2025-05-14 15:03:47 +08:00
xiaojunnuo
abf015f485 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-05-14 08:57:15 +08:00
xiaojunnuo
0b9a02afde docs: docs 2025-05-14 08:56:48 +08:00
xiaojunnuo
e332ce28f8 chore: baotawaf access 2025-05-14 01:06:30 +08:00
xiaojunnuo
08e779f9f1 docs: ipv6 2025-05-13 23:06:54 +08:00
xiaojunnuo
a53b6cd28f perf: 宝塔插件、1panel 改成完全免费版 2025-05-13 21:15:59 +08:00
xiaojunnuo
5a5af60f97 build: publish 2025-05-11 20:29:09 +08:00
xiaojunnuo
50cc17c7cb build: trigger build image 2025-05-11 20:28:45 +08:00
xiaojunnuo
a1e504c138 v1.34.2 2025-05-11 20:27:04 +08:00
xiaojunnuo
4cc413047c build: prepare to build 2025-05-11 20:23:52 +08:00
xiaojunnuo
2397097e4d fix: 修复部署到又拍云强制https无效的bug 2025-05-11 12:29:04 +08:00
xiaojunnuo
c88f959ec9 chore: 2025-05-11 10:55:45 +08:00
xiaojunnuo
0b2e28b62d fix: 修复刷新流水线页面后,日志不自动更新的bug 2025-05-11 10:55:01 +08:00
xiaojunnuo
c7f2ead696 chore: doc 2025-05-11 10:22:10 +08:00
xiaojunnuo
b454e02d01 chore: doc 2025-05-11 10:04:54 +08:00
xiaojunnuo
47df2ffc3e chore: doc 2025-05-10 22:05:21 +08:00
xiaojunnuo
d18e431e2f perf: 支持设置网安备案号 2025-05-10 21:31:32 +08:00
xiaojunnuo
0a147d2db7 chore: 2025-05-10 20:52:23 +08:00
xiaojunnuo
ccdc933064 chore: 2025-05-10 17:29:10 +08:00
xiaojunnuo
023f2d4569 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-05-10 15:05:28 +08:00
xiaojunnuo
06a7371d2b chore: 2025-05-10 15:04:57 +08:00
xiaojunnuo
626f5d3487 chore: 2025-05-10 13:58:08 +08:00
xiaojunnuo
8cd3b9fe2e Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-05-09 18:51:39 +08:00
xiaojunnuo
716c35d52a chore: doc 2025-05-09 18:51:08 +08:00
xiaojunnuo
8cc0f3918b chore: 文档增加插件列表 2025-05-08 23:47:50 +08:00
xiaojunnuo
98b51f0799 chore: 文档增加插件列表 2025-05-08 23:27:46 +08:00
xiaojunnuo
81d6dad548 chore: 2025-05-08 10:29:42 +08:00
xiaojunnuo
41bc11cf96 chore: 2025-05-08 10:29:25 +08:00
xiaojunnuo
721dbe415a chore: 2025-05-08 10:28:44 +08:00
xiaojunnuo
f5c0b51428 chore: 2025-05-08 10:27:49 +08:00
xiaojunnuo
892c6ad80c chore: 2025-05-08 10:23:47 +08:00
xiaojunnuo
a47805e494 chore: 2025-05-08 10:22:31 +08:00
xiaojunnuo
9dd49054d1 perf: 集成智能问答机器人 2025-05-07 14:15:32 +08:00
xiaojunnuo
f5d1d1a0b7 chore: 1 2025-05-06 17:52:39 +08:00
xiaojunnuo
d75fcb7fec perf: http方式支持校验443端口 2025-05-06 17:01:20 +08:00
xiaojunnuo
826be45b6a chore: 等待解析生效时长可自定义 2025-05-06 11:04:02 +08:00
xiaojunnuo
d35d9c17c5 chore: doc 2025-05-06 10:57:07 +08:00
xiaojunnuo
638c9720cf chore: 1.34.1 2025-05-06 00:32:38 +08:00
xiaojunnuo
08a190882f build: trigger build image 2025-05-06 00:32:25 +08:00
xiaojunnuo
bfa7530a39 chore: 1.34.1 2025-05-06 00:32:11 +08:00
118 changed files with 5332 additions and 4880 deletions

1
.gitignore vendored
View File

@@ -17,6 +17,7 @@ gen
/test/*.private.*
/*.log
nohup.out
/packages/ui/*/.idea
/packages/ui/*/node_modules

View File

@@ -3,6 +3,29 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
### Performance Improvements
* 宝塔插件、1panel 改成完全免费版 ([a53b6cd](https://github.com/certd/certd/commit/a53b6cd28ff2ce5662ada82379ea44a06b179b81))
* 添加 FlexCDN 更新证书插件 ([bf040d4](https://github.com/certd/certd/commit/bf040d4c428d29c06fbaca5e29100e0c583b2b0b))
* 小助手可以关闭 ([3e2101a](https://github.com/certd/certd/commit/3e2101aa5b56548614102e900d59819ce8c7e97c))
* 支持部署到maoyun cdn ([68f333f](https://github.com/certd/certd/commit/68f333fb87ce85eed27436ecb0f76351c0ccb0d1))
* 支持AI分析报错 ([aa96859](https://github.com/certd/certd/commit/aa96859798166426e485947a6590464de189de05))
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
### Bug Fixes
* 修复部署到又拍云强制https无效的bug ([2397097](https://github.com/certd/certd/commit/2397097e4ddcb6f593210598e8779ffd44ac3f8f))
* 修复刷新流水线页面后日志不自动更新的bug ([0b2e28b](https://github.com/certd/certd/commit/0b2e28b62dd5eb6804c602083e65c87a9d1d72d2))
### Performance Improvements
* 集成智能问答机器人 ([9dd4905](https://github.com/certd/certd/commit/9dd49054d18ec436a5029444ca55a38adc682933))
* 支持设置网安备案号 ([d18e431](https://github.com/certd/certd/commit/d18e431e2f08e6b37704032c4ea6fbdd8e971442))
* http方式支持校验443端口 ([d75fcb7](https://github.com/certd/certd/commit/d75fcb7fec421a9a638eaa27fe9378c84b5e0f19))
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
### Bug Fixes

View File

@@ -5,28 +5,35 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
> 关于证书续期:
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
>* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
> 流水线数量现已调整为无限制,欢迎大家使用
## 一、特性
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
* 全自动申请证书(支持所有注册商注册的域名)
* 全自动申请证书(支持所有注册商注册的域名支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
* 全自动部署更新证书目前支持部署到主机、阿里云、腾讯云等70+部署插件)
* 支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
* 支持通配符域名/泛域名支持多个域名打到一个证书上支持pem、pfx、der、jks等多种证书格式
* 邮件通知、webhook通知
* 私有化部署,数据保存本地,授权信息加密存储镜像由Github Actions构建过程公开透明
* 支持SQLitePostgreSQL、MySQL数据库
* 邮件通知、webhook通知、企微、钉钉、飞书、anpush等多种通知方式
* 私有化部署,数据保存本地,安装升级非常简单快捷
* 镜像由Github Actions构建过程公开透明
* 授权加密站点隐藏2FA密码防爆破等多重安全保障
* 支持SQLitePostgreSQL、MySQL多种数据库
* 开放接口支持
* 站点证书监控
* 多用户管理
![](./docs/images/intro/intro.svg)
>
> 流水线数量现已调整为无限制,欢迎大家使用
>
> 关于证书续期:
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
>* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
## 二、在线体验
@@ -143,14 +150,14 @@ https://afdian.com/a/greper
专业版特权对比
| 功能 | 免费版 | 专业版 |
|---------|--------------------|-----------------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 域名数量 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 |
| 站点证书监控 | 限制1条 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署等 | 支持群晖、宝塔、1Panel等,持续开发中 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、飞书、anpush、server酱等 |
| 功能 | 免费版 | 专业版 |
|---------|---------------------------------------|--------------------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 域名数量 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 |
| 站点证书监控 | 限制1条 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
************************

View File

@@ -1 +1 @@
17:28
20:28

View File

@@ -3,6 +3,8 @@ services:
certd:
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号,例如certd:1.29.0
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
# image: ghcr.io/certd/certd:latest # --------- 如果 报镜像not found可以尝试其他镜像源
# image: greper/certd:latest
container_name: certd # 容器名
restart: unless-stopped # 自动重启
volumes:
@@ -35,6 +37,8 @@ services:
# networks:
# - ip6net
environment:
# ↓↓↓↓ ----------------------------------------------------- 使用上海东八时区
# - TZ=Asia/Shanghai
# 设置环境变量即可自定义certd配置
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
# 配置规则: certd_ + 配置项, 点号用_代替

View File

@@ -88,22 +88,26 @@ export default defineConfig({
text: "特性",
items: [
{text: "CNAME代理校验", link: "/guide/feature/cname/index.md"},
{text: "插件列表", link: "/guide/plugins.md"},
{text: "多数据库支持", link: "/guide/install/database.md"},
{text: "开放接口", link: "/guide/open/index.md"},
{
text: "站点安全", items: [
{text: "安全特性", link: "/guide/feature/safe"},
{text: "站点隐藏", link: "/guide/feature/safe/hidden"},
{text: "安全生产建议", link: "/guide/feature/safe/suggest"},
text: "站点安全", link: "/guide/feature/safe"
},
{
text: "插件列表", items: [
{text: "授权提供商", link: "/guide/plugins/access"},
{text: "DNS提供商", link: "/guide/plugins/dns-provider"},
{text: "任务插件", link: "/guide/plugins/deploy"},
{text: "通知插件", link: "/guide/plugins/notification"},
]
},
]
},
{
text: "常见问题",
items: [
{text: "QA", link: "/guide/qa/use"},
{text: "常见报错处理", link: "/guide/qa/"},
{text: "群晖证书部署", link: "/guide/use/synology/"},
{text: "腾讯云密钥获取", link: "/guide/use/tencent/"},
{text: "连接windows主机", link: "/guide/use/host/windows.md"},
@@ -115,8 +119,14 @@ export default defineConfig({
{text: "js脚本插件使用", link: "/guide/use/custom-script/index.md"},
{text: "邮箱配置", link: "/guide/use/email/index.md"},
{text: "IPv6支持", link: "/guide/use/setting/ipv6.md"},
{text: "其他插件使用", link: "/deploy/"},
{text: "商业版说明", link: "/comm/"},
{text: "ESXi", link: "/guide/use/ESXi/index.md"},
]
},
{
text: "商业版配置", link: "/guide/use/comm/", items: [
{text: "支付宝配置", link: "/guide/use/comm/payments/alipay.md"},
{text: "微信支付配置", link: "/guide/use/comm/payments/wxpay.md"},
{text: "彩虹易支付配置", link: "/guide/use/comm/payments/yizhifu.md"},
]
},
{
@@ -133,26 +143,6 @@ export default defineConfig({
]
}
],
"/deploy/": [
{
text: "部署证书插件",
items: [
{text: "插件说明", link: "/deploy/index.md"},
{text: "部署到ESXi", link: "/deploy/ESXi/index.md"},
]
}
],
"/comm/": [
{
text: "商业版",
items: [
{text: "支付宝配置", link: "/comm/payments/alipay.md"},
{text: "微信支付配置", link: "/comm/payments/wxpay.md"},
{text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md"},
]
}
]
,
},
socialLinks: [

View File

@@ -1,4 +0,0 @@
# 部署插件说明
## 待完善

View File

@@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
### Bug Fixes
* 修复部署到又拍云强制https无效的bug ([2397097](https://github.com/certd/certd/commit/2397097e4ddcb6f593210598e8779ffd44ac3f8f))
* 修复刷新流水线页面后日志不自动更新的bug ([0b2e28b](https://github.com/certd/certd/commit/0b2e28b62dd5eb6804c602083e65c87a9d1d72d2))
### Performance Improvements
* 集成智能问答机器人 ([9dd4905](https://github.com/certd/certd/commit/9dd49054d18ec436a5029444ca55a38adc682933))
* 支持设置网安备案号 ([d18e431](https://github.com/certd/certd/commit/d18e431e2f08e6b37704032c4ea6fbdd8e971442))
* http方式支持校验443端口 ([d75fcb7](https://github.com/certd/certd/commit/d75fcb7fec421a9a638eaa27fe9378c84b5e0f19))
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
### Bug Fixes
* 根据SOA记录判断子域名托管有缺陷改回手动配置子域名托管记录的方式 ([1b280a2](https://github.com/certd/certd/commit/1b280a2940f9e2d919b0bf23b89cc185be1fa498))
* 修复宝塔授权测试按钮显示错误的bug ([048696e](https://github.com/certd/certd/commit/048696ee9386491bb68592fb3a47d1c900bb68bf))
### Performance Improvements
* 支持部署证书到火山dcdn ([5f85219](https://github.com/certd/certd/commit/5f852194953dc1b4e6336770f417507b8f5a33ad))
* 支持部署证书到unicloud ([a63d687](https://github.com/certd/certd/commit/a63d687f1c573159f0857693f37602b0e1e44072))
# [1.34.0](https://github.com/certd/certd/compare/v1.33.8...v1.34.0) (2025-04-28)
### Bug Fixes

View File

@@ -0,0 +1,88 @@
# 授权插件Demo
```ts
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
import { isDev } from '../../utils/env.js';
/**
* 这个注解将注册一个授权配置
* 在certd的后台管理系统中用户可以选择添加此类型的授权
*/
@IsAccess({
name: 'demo',
title: '授权插件示例',
icon: 'clarity:plugin-line',
desc: '',
})
export class DemoAccess extends BaseAccess {
/**
* 授权属性配置
*/
@AccessInput({
title: '密钥Id',
component: {
placeholder: 'demoKeyId',
},
required: true,
})
demoKeyId = '';
/**
* 授权属性配置
*/
@AccessInput({
//标题
title: '密钥串',
component: {
//input组件的placeholder
placeholder: 'demoKeySecret',
},
//是否必填
required: true,
//改属性是否需要加密
encrypt: true,
})
//属性名称
demoKeySecret = '';
}
new DemoAccess();
```
# 阿里云授权
```ts
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
@IsAccess({
name: "aliyun",
title: "阿里云授权",
desc: "",
icon: "ant-design:aliyun-outlined",
order: 0,
})
export class AliyunAccess extends BaseAccess {
@AccessInput({
title: "accessKeyId",
component: {
placeholder: "accessKeyId",
},
helper: "登录阿里云控制台->AccessKey管理页面获取。",
required: true,
})
accessKeyId = "";
@AccessInput({
title: "accessKeySecret",
component: {
placeholder: "accessKeySecret",
},
required: true,
encrypt: true,
helper: "注意证书申请需要dns解析权限其他阿里云插件需要对应的权限比如证书上传需要证书管理权限嫌麻烦就用主账号的全量权限的accessKey",
})
accessKeySecret = "";
}
new AliyunAccess();
```

View File

@@ -22,4 +22,6 @@
![](./images/hidden2.png)
## 3、忘记解除地址和解除密码怎么办
登录服务器,在数据库平级的目录下创建`.unhidden`文件即可`临时解除`站点隐藏
登录服务器,在数据库平级的目录下创建`.unhidden`命名的空白文件即可临时解除站点隐藏
临时解除后会自动删除`.unhidden`文件,请尽快设置好新的`解除地址``解除密码`,并记住

View File

@@ -1,36 +1,49 @@
# 站点安全特性
# 安全特性
Certd 存储了证书以及授权等敏感数据,所以需要严格保障安全。
我们非常重视您的数据安全,提供了以下安全特性
Certd 存储了证书以及授权等敏感数据,所以需要严格保障安全。
我们提供了以下安全特性,以及安全生产建议(请遵照建议进行生产部署以保障数据安全)
## 1、 授权数据加密存储【默认开启】
## 一、站点安全特性
### 1、 授权数据加密存储【默认开启】
* 所有的授权敏感字段会加密后存储
* 每个用户独立维护授权数据,连管理员都无权查看
![星号部分为加密数据](./images/access.png)
星号部分为加密数据
## 2、 密码防爆破【默认开启】
### 2、 密码防爆破【默认开启】
* 登录失败次数过多账号将被锁定最高24小时(重启服务可解除锁定)
* 用户登录密码加密hash后存储无法计算出密码明文
![](./images/login.png)
## 3、站点隐藏【建议开启】
### 3、站点隐藏【建议开启】
* 一般来说Certd设置好之后后续很少需要访问修改。
* 所以我们平时可以把站点访问关闭,需要的时候再打开,减少站点被攻击的风险
* 请前往 `系统管理->系统设置->安全设置->开启站点隐藏`
* [站点隐藏设置说明](./hidden/)
![](./images/hidden.png)
## 4、登录双重验证
点击查看 [站点隐藏功能详细使用说明](./hidden/)
### 4、登录双重验证
支持2FA双重认证
![](./images/2fa.png)
## 5、数据库自动备份【建议开启】
### 5、数据库自动备份【建议开启】
* [自动备份设置说明](../../use/backup/)
## 更多安全生产建议
[安全生产建议](./suggest.md)
## 二、安全生产建议
尽管`Cert`本身实现了很多安全特性,但`外部环境的安全`仍需要您来确保。
`务必`遵循如下建议做好安全防护
*`务必`使用`HTTPS协议`访问本应用,避免被中间人攻击
*`务必`使用`web应用防火墙`防护本应用防止XSS、SQL注入等攻击
*`务必`做好`服务器本身`的安全防护,防止数据库泄露
*`务必`做好[`数据备份`](../../use/backup/),避免数据丢失
*`务必`修改管理员账号用户名且建议将admin注册为普通用户且设置为禁用。
* 建议开启[`站点隐藏`](./hidden/)功能

View File

@@ -1,10 +0,0 @@
# 安全生产建议
尽管`Cert`本身实现了很多安全特性,但`外部环境的安全`仍需要您来确保。
`务必`遵循如下建议做好安全防护
*`务必`使用`HTTPS协议`访问本应用,避免被中间人攻击
*`务必`使用`web应用防火墙`防护本应用防止XSS、SQL注入等攻击
*`务必`做好`服务器本身`的安全防护,防止数据库泄露
*`务必`做好[`数据备份`](../../use/backup/),避免数据丢失
* 建议开启[`站点隐藏`](./hidden/)功能

View File

@@ -5,32 +5,28 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
## 1、关于证书续期
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
>* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
## 一、特性
## 2、项目特性
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
* 全自动申请证书(支持所有注册商注册的域名)
* 全自动部署更新证书(目前支持部署到主机、部署到阿里云、腾讯云等目前已支持60+部署插件)
* 支持通配符域名/泛域名,支持多个域名打到一个证书上
* 邮件通知
* 私有化部署,保障数据安全
* 支持SQLite、Postgresql、MySQL数据库
* 全自动申请证书(支持所有注册商注册的域名支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等70+部署插件)
* 支持通配符域名/泛域名,支持多个域名打到一个证书上支持pem、pfx、der、jks等多种证书格式
* 邮件通知、webhook通知、企微、钉钉、飞书、anpush等多种通知方式
* 私有化部署,数据保存本地,安装升级非常简单快捷
* 镜像由Github Actions构建过程公开透明
* 授权加密站点隐藏2FA密码防爆破等多重安全保障
* 支持SQLitePostgreSQL、MySQL多种数据库
* 开放接口支持
* 站点证书监控
* 多用户管理
![](../images/intro/intro.svg)
## 二、一些说明
* 本项目申请证书过程遵循acme协议
* 需要验证域名所有权,一般有两种方式
* http-01 在网站根目录下放置一份txt文件
* dns-01 需要给域名添加txt解析记录通配符域名只能用这种方式本项目仅支持dns-01
* 证书续期:
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
* 设置每天自动运行当证书过期前35天会自动重新申请证书并部署
## 三、证书颁发机构对比
* Let's Encrypt申请最简单。
* Google: 大厂光环兼容性好首次需要翻墙获取EAB。
* ZeroSSL 需要EAB获取EAB无需翻墙。

View File

@@ -1,5 +1,6 @@
# 源码部署
如果没有`git``nodejs`基础,不推荐
如果没有开发基础、没有运维基础、没有`git``nodejs`基础,强烈不推荐此方式
## 一、源码安装
### 环境要求
@@ -42,8 +43,8 @@ git pull
kill -9 $(lsof -t -i:7001)
# 重新编译启动
./start.sh
```
```
::: warning
升级certd版本前切记切记先备份一下数据
:::

View File

@@ -1,5 +0,0 @@
# 插件列表
![img_1.png](../images/plugins/list.png)

View File

@@ -0,0 +1,58 @@
# 授权列表
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **阿里云授权** | |
| 2.| **EAB授权** | ZeroSSL证书申请需要EAB授权 |
| 3.| **google cloud** | 谷歌云授权 |
| 4.| **主机登录授权** | |
| 5.| **SFTP授权** | |
| 6.| **阿里云OSS授权** | 包含地域和Bucket |
| 7.| **FTP授权** | |
| 8.| **腾讯云** | |
| 9.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
| 10.| **七牛云授权** | |
| 11.| **七牛OSS授权** | |
| 12.| **天翼云授权** | |
| 13.| **s3/minio授权** | S3/minio oss授权 |
| 14.| **baota授权** | |
| 15.| **易盾DCDN授权** | https://user.yiduncdn.com |
| 16.| **易盾rcdn授权** | 易盾CDN每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 17.| **易发云短信** | sms.yfyidc.cn/ |
| 18.| **cdnfly授权** | |
| 19.| **群晖登录授权** | |
| 20.| **k8s授权** | |
| 21.| **1panel授权** | 账号和密码 |
| 22.| **百度云授权** | |
| 23.| **LeCDN授权** | |
| 24.| **白山云授权** | |
| 25.| **plesk授权** | |
| 26.| **易支付** | |
| 27.| **支付宝** | |
| 28.| **微信支付** | |
| 29.| **长亭雷池授权** | |
| 30.| **lucky** | |
| 31.| **括彩云cdn授权** | 括彩云CDN每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
| 32.| **uniCloud** | unicloud授权 |
| 33.| **华为云授权** | |
| 34.| **西部数码授权** | |
| 35.| **多吉云** | |
| 36.| **我爱云授权** | 我爱云CDN |
| 37.| **CacheFly** | CacheFly |
| 38.| **Gcore** | Gcore |
| 39.| **亚马逊云aws授权** | |
| 40.| **dns.la授权** | |
| 41.| **又拍云** | |
| 42.| **火山引擎** | |
| 43.| **京东云** | |
| 44.| **51dns授权** | |
<style module>
table th:first-of-type {
width: 65px;
}
table th:nth-of-type(2) {
width: 240px;
}
</style>

View File

@@ -0,0 +1,130 @@
# 任务插件
`70` 款任务插件
## 1. 证书申请
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **证书申请JS版** | 免费通配符域名证书申请,支持多个域名打到同一个证书上 |
| 2.| **证书申请Lego** | 支持海量DNS解析提供商推荐使用一样的免费通配符域名证书申请支持多个域名打到同一个证书上 |
| 3.| **商用证书托管** | 手动上传自定义证书后,自动部署(每次证书有更新,都需要手动上传一次) |
## 2. 主机
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **FTP-上传证书到FTP** | 将证书上传到FTP服务器 |
| 2.| **IIS-部署到IIS站点** | |
| 3.| **主机-执行远程主机脚本命令** | 可以执行重启nginx等操作让证书生效 |
| 4.| **主机-部署证书到SSH主机** | SFTP上传证书到主机然后SSH执行部署脚本命令 |
## 3. CDN
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **易盾-部署到易盾DCDN** | 主要是防御http://user.yiduncdn.com/ |
| 2.| **易盾-部署到易盾RCDN** | 易盾CDN每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 3.| **cdnfly-部署证书到cdnfly** | cdnfly |
| 4.| **百度云-部署证书到CDN** | 部署到百度云CDN |
| 5.| **LeCDN-更新证书** | |
| 6.| **LeCDN-更新证书V2** | 支持新版本LeCDN |
| 7.| **白山云-更新证书** | |
| 8.| **天翼云-部署证书到CDN** | 部署证书到天翼云CDN和全站加速 |
| 9.| **括彩云-部署到括彩云CDN** | 括彩云CDN每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
| 10.| **多吉云-部署到多吉云CDN** | |
| 11.| **我爱云-部署证书到我爱云CDN** | 部署证书到我爱云CDN |
| 12.| **CacheFly-部署证书到CacheFly** | 部署证书到 CacheFly |
| 13.| **Gcore-部署证书到Gcore** | 仅上传 并不会部署到cdn |
| 14.| **Gcore-刷新Gcore证书** | 刷新现有的证书 |
| 15.| **又拍云-部署证书到CDN/USS** | 支持又拍云CDN又拍云云存储USS |
## 4. 面板
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **宝塔-面板证书部署** | 部署宝塔面板本身的ssl证书 |
| 2.| **宝塔-网站证书部署** | 部署宝塔管理的站点的ssl证书目前支持网站站点、docker站点等 |
| 3.| **群晖-部署证书到群晖面板** | Synology支持6.x以上版本 |
| 4.| **K8S-部署证书到Secret** | 部署证书到k8s的secret |
| 5.| **K8S-Ingress 证书部署** | 部署证书到k8s的Ingress |
| 6.| **1Panel-部署证书到1Panel** | 更新1Panel的证书 |
| 7.| **Plesk-部署Plesk网站证书** | |
| 8.| **雷池-更新证书** | 更新长亭雷池WAF的证书 |
| 9.| **lucky-更新Lucky证书** | |
| 10.| **uniCloud-部署到服务空间** | 部署到服务空间 |
| 11.| **威联通-部署证书到威联通** | 部署证书到qnap |
## 5. 阿里云
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **阿里云-部署到Ack** | 部署到阿里云Ack集群Ingress等通过Secret管理证书的应用 |
| 2.| **阿里云-部署至任意云资源** | 【不建议使用】需要消耗阿里云自动部署次数支持SLB、LIVE、webHosting、VOD、CR、DCDN、DDoS、CDN、ALB、APIGateway、FC、GA、MSE、NLB、OSS、SAE、WAF等云产品 |
| 3.| **阿里云-部署证书至CDN** | 自动部署域名证书至阿里云CDN |
| 4.| **阿里云-部署证书至DCDN** | 依赖证书申请前置任务自动部署域名证书至阿里云DCDN |
| 5.| **阿里云-部署证书至OSS** | 自动部署域名证书至阿里云OSS |
| 6.| **阿里云-上传证书到阿里云** | 如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出 |
| 7.| **阿里云-部署至阿里云WAF** | 部署证书到阿里云WAF |
| 8.| **阿里云-部署至ALB应用负载均衡** | ALB,更新监听器的默认证书 |
| 9.| **阿里云-部署至NLB网络负载均衡** | NLB,网络负载均衡,更新监听器的默认证书 |
| 10.| **阿里云-部署至SLB(传统负载均衡)** | 部署证书到阿里云SLB(传统负载均衡) |
| 11.| **阿里云-部署至阿里云FC(3.0)** | 部署证书到阿里云函数计算FC3.0,【注意】证书的加密算法必须选择【pkcs1旧版】 |
## 6. 华为云
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **华为云-部署证书至CDN** | |
## 7. 腾讯云
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **腾讯云-部署证书到任意云资源** | 支持负载均衡、CDN、DDoS、直播、点播、Web应用防火墙、API网关、TEO、容器服务、对象存储、轻应用服务器、云原生微服务、云开发 |
| 2.| **腾讯云-部署到CLB** | 暂时只支持单向认证证书,暂时只支持通用负载均衡 |
| 3.| **腾讯云-部署到CDN废弃** | 已废弃请使用v2版 |
| 4.| **腾讯云-部署到CDN-v2** | 推荐使用 |
| 5.| **腾讯云-上传证书到腾讯云** | 上传成功后输出tencentCertId |
| 6.| **腾讯云-部署证书到COS** | 部署到腾讯云COS源站域名证书【注意很不稳定需要重试很多次偶尔才能成功一次】 |
| 7.| **腾讯云-部署到腾讯云EO** | 腾讯云边缘安全加速平台EO必须配置上传证书到腾讯云任务 |
| 8.| **腾讯云-删除即将过期证书** | 仅删除未使用的证书 |
| 9.| **腾讯云-部署到TKE-ingress** | serverless集群请使用K8S部署插件Qcloud类型需要【上传到腾讯云】作为前置任务ApiServer未开启外网访问则需要做域名的内网IP映射 |
## 8. 火山引擎
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **火山引擎-部署证书至CDN** | 支持网页,文件下载,音视频点播 |
| 2.| **火山引擎-部署证书至CLB** | 部署至火山引擎负载均衡 |
| 3.| **火山引擎-上传证书至证书中心** | 上传证书至火山引擎证书中心 |
| 4.| **火山引擎-部署证书至ALB** | 部署至火山引擎应用负载均衡 |
| 5.| **火山引擎-部署证书至Live** | 部署至火山引擎视频直播 |
## 9. 京东云
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **京东云-部署证书至CDN** | 京东云内容分发网络 |
| 2.| **京东云-更新已有证书** | 更新SSL数字证书中的证书 |
| 3.| **京东云-上传新证书** | 上传证书到SSL数字证书中心 |
## 10. 七牛云
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **七牛云-部署证书至OSS** | 自动部署域名证书至七牛云KODO注意是自定义源站域名不是CDN域名 |
| 2.| **七牛云-部署证书至CDN** | 自动部署域名证书至七牛云CDN |
## 11. 亚马逊云
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **AWS-部署证书到CloudFront** | 部署证书到 AWS CloudFront |
## 12. 其他
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **Demo-测试插件** | |
| 2.| **重启 Certd** | 【仅管理员可用】 重启 certd的https服务用于更新 Certd 的 ssl 证书 |
| 3.| **自定义js脚本** | 【仅管理员】运行自定义js脚本执行 |
| 4.| **等待** | 等待一段时间 |
| 5.| **数据库备份** | 仅支持备份SQLite数据库 |
<style module>
table th:first-of-type {
width: 65px;
}
table th:nth-of-type(2) {
width: 240px;
}
</style>

View File

@@ -0,0 +1,22 @@
# DNS提供商
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **阿里云** | 阿里云DNS解析提供商 |
| 2.| **腾讯云** | 腾讯云域名DNS解析提供者 |
| 3.| **华为云** | 华为云DNS解析提供商 |
| 4.| **西部数码** | west dns provider |
| 5.| **dns.la** | dns.la |
| 6.| **火山引擎** | 火山引擎DNS解析提供商 |
| 7.| **京东云** | 京东云DNS解析提供商 |
| 8.| **51dns** | 51DNS |
<style module>
table th:first-of-type {
width: 65px;
}
table th:nth-of-type(2) {
width: 240px;
}
</style>

View File

@@ -0,0 +1,26 @@
# 通知插件
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **企业微信通知** | 企业微信群聊机器人通知 |
| 2.| **电子邮件** | 电子邮件通知 |
| 3.| **爱语飞飞微信通知(iyuu)** | https://iyuu.cn/ |
| 4.| **自定义webhook** | 根据模版自定义http请求 |
| 5.| **Server酱ᵀ** | https://sct.ftqq.com/ |
| 6.| **Server酱³** | https://doc.sc3.ft07.com/serverchan3 |
| 7.| **AnPush** | https://anpush.com |
| 8.| **Telegram通知** | Telegram Bot推送通知 |
| 9.| **Discord 通知** | Discord 机器人通知 |
| 10.| **Slack通知** | Slack消息推送通知 |
| 11.| **Bark 通知** | Bark 推送通知插件 |
| 12.| **飞书通知** | 飞书群聊webhook通知 |
<style module>
table th:first-of-type {
width: 65px;
}
table th:nth-of-type(2) {
width: 240px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

73
docs/guide/qa/index.md Normal file
View File

@@ -0,0 +1,73 @@
# 常见报错解决
## 1. getaddrinfo ENOTFOUND错误
如果出现`getaddrinfo ENOTFOUND`错误,可以尝试在`docker-compose.yaml`中设置dns
```yaml
version: '3.3' # 兼容旧版docker-compose
services:
certd:
#↓↓↓↓ ------------ # 如果出现getaddrinfo ENOTFOUND错误可以尝试设置dns
dns:
- 223.5.5.5 # 阿里云公共dns
- 223.6.6.6
# # ↓↓↓↓ ------- # 如果你服务器在腾讯云可以用这个替换上面阿里云的公共dns
# - 119.29.29.29 # 腾讯云公共dns
# - 182.254.116.116
# # ↓↓↓↓ ------- # 如果你服务器部署在国外可以用这个替换上面阿里云的公共dns
# - 8.8.8.8 # 谷歌公共dns
# - 8.8.4.4
```
如果仍然有问题,按如下步骤检查是否能够ping通域名
```shell
docker exec -it certd /bin/sh
ping www.baidu.com
ping gg.px.certd.handfree.work
ping app.handfree.work
```
如果您是宝塔部署的
可以试试将容器网络加入brige网络看是否解决问题
![img.png](images/baota-net.png)
如果还是不行,请联系我们
## 2. 连接IPv6超时
docker-compose 需要放开IPv6网络的配置
```yaml
services:
certd:
networks:
- ip6net
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络还需要把上面networks的注释放开
networks:
ip6net:
enable_ipv6: true
ipam:
config:
- subnet: 2001:db8::/64
```
## 3. SSL_CERT_NOT_MATCH_DOMAIN_ERROR
部署证书任务报类似 `SSL_CERT_NOT_MATCH_DOMAIN_ERROR`错误
这是由于当前流水线的证书域名与要部署的目标站点的域名不匹配导致的,在申请证书任务中,增加目标站点域名,重新运行流水线即可
## 4. 没有服务器配置文件,请检查是否开启了外网映射!
宝塔网站证书部署报错:`Error: 没有服务器配置文件,请检查是否开启了外网映射!`
解决方案:先手动在宝塔网站中设置一次证书
## 5. 如何查看容器日志
```shell
docker logs -f --tail 200 certd
```

14
docs/guide/qa/use.md Normal file
View File

@@ -0,0 +1,14 @@
# 使用问题
## 1. 是否支持IP证书
因为ACME协议不支持IP证书所以certd目前也不支持IP证书
## 2. 建议设置多长时间运行一次流水线
建议每天运行一次,检查证书过期时间
当证书没过期时,自动跳过部署
当证书到期前35天创建流水线时可以修改将会自动重新申请证书自动部署

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 165 KiB

View File

@@ -17,7 +17,9 @@ CERTD_HTTPS_port=7002
参考Certd顶部的创建证书流水线教程
### 2、配置复制到本机任务
将证书复制到certd的证书安装位置
将证书复制到certd的证书安装位置
证书路径:`ssl/cert.crt`
私钥路径:`ssl/cert.key`
![](./images/1.png)
![](./images/2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,13 @@
# 带输出的前置任务
前置任务输出可以在后续任务中使用
比如上传证书到阿里云会返回阿里云的CertId之后其他阿里云的部署任务可以选择复用这个证书
## 复用证书
![img.png](images/pretask1.png)
在后续任务中可以选择前置任务的输出
![img.png](images/pretask2.png)

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.34.1"
"version": "1.34.3"
}

View File

@@ -30,7 +30,8 @@
"init": "lerna run build",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
"docs:preview": "vitepress preview docs",
"pub": "echo 1"
},
"license": "AGPL-3.0",
"dependencies": {

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/publishlab/node-acme-client/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/acme-client
## [1.34.2](https://github.com/publishlab/node-acme-client/compare/v1.34.1...v1.34.2) (2025-05-11)
### Performance Improvements
* http方式支持校验443端口 ([d75fcb7](https://github.com/publishlab/node-acme-client/commit/d75fcb7fec421a9a638eaa27fe9378c84b5e0f19))
## [1.34.1](https://github.com/publishlab/node-acme-client/compare/v1.34.0...v1.34.1) (2025-05-05)
### Bug Fixes

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.34.1",
"version": "1.34.3",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.34.1",
"@certd/basic": "^1.34.3",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
@@ -51,7 +51,8 @@
"lint": "eslint .",
"lint-types": "tsd",
"prepublishOnly": "npm run build-docs",
"test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\""
"test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
"pub": "npm publish"
},
"repository": {
"type": "git",
@@ -68,5 +69,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -234,6 +234,7 @@ export default async (client, userOpts) => {
throw new CancelError("用户取消");
}
const waitDnsDiffuseTime = opts.waitDnsDiffuseTime || 30;
try {
// eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises);
@@ -242,8 +243,8 @@ export default async (client, userOpts) => {
await wait(60 * 1000);
} else {
await runPromisePa(localVerifyTasks, 1000);
log("本地校验完成,等待30s")
await wait(30 * 1000)
log(`本地校验完成,等待${waitDnsDiffuseTime}s`)
await wait(waitDnsDiffuseTime * 1000)
}
log("开始向提供商请求挑战验证");

View File

@@ -24,22 +24,46 @@ const dns = dnsSdk.promises
*/
async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
async function doQuery(challengeUrl){
log(`正在测试请求 ${challengeUrl} `)
// const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
// const challengeUrl = `https://${authz.identifier.value}:${httpsPort}${suffix}`;
/* May redirect to HTTPS with invalid/self-signed cert - https://letsencrypt.org/docs/challenge-types/#http-01-challenge */
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
let data = ""
try{
const resp = await axios.get(challengeUrl, { httpsAgent });
data = (resp.data || '').replace(/\s+$/, '');
}catch (e) {
log(`[error] HTTP request error from ${authz.identifier.value}`,e.message);
return false
}
if (!data || (data !== keyAuthorization)) {
log(`[error] Authorization not found in HTTP response from ${authz.identifier.value}`);
return false
}
return true
}
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
const challengeUrl = `http://${authz.identifier.value}:${httpPort}${suffix}`;
/* May redirect to HTTPS with invalid/self-signed cert - https://letsencrypt.org/docs/challenge-types/#http-01-challenge */
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
const resp = await axios.get(challengeUrl, { httpsAgent });
const data = (resp.data || '').replace(/\s+$/, '');
log(`Query successful, HTTP status code: ${resp.status}`);
if (!data || (data !== keyAuthorization)) {
throw new Error(`Authorization not found in HTTP response from ${authz.identifier.value}`);
if (!await doQuery(challengeUrl)) {
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
const httpsChallengeUrl = `https://${authz.identifier.value}:${httpsPort}${suffix}`;
const res = await doQuery(httpsChallengeUrl)
if (!res) {
throw new Error(`[error] 验证失败请检查以上测试url是否可以正常访问`);
}
}
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
return true;
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/basic
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/basic
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
### Performance Improvements

View File

@@ -1 +1 @@
00:16
00:08

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.34.1",
"version": "1.34.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -12,7 +12,8 @@
"build": "npm run before-build && tsc --skipLibCheck",
"dev-build": "npm run build",
"preview": "vite preview",
"test": "mocha --loader=ts-node/esm"
"test": "mocha --loader=ts-node/esm",
"pub": "npm publish"
},
"dependencies": {
"axios": "^1.7.2",
@@ -44,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -145,7 +145,8 @@ export function createAxiosService({ logger }: { logger: Logger }) {
} else {
logger.info("http response status:", response?.status);
}
if (response?.config?.returnResponse) {
if (response?.config?.returnOriginRes) {
return response;
}
return response.data;
@@ -215,7 +216,7 @@ export type HttpRequestConfig<D = any> = {
logParams?: boolean;
logRes?: boolean;
httpProxy?: string;
returnResponse?: boolean;
returnOriginRes?: boolean;
} & AxiosRequestConfig<D>;
export type HttpClient = {
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/pipeline
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/pipeline
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.34.1",
"version": "1.34.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -13,11 +13,12 @@
"dev-build": "npm run build",
"build3": "rollup -c",
"preview": "vite preview",
"test": "mocha --loader=ts-node/esm"
"test": "mocha --loader=ts-node/esm",
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.34.1",
"@certd/plus-core": "^1.34.1",
"@certd/basic": "^1.34.3",
"@certd/plus-core": "^1.34.3",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -43,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/lib-huawei
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/lib-huawei
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.34.1",
"version": "1.34.3",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -10,7 +10,8 @@
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
"build": "npm run before-build && rollup -c ",
"dev-build": "npm run build",
"preview": "vite preview"
"preview": "vite preview",
"pub": "npm publish"
},
"dependencies": {
"axios": "^1.7.2",
@@ -23,5 +24,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/lib-iframe
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/lib-iframe
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.34.1",
"version": "1.34.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -13,7 +13,8 @@
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
"preview": "vite preview",
"pub": "npm publish"
},
"dependencies": {
"nanoid": "^4.0.0"
@@ -30,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/jdcloud
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/jdcloud
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.34.1",
"version": "1.34.3",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -9,7 +9,8 @@
"test": "cross-env NODE_CONFIG_DIR=./test/config mocha --recursive --require babel-register",
"dev": "babel src --out-dir babel -w",
"build": "rollup -c ",
"dev-build": "npm run build"
"dev-build": "npm run build",
"pub": "npm publish"
},
"author": "",
"license": "Apache",
@@ -60,5 +61,5 @@
"fetch"
]
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/lib-k8s
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/lib-k8s
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.34.1",
"version": "1.34.3",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -13,10 +13,11 @@
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
"preview": "vite preview",
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.34.1",
"@certd/basic": "^1.34.3",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -31,5 +32,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
### Performance Improvements
* 小助手可以关闭 ([3e2101a](https://github.com/certd/certd/commit/3e2101aa5b56548614102e900d59819ce8c7e97c))
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
### Performance Improvements
* 支持设置网安备案号 ([d18e431](https://github.com/certd/certd/commit/d18e431e2f08e6b37704032c4ea6fbdd8e971442))
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
**Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.34.1",
"version": "1.34.3",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -27,10 +27,10 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.34.1",
"@certd/basic": "^1.34.1",
"@certd/pipeline": "^1.34.1",
"@certd/plus-core": "^1.34.1",
"@certd/acme-client": "^1.34.3",
"@certd/basic": "^1.34.3",
"@certd/pipeline": "^1.34.3",
"@certd/plus-core": "^1.34.3",
"@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.20.3",
"@midwayjs/i18n": "~3.20.3",
@@ -61,5 +61,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -25,7 +25,9 @@ export class SysPublicSettings extends BaseSettings {
limitUserPipelineCount = 0;
managerOtherUserPipeline = false;
icpNo?: string;
mpsNo?: string;
robots?: boolean = true;
aiChatEnabled = true;
}
export class SysPrivateSettings extends BaseSettings {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.34.1",
"version": "1.34.3",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
**Note:** Version bump only for package @certd/plugin-cert
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
**Note:** Version bump only for package @certd/plugin-cert
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
### Bug Fixes

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.34.1",
"version": "1.34.3",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -12,13 +12,14 @@
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
"preview": "vite preview",
"pub": "npm publish"
},
"dependencies": {
"@certd/acme-client": "^1.34.1",
"@certd/basic": "^1.34.1",
"@certd/pipeline": "^1.34.1",
"@certd/plugin-lib": "^1.34.1",
"@certd/acme-client": "^1.34.3",
"@certd/basic": "^1.34.3",
"@certd/pipeline": "^1.34.3",
"@certd/plugin-lib": "^1.34.3",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -42,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -8,6 +8,8 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
logger!: ILogger;
usePunyCode(): boolean {
//是否使用punycode来添加解析记录
//默认都使用原始中文域名来添加
return false;
}

View File

@@ -63,6 +63,7 @@ type AcmeServiceOptions = {
maxCheckRetryCount?: number;
userId: number;
domainParser: IDomainParser;
waitDnsDiffuseTime?: number;
};
export class AcmeService {

View File

@@ -68,9 +68,9 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
],
},
required: true,
helper: `DNS直接验证:域名是在阿里云/腾讯云/华为云/Cloudflare/NameSilo/西数/火山/dns.la/京东云注册的,选它
CNAME代理验证支持任何注册商注册的域名,第一次需要手动添加CNAME记录
HTTP文件验证不支持泛域名需要配置网站文件上传`,
helper: `1. <b>DNS直接验证</b>域名dns解析是在阿里云/腾讯云/华为云/CF/NameSilo/西数/火山/dns.la/京东云/51dns的,选它
2. <b>CNAME代理验证</b>支持任何注册商的域名第一次需要手动添加CNAME记录建议将DNS服务器修改为阿里云/腾讯云的然后使用DNS直接验证
3. <b>HTTP文件验证</b>:不支持泛域名,需要配置网站文件上传`,
})
challengeType!: string;
@@ -290,6 +290,17 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
})
maxCheckRetryCount = 20;
@TaskInput({
title: "等待解析生效时长",
value: 30,
component: {
name: "a-input-number",
vModel: "value",
},
helper: "等待解析生效时长(秒)",
})
waitDnsDiffuseTime = 30;
acme!: AcmeService;
eab!: EabAccess;
@@ -341,6 +352,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
signal: this.ctx.signal,
maxCheckRetryCount: this.maxCheckRetryCount,
domainParser,
waitDnsDiffuseTime: this.waitDnsDiffuseTime,
});
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
### Performance Improvements
* 支持部署到maoyun cdn ([68f333f](https://github.com/certd/certd/commit/68f333fb87ce85eed27436ecb0f76351c0ccb0d1))
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
### Performance Improvements
* http方式支持校验443端口 ([d75fcb7](https://github.com/certd/certd/commit/d75fcb7fec421a9a638eaa27fe9378c84b5e0f19))
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.34.1",
"version": "1.34.3",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -12,13 +12,14 @@
"dev-build": "npm run build",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
"preview": "vite preview",
"pub": "npm publish"
},
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@aws-sdk/client-s3": "^3.787.0",
"@certd/basic": "^1.34.1",
"@certd/pipeline": "^1.34.1",
"@certd/basic": "^1.34.3",
"@certd/pipeline": "^1.34.3",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -49,5 +50,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "9749fc817d3cfd435e8cb3f2f86edc81d69e2310"
"gitHead": "a1e504c1387e9b0554c8d030cb53c5058e7d683a"
}

View File

@@ -52,9 +52,11 @@ export class AliossClient {
}
}
async uploadFile(filePath: string, content: Buffer | string) {
async uploadFile(filePath: string, content: Buffer | string, timeout = 1000 * 60 * 60) {
await this.init();
return await this.client.put(filePath, content);
return await this.client.put(filePath, content, {
timeout,
});
}
async removeFile(filePath: string) {
@@ -62,9 +64,11 @@ export class AliossClient {
return await this.client.delete(filePath);
}
async downloadFile(key: string, savePath: string) {
async downloadFile(key: string, savePath: string, timeout = 1000 * 60 * 60) {
await this.init();
return await this.client.get(key, savePath);
return await this.client.get(key, savePath, {
timeout,
});
}
async listDir(dirKey: string) {

View File

@@ -1,87 +1 @@
import { merge } from "lodash-es";
export function createCertDomainGetterInputDefine(opts?: { certInputKey?: string; props?: any }) {
const certInputKey = opts?.certInputKey || "cert";
return merge(
{
title: "当前证书域名",
component: {
name: "cert-domains-getter",
},
mergeScript: `
return {
component:{
inputKey: ctx.compute(({form})=>{
return form.${certInputKey}
}),
}
}
`,
required: true,
},
opts?.props
);
}
export function createRemoteSelectInputDefine(opts?: {
title: string;
certDomainsInputKey?: string;
accessIdInputKey?: string;
typeName?: string;
action: string;
type?: string;
watches?: string[];
helper?: string;
formItem?: any;
mode?: string;
multi?: boolean;
required?: boolean;
rules?: any;
mergeScript?: string;
}) {
const title = opts?.title || "请选择";
const certDomainsInputKey = opts?.certDomainsInputKey || "certDomains";
const accessIdInputKey = opts?.accessIdInputKey || "accessId";
const typeName = opts?.typeName;
const action = opts?.action;
const type = opts?.type || "plugin";
const watches = opts?.watches || [];
const helper = opts?.helper || "请选择";
let mode = "tags";
if (opts.multi === false) {
mode = undefined;
} else {
mode = opts?.mode ?? "tags";
}
const item = {
title,
component: {
name: "remote-select",
vModel: "value",
mode,
type,
typeName,
action,
watches: [certDomainsInputKey, accessIdInputKey, ...watches],
},
rules: opts?.rules,
required: opts.required ?? true,
mergeScript:
opts.mergeScript ??
`
return {
component:{
form: ctx.compute(({form})=>{
return form
})
},
}
`,
helper,
};
return merge(item, opts?.formItem);
}
export * from "./util.js";

View File

@@ -0,0 +1,85 @@
import { merge } from "lodash-es";
export function createCertDomainGetterInputDefine(opts?: { certInputKey?: string; props?: any }) {
const certInputKey = opts?.certInputKey || "cert";
return merge(
{
title: "当前证书域名",
component: {
name: "cert-domains-getter",
},
mergeScript: `
return {
component:{
inputKey: ctx.compute(({form})=>{
return form.${certInputKey}
}),
}
}
`,
required: true,
},
opts?.props
);
}
export function createRemoteSelectInputDefine(opts?: {
title: string;
certDomainsInputKey?: string;
accessIdInputKey?: string;
typeName?: string;
action: string;
type?: string;
watches?: string[];
helper?: string;
formItem?: any;
mode?: string;
multi?: boolean;
required?: boolean;
rules?: any;
mergeScript?: string;
}) {
const title = opts?.title || "请选择";
const certDomainsInputKey = opts?.certDomainsInputKey || "certDomains";
const accessIdInputKey = opts?.accessIdInputKey || "accessId";
const typeName = opts?.typeName;
const action = opts?.action;
const type = opts?.type || "plugin";
const watches = opts?.watches || [];
const helper = opts?.helper || "请选择";
let mode = "tags";
if (opts.multi === false) {
mode = undefined;
} else {
mode = opts?.mode ?? "tags";
}
const item = {
title,
component: {
name: "remote-select",
vModel: "value",
mode,
type,
typeName,
action,
watches: [certDomainsInputKey, accessIdInputKey, ...watches],
},
rules: opts?.rules,
required: opts.required ?? true,
mergeScript:
opts.mergeScript ??
`
return {
component:{
form: ctx.compute(({form})=>{
return form
})
},
}
`,
helper,
};
return merge(item, opts?.formItem);
}

View File

@@ -24,7 +24,7 @@ export class FtpAccess extends BaseAccess {
host!: string;
@AccessInput({
title: "host",
title: "端口",
value: 21,
component: {
placeholder: "21",

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
### Performance Improvements
* 小助手可以关闭 ([3e2101a](https://github.com/certd/certd/commit/3e2101aa5b56548614102e900d59819ce8c7e97c))
* 支持AI分析报错 ([aa96859](https://github.com/certd/certd/commit/aa96859798166426e485947a6590464de189de05))
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
### Bug Fixes
* 修复刷新流水线页面后日志不自动更新的bug ([0b2e28b](https://github.com/certd/certd/commit/0b2e28b62dd5eb6804c602083e65c87a9d1d72d2))
### Performance Improvements
* 集成智能问答机器人 ([9dd4905](https://github.com/certd/certd/commit/9dd49054d18ec436a5029444ca55a38adc682933))
* 支持设置网安备案号 ([d18e431](https://github.com/certd/certd/commit/d18e431e2f08e6b37704032c4ea6fbdd8e971442))
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.34.1",
"version": "1.34.3",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -19,7 +19,8 @@
"upgrade": "yarn upgrade-interactive --latest",
"tsc": "vue-tsc --noEmit --skipLibCheck",
"circle:check": "pnpm dependency-cruise --validate --output-type err-html -f dependency-report.html src",
"afterPubPush": "git add . && git commit -m \"build: publish success\" && git push"
"afterPubPush": "git add . && git commit -m \"build: publish success\" && git push",
"pub": "echo 1"
},
"author": "greper",
"license": "AGPL-3.0",
@@ -101,8 +102,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.34.1",
"@certd/pipeline": "^1.34.1",
"@certd/lib-iframe": "^1.34.3",
"@certd/pipeline": "^1.34.3",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -3,6 +3,7 @@
<FsFormProvider>
<contextHolder />
<router-view />
<MaxKBChat v-if="settingsStore.sysPublic.aiChatEnabled !== false" ref="chatBox" />
</FsFormProvider>
</AConfigProvider>
</template>
@@ -10,7 +11,7 @@
<script lang="ts" setup>
import zhCN from "ant-design-vue/es/locale/zh_CN";
import enUS from "ant-design-vue/es/locale/en_US";
import { computed, provide, ref } from "vue";
import { computed, onMounted, provide, ref } from "vue";
import "dayjs/locale/zh-cn";
import "dayjs/locale/en";
import dayjs from "dayjs";
@@ -19,7 +20,9 @@ import { useAntdDesignTokens } from "/@/vben/hooks";
import { theme } from "ant-design-vue";
import AConfigProvider from "ant-design-vue/es/config-provider";
import { Modal } from "ant-design-vue";
import MaxKBChat from "/@/components/ai/index.vue";
import { util } from "/@/utils";
import { useSettingStore } from "/@/store/settings";
defineOptions({
name: "App",
});
@@ -46,6 +49,7 @@ localeChanged("zh-cn");
provide("fn:router.reload", reload);
provide("fn:locale.changed", localeChanged);
const settingsStore = useSettingStore();
const { isDark } = usePreferences();
const { tokens } = useAntdDesignTokens();
@@ -69,4 +73,14 @@ const tokenTheme = computed(() => {
// pageStore.init();
// const settingStore = useSettingStore();
// settingStore.init();
const chatBox = ref();
// onMounted(async () => {
// await util.sleep(2000);
// await chatBox.value.openChat({ q: "hello" });
// });
const openChat = (q: string) => {
chatBox.value.openChat({ q });
};
provide("fn:ai.open", openChat);
</script>

View File

@@ -36,7 +36,7 @@ function createService() {
return response;
}
//@ts-ignore
if (response.config.returnResponse) {
if (response.config.returnOriginRes) {
return response;
}
// dataAxios 是 axios 返回数据中的 data

View File

@@ -0,0 +1,316 @@
<template>
<div id="maxkb">
<!-- 引导层 -->
<div v-if="showGuide" class="maxkb-mask">
<div class="maxkb-content" :style="contentStyle"></div>
</div>
<div v-if="showGuide" class="maxkb-tips" :style="tipsStyle">
<div class="" @click="closeGuide">
<!-- 关闭图标 -->
<svg style="vertical-align: middle; overflow: hidden" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z"
fill="#ffffff"
></path>
</svg>
</div>
<div class="maxkb-title">🌟 遇见问题不再有障碍</div>
<p>你好我是你的智能小助手<br />点我开启高效解答模式让问题变成过去式</p>
<div class="maxkb-button">
<button @click="closeGuide">我知道了</button>
</div>
<span class="maxkb-arrow"></span>
</div>
<!-- 聊天按钮 -->
<div v-show="!chatVisible" class="maxkb-chat-button" @click="toggleChat">
<img src="https://maxkb.handfree.work/ui/MaxKB.gif" />
</div>
<!-- 聊天窗口 -->
<div id="maxkb-chat-container" :class="{ 'maxkb-enlarge': isFullscreen }" :style="containerStyle">
<iframe id="maxkb-chat" allow="microphone" :src="chatUrl"></iframe>
<div class="maxkb-operate">
<div class="viewport" :class="{ 'maxkb-viewportnone': !isFullscreen }" @click="toggleFullscreen">
<!-- 缩小图标 -->
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M7.507 11.6645C7.73712 11.6645 7.94545 11.7578 8.09625 11.9086C8.24706 12.0594 8.34033 12.2677 8.34033 12.4978V16.7976C8.34033 17.0277 8.15378 17.2143 7.92366 17.2143H7.09033C6.86021 17.2143 6.67366 17.0277 6.67366 16.7976V14.5812L3.41075 17.843C3.24803 18.0057 2.98421 18.0057 2.82149 17.843L2.23224 17.2537C2.06952 17.091 2.06952 16.8272 2.23224 16.6645L5.56668 13.3311H3.19634C2.96622 13.3311 2.77967 13.1446 2.77967 12.9145V12.0811C2.77967 11.851 2.96622 11.6645 3.19634 11.6645H7.507ZM16.5991 2.1572C16.7619 1.99448 17.0257 1.99448 17.1884 2.1572L17.7777 2.74645C17.9404 2.90917 17.9404 3.17299 17.7777 3.33571L14.4432 6.66904H16.8136C17.0437 6.66904 17.2302 6.85559 17.2302 7.08571V7.91904C17.2302 8.14916 17.0437 8.33571 16.8136 8.33571H12.5029C12.2728 8.33571 12.0644 8.24243 11.9136 8.09163C11.7628 7.94082 11.6696 7.73249 11.6696 7.50237V3.20257C11.6696 2.97245 11.8561 2.7859 12.0862 2.7859H12.9196C13.1497 2.7859 13.3362 2.97245 13.3362 3.20257V5.419L16.5991 2.1572Z"
fill="rgb(100, 106, 115)"
/>
</svg>
</div>
<div class="maxkb-openviewport" :class="{ 'maxkb-viewportnone': isFullscreen }" @click="toggleFullscreen">
<!-- 放大图标 -->
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M7.15209 11.5968C7.31481 11.4341 7.57862 11.4341 7.74134 11.5968L8.3306 12.186C8.49332 12.3487 8.49332 12.6126 8.3306 12.7753L4.99615 16.1086H7.3665C7.59662 16.1086 7.78316 16.2952 7.78316 16.5253V17.3586C7.78316 17.5887 7.59662 17.7753 7.3665 17.7753H3.05584C2.82572 17.7753 2.61738 17.682 2.46658 17.5312C2.31578 17.3804 2.2225 17.1721 2.2225 16.9419V12.6421C2.2225 12.412 2.40905 12.2255 2.63917 12.2255H3.4725C3.70262 12.2255 3.88917 12.412 3.88917 12.6421V14.8586L7.15209 11.5968ZM16.937 2.22217C17.1671 2.22217 17.3754 2.31544 17.5262 2.46625C17.677 2.61705 17.7703 2.82538 17.7703 3.0555V7.35531C17.7703 7.58543 17.5837 7.77198 17.3536 7.77198H16.5203C16.2902 7.77198 16.1036 7.58543 16.1036 7.35531V5.13888L12.8407 8.40068C12.678 8.5634 12.4142 8.5634 12.2515 8.40068L11.6622 7.81142C11.4995 7.64871 11.4995 7.38489 11.6622 7.22217L14.9966 3.88883H12.6263C12.3962 3.88883 12.2096 3.70229 12.2096 3.47217V2.63883C12.2096 2.40872 12.3962 2.22217 12.6263 2.22217H16.937Z"
fill="rgb(100, 106, 115)"
/>
</svg>
</div>
<div class="maxkb-chat-close" @click="toggleChat">
<!-- 关闭图标 -->
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z"
fill="rgb(100, 106, 115)"
/>
</svg>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
defineOptions({
name: "MaxKBChat",
});
const props = defineProps<{
url?: string;
showGuideOnInit?: boolean;
}>();
// 响应式状态
const showGuide = ref(false);
const chatVisible = ref(false);
const isFullscreen = ref(false);
const buttonPos = ref({ x: 16, y: 30 });
const buttonSize = ref({ width: 64, height: 64 });
// 计算属性
const chatUrl = computed(() => {
return props.url || "https://maxkb.handfree.work/ui/chat/326a8651825e8a02?mode=embed";
});
const contentStyle = computed(() => ({
width: `${buttonSize.value.width}px`,
height: `${buttonSize.value.height}px`,
}));
const tipsStyle = computed(() => ({
marginRight: `${Math.min(buttonSize.value.width, 500) - 64}px`,
}));
const containerStyle = computed(() => ({
display: chatVisible.value ? "block" : "none",
right: `${buttonPos.value.x}px`,
bottom: `${buttonPos.value.y}px`,
}));
// 方法
const closeGuide = () => {
showGuide.value = false;
localStorage.setItem("maxkbMaskTip", "true");
};
const toggleChat = () => {
chatVisible.value = !chatVisible.value;
};
const toggleFullscreen = () => {
isFullscreen.value = !isFullscreen.value;
};
// 初始化
onMounted(() => {
if (props.showGuideOnInit && !localStorage.getItem("maxkbMaskTip")) {
showGuide.value = true;
}
});
async function openChat(req: { q: string }) {
if (!req.q) {
return;
}
chatVisible.value = true;
const iframeId = "maxkb-chat";
const iframe = document.getElementById(iframeId) as HTMLIFrameElement | null;
if (!iframe) {
throw new Error("iframe not found");
return;
}
iframe.contentWindow?.postMessage(
{
...req,
from: "certd",
},
"*"
);
}
defineExpose({
openChat,
});
</script>
<style scoped>
/* 这里放置所有样式(保持原有样式不变) */
/* 注意将原样式中的 #maxkb 选择器改为 #maxkb-container */
/* 示例: */
/* 其他样式保持原样... */
/* 放大 */
#maxkb .maxkb-enlarge {
width: 50% !important;
height: 100% !important;
bottom: 0 !important;
right: 0 !important;
}
@media only screen and (max-width: 768px) {
#maxkb .maxkb-enlarge {
width: 100% !important;
height: 100% !important;
right: 0 !important;
bottom: 0 !important;
}
}
/* 引导 */
#maxkb .maxkb-mask {
position: fixed;
z-index: 10001;
background-color: transparent;
height: 100%;
width: 100%;
top: 0;
left: 0;
}
#maxkb .maxkb-mask .maxkb-content {
width: 64px;
height: 64px;
box-shadow: 1px 1px 1px 9999px rgba(0, 0, 0, 0.6);
position: absolute;
right: 0px;
bottom: 30px;
z-index: 10001;
}
#maxkb .maxkb-tips {
position: fixed;
right: calc(0px + 75px);
bottom: calc(30px + 0px);
padding: 22px 24px 24px;
border-radius: 6px;
color: #ffffff;
font-size: 14px;
background: #3370ff;
z-index: 10001;
}
#maxkb .maxkb-tips .maxkb-arrow {
position: absolute;
background: #3370ff;
width: 10px;
height: 10px;
pointer-events: none;
transform: rotate(45deg);
box-sizing: border-box;
/* left */
right: -5px;
bottom: 33px;
border-left-color: transparent;
border-bottom-color: transparent;
}
#maxkb .maxkb-tips .maxkb-title {
font-size: 20px;
font-weight: 500;
margin-bottom: 8px;
}
#maxkb .maxkb-tips .maxkb-button {
text-align: right;
margin-top: 24px;
}
#maxkb .maxkb-tips .maxkb-button button {
border-radius: 4px;
background: #fff;
padding: 3px 12px;
color: #3370ff;
cursor: pointer;
outline: none;
border: none;
}
#maxkb .maxkb-tips .maxkb-button button::after {
border: none;
}
#maxkb .maxkb-tips {
position: absolute;
right: 20px;
//top: 20px;
cursor: pointer;
}
#maxkb-chat-container {
width: 450px;
height: 600px;
display: none;
}
@media only screen and (max-width: 768px) {
#maxkb-chat-container {
width: 100%;
height: 70%;
right: 0 !important;
}
}
#maxkb .maxkb-chat-button {
position: fixed;
right: 10px;
bottom: 30px;
cursor: pointer;
z-index: 10000;
}
#maxkb #maxkb-chat-container {
z-index: 10000;
position: relative;
border-radius: 8px;
border: 1px solid #ffffff;
background: linear-gradient(188deg, rgba(235, 241, 255, 0.2) 39.6%, rgba(231, 249, 255, 0.2) 94.3%), #eff0f1;
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.1);
position: fixed;
bottom: 16px;
right: 16px;
overflow: hidden;
}
#maxkb #maxkb-chat-container .maxkb-operate {
top: 18px;
right: 15px;
position: absolute;
display: flex;
align-items: center;
line-height: 18px;
}
#maxkb #maxkb-chat-container .maxkb-operate .maxkb-chat-close {
margin-left: 15px;
cursor: pointer;
}
#maxkb #maxkb-chat-container .maxkb-operate .maxkb-openviewport {
cursor: pointer;
}
#maxkb #maxkb-chat-container .maxkb-operate .maxkb-closeviewport {
cursor: pointer;
}
#maxkb #maxkb-chat-container .maxkb-viewportnone {
display: none;
}
#maxkb #maxkb-chat-container #maxkb-chat {
height: 100%;
width: 100%;
border: none;
}
#maxkb #maxkb-chat-container {
animation: appear 0.4s ease-in-out;
}
@keyframes appear {
from {
height: 0;
}
to {
height: 600px;
}
}
</style>

View File

@@ -19,6 +19,7 @@ export default {
"CodeEditor",
defineAsyncComponent(() => import("./code-editor/index.vue"))
);
app.component("PiContainer", PiContainer);
app.component("TextEditable", TextEditable);
app.component("FileInput", FileInput);

View File

@@ -18,7 +18,7 @@ const props = defineProps<{
modelValue: string;
title: string;
action: string;
form: any;
form?: any;
button?: any;
}>();

View File

@@ -15,8 +15,13 @@
<template v-if="sysPublic.icpNo">
<span>
<a href="https://beian.miit.gov.cn/" target="_blank">{{ sysPublic.icpNo }}</a>
<a-divider type="vertical" />
</span>
</template>
<span v-if="sysPublic.mpsNo">
<a href="http://www.beian.gov.cn/portal/registerSystemInfo" target="_blank">{{ sysPublic.mpsNo }}</a>
</span>
</div>
<div class="ml-5">v{{ version }}</div>
</div>
@@ -26,7 +31,7 @@ import { computed, onMounted, ref } from "vue";
import { useSettingStore } from "/@/store/settings";
defineOptions({
name: "PageFooter"
name: "PageFooter",
});
const version = ref(import.meta.env.VITE_APP_VERSION);

View File

@@ -20,8 +20,8 @@ const menus = computed(() => [
router.push("/certd/mine/user-profile");
},
icon: "fa-solid:book",
text: "账号信息"
}
text: "账号信息",
},
]);
const avatar = computed(() => {
@@ -42,7 +42,7 @@ const siteInfo = computed(() => {
return settingStore.siteInfo;
});
onErrorCaptured((e) => {
onErrorCaptured(e => {
console.error("ErrorCaptured:", e);
// notification.error({ message: e.message });
//阻止错误向上传递
@@ -52,6 +52,10 @@ onErrorCaptured((e) => {
onMounted(async () => {
await settingStore.checkUrlBound();
});
function goGithub() {
window.open("https://github.com/certd/certd");
}
</script>
<template>
@@ -63,12 +67,15 @@ onMounted(async () => {
<LockScreen :avatar @to-login="handleLogout" />
</template>
<template #header-right-0>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<tutorial-button v-if="!settingStore.isComm" class="flex-center header-btn" />
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<tutorial-button class="flex-center header-btn" />
</div>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<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">
<fs-button shape="circle" type="text" icon="ion:logo-github" :text="null" @click="goGithub" />
</div>
</template>
<template #footer>
<PageFooter></PageFooter>

View File

@@ -133,7 +133,7 @@ const asideCollapsed = ref(false);
function asideCollapsedToggle() {
asideCollapsed.value = !asideCollapsed.value;
}
onErrorCaptured((e) => {
onErrorCaptured(e => {
console.error("ErrorCaptured:", e);
// notification.error({ message: e.message });
//阻止错误向上传递

View File

@@ -31,6 +31,10 @@
<a-divider type="vertical" />
<a href="https://beian.miit.gov.cn/" target="_blank">{{ sysPublic.icpNo }}</a>
</span>
<span v-if="sysPublic.mpsNo">
<a-divider type="vertical" />
<a href="http://www.beian.gov.cn/portal/registerSystemInfo" target="_blank">{{ sysPublic.mpsNo }}</a>
</span>
</div>
</div>
</div>

View File

@@ -39,7 +39,9 @@ export type SysPublicSetting = {
limitUserPipelineCount?: number;
managerOtherUserPipeline?: boolean;
icpNo?: string;
mpsNo?: string;
robots?: boolean;
aiChatEnabled?: boolean;
};
export type SuiteSetting = {
enabled?: boolean;

View File

@@ -18,7 +18,7 @@ import { useLayout } from "./hooks/use-layout";
interface Props extends VbenLayoutProps {}
defineOptions({
name: "VbenLayout"
name: "VbenLayout",
});
const props = withDefaults(defineProps<Props>(), {
@@ -48,7 +48,7 @@ const props = withDefaults(defineProps<Props>(), {
sideCollapseWidth: 60,
tabbarEnable: true,
tabbarHeight: 40,
zIndex: 200
zIndex: 200,
});
const emit = defineEmits<{ sideMouseLeave: []; toggleSidebar: [] }>();
@@ -193,7 +193,7 @@ const mainStyle = computed(() => {
}
return {
sidebarAndExtraWidth,
width
width,
};
});
@@ -221,7 +221,7 @@ const tabbarStyle = computed((): CSSProperties => {
return {
marginLeft: `${marginLeft}px`,
width
width,
};
});
@@ -231,7 +231,7 @@ const contentStyle = computed((): CSSProperties => {
const { footerEnable, footerFixed, footerHeight } = props;
return {
marginTop: fixed && !isFullContent.value && !headerIsHidden.value && (!isHeaderAutoMode.value || scrollY.value < headerWrapperHeight.value) ? `${headerWrapperHeight.value}px` : 0,
paddingBottom: `${footerEnable && footerFixed ? footerHeight : 0}px`
paddingBottom: `${footerEnable && footerFixed ? footerHeight : 0}px`,
};
});
@@ -249,7 +249,7 @@ const headerWrapperStyle = computed((): CSSProperties => {
position: fixed ? "fixed" : "static",
top: headerIsHidden.value || isFullContent.value ? `-${headerWrapperHeight.value}px` : 0,
width: mainStyle.value.width,
"z-index": headerZIndex.value
"z-index": headerZIndex.value,
};
});
@@ -289,13 +289,13 @@ const showHeaderLogo = computed(() => {
watch(
() => props.isMobile,
(val) => {
val => {
if (val) {
sidebarCollapse.value = true;
}
},
{
immediate: true
immediate: true,
}
);
@@ -305,7 +305,7 @@ watch(
setLayoutHeaderHeight(isFullContent.value ? 0 : height);
},
{
immediate: true
immediate: true,
}
);
@@ -315,7 +315,7 @@ watch(
setLayoutFooterHeight(height);
},
{
immediate: true
immediate: true,
}
);
@@ -336,7 +336,7 @@ watch(
mouseMove();
},
{
immediate: true
immediate: true,
}
);
}
@@ -433,9 +433,9 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
<div
:class="[
{
'shadow-[0_16px_24px_hsl(var(--background))]': scrollY > 20
'shadow-[0_16px_24px_hsl(var(--background))]': scrollY > 20,
},
SCROLL_FIXED_CLASS
SCROLL_FIXED_CLASS,
]"
:style="headerWrapperStyle"
class="overflow-hidden transition-all duration-200"

View File

@@ -18,11 +18,11 @@ interface Props {
}
defineOptions({
name: "LayoutHeader"
name: "LayoutHeader",
});
withDefaults(defineProps<Props>(), {
theme: "light"
theme: "light",
});
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
@@ -39,42 +39,42 @@ const rightSlots = computed(() => {
if (preferences.widget.globalSearch) {
list.push({
index: REFERENCE_VALUE,
name: "global-search"
name: "global-search",
});
}
if (preferencesButtonPosition.value.header) {
list.push({
index: REFERENCE_VALUE + 10,
name: "preferences"
name: "preferences",
});
}
if (preferences.widget.themeToggle) {
list.push({
index: REFERENCE_VALUE + 20,
name: "theme-toggle"
name: "theme-toggle",
});
}
if (preferences.widget.languageToggle) {
list.push({
index: REFERENCE_VALUE + 30,
name: "language-toggle"
name: "language-toggle",
});
}
if (preferences.widget.fullscreen) {
list.push({
index: REFERENCE_VALUE + 40,
name: "fullscreen"
name: "fullscreen",
});
}
if (preferences.widget.notification) {
list.push({
index: REFERENCE_VALUE + 50,
name: "notification"
name: "notification",
});
}
Object.keys(slots).forEach((key) => {
Object.keys(slots).forEach(key => {
const name = key.split("-");
if (key.startsWith("header-right")) {
list.push({ index: Number(name[2]), name: key });
@@ -89,11 +89,11 @@ const leftSlots = computed(() => {
if (preferences.widget.refresh) {
list.push({
index: 0,
name: "refresh"
name: "refresh",
});
}
Object.keys(slots).forEach((key) => {
Object.keys(slots).forEach(key => {
const name = key.split("-");
if (key.startsWith("header-left")) {
list.push({ index: Number(name[2]), name: key });
@@ -108,7 +108,7 @@ function clearPreferencesAndLogout() {
</script>
<template>
<template v-for="slot in leftSlots.filter((item) => item.index < REFERENCE_VALUE)" :key="slot.name">
<template v-for="slot in leftSlots.filter(item => item.index < REFERENCE_VALUE)" :key="slot.name">
<slot :name="slot.name">
<template v-if="slot.name === 'refresh'">
<VbenIconButton class="my-0 mr-1 rounded-md" @click="refresh">
@@ -120,7 +120,7 @@ function clearPreferencesAndLogout() {
<div class="flex-center hidden lg:block">
<slot name="breadcrumb"></slot>
</div>
<template v-for="slot in leftSlots.filter((item) => item.index > REFERENCE_VALUE)" :key="slot.name">
<template v-for="slot in leftSlots.filter(item => item.index > REFERENCE_VALUE)" :key="slot.name">
<slot :name="slot.name"></slot>
</template>
<div :class="`menu-align-${preferences.header.menuAlign}`" class="flex h-full min-w-0 flex-1 items-center">

View File

@@ -9,15 +9,15 @@ import { preferences, updatePreferences } from "/@/vben/preferences";
import { VbenDropdownRadioMenu, VbenIconButton } from "/@/vben//shadcn-ui";
defineOptions({
name: "LanguageToggle"
name: "LanguageToggle",
});
async function handleUpdate(value: string) {
const locale = value as SupportedLanguagesType;
updatePreferences({
app: {
locale
}
locale,
},
});
await loadLocaleMessages(locale);
}

View File

@@ -11,11 +11,11 @@ interface Props {
}
defineOptions({
name: "ThemeToggleButton"
name: "ThemeToggleButton",
});
const props = withDefaults(defineProps<Props>(), {
type: "normal"
type: "normal",
});
const isDark = defineModel<boolean>();
@@ -29,13 +29,13 @@ const bindProps = computed(() => {
return type === "normal"
? {
variant: "heavy" as const
variant: "heavy" as const,
}
: {
class: "rounded-full",
size: "icon" as const,
style: { padding: "7px" },
variant: "icon" as const
variant: "icon" as const,
};
});
@@ -59,12 +59,12 @@ function toggleTheme(event: MouseEvent) {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
document.documentElement.animate(
{
clipPath: isDark.value ? [...clipPath].reverse() : clipPath
clipPath: isDark.value ? [...clipPath].reverse() : clipPath,
},
{
duration: 450,
easing: "ease-in",
pseudoElement: isDark.value ? "::view-transition-old(root)" : "::view-transition-new(root)"
pseudoElement: isDark.value ? "::view-transition-old(root)" : "::view-transition-new(root)",
}
);
});

View File

@@ -10,16 +10,16 @@ import { ToggleGroup, ToggleGroupItem, VbenTooltip } from "/@/vben//shadcn-ui";
import ThemeButton from "./theme-button.vue";
defineOptions({
name: "ThemeToggle"
name: "ThemeToggle",
});
withDefaults(defineProps<{ shouldOnHover?: boolean }>(), {
shouldOnHover: false
shouldOnHover: false,
});
function handleChange(isDark: boolean) {
updatePreferences({
theme: { mode: isDark ? "dark" : "light" }
theme: { mode: isDark ? "dark" : "light" },
});
}
@@ -29,18 +29,18 @@ const PRESETS = [
{
icon: Sun,
name: "light",
title: $t("preferences.theme.light")
title: $t("preferences.theme.light"),
},
{
icon: MoonStar,
name: "dark",
title: $t("preferences.theme.dark")
title: $t("preferences.theme.dark"),
},
{
icon: SunMoon,
name: "auto",
title: $t("preferences.followSystem")
}
title: $t("preferences.followSystem"),
},
];
</script>
<template>
@@ -49,7 +49,7 @@ const PRESETS = [
<template #trigger>
<ThemeButton :model-value="isDark" type="icon" @update:model-value="handleChange" />
</template>
<ToggleGroup :model-value="preferences.theme.mode" class="gap-2" type="single" variant="outline" @update:model-value="(val) => updatePreferences({ theme: { mode: val as ThemeModeType } })">
<ToggleGroup :model-value="preferences.theme.mode" class="gap-2" type="single" variant="outline" @update:model-value="val => updatePreferences({ theme: { mode: val as ThemeModeType } })">
<ToggleGroupItem v-for="item in PRESETS" :key="item.name" :value="item.name">
<component :is="item.icon" class="size-5" />
</ToggleGroupItem>

View File

@@ -35,7 +35,7 @@ interface Props {
/**
* 菜单数组
*/
menus?: Array<{ handler: AnyFunction; icon?: Component; text: string }>;
menus?: Array<{ handler: AnyFunction; icon?: Component | string; text: string }>;
/**
* 标签文本
@@ -52,7 +52,7 @@ interface Props {
}
defineOptions({
name: "UserDropdown"
name: "UserDropdown",
});
const props = withDefaults(defineProps<Props>(), {
@@ -64,7 +64,7 @@ const props = withDefaults(defineProps<Props>(), {
tagText: "",
text: "",
trigger: "click",
hoverDelay: 500
hoverDelay: 500,
});
const emit = defineEmits<{ logout: [] }>();
@@ -72,12 +72,12 @@ const emit = defineEmits<{ logout: [] }>();
const { globalLockScreenShortcutKey, globalLogoutShortcutKey } = usePreferences();
const lockStore = useLockStore();
const [LockModal, lockModalApi] = useVbenModal({
connectedComponent: LockScreenModal
connectedComponent: LockScreenModal,
});
const [LogoutModal, logoutModalApi] = useVbenModal({
onConfirm() {
handleSubmitLogout();
}
},
});
const refTrigger = useTemplateRef("refTrigger");
@@ -86,7 +86,7 @@ const [openPopover, hoverWatcher] = useHoverToggle([refTrigger, refContent], ()
watch(
() => props.trigger === "hover" || props.trigger === "both",
(val) => {
val => {
if (val) {
hoverWatcher.enable();
} else {
@@ -94,7 +94,7 @@ watch(
}
},
{
immediate: true
immediate: true,
}
);

View File

@@ -18,7 +18,7 @@ const props = withDefaults(defineProps<Props>(), {
disabled: false,
loading: false,
size: "default",
variant: "default"
variant: "default",
});
const isDisabled = computed(() => {

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { ButtonVariants } from '../../ui';
import type { VbenButtonProps } from './button';
import type { ButtonVariants } from "../../ui";
import type { VbenButtonProps } from "./button";
import { computed, useSlots } from 'vue';
import { computed, useSlots } from "vue";
import { cn } from '/@/vben/shared/utils';
import { cn } from "/@/vben/shared/utils";
import { VbenTooltip } from '../tooltip';
import VbenButton from './button.vue';
import { VbenTooltip } from "../tooltip";
import VbenButton from "./button.vue";
interface Props extends VbenButtonProps {
class?: any;
@@ -15,7 +15,7 @@ interface Props extends VbenButtonProps {
onClick?: () => void;
tooltip?: string;
tooltipDelayDuration?: number;
tooltipSide?: 'bottom' | 'left' | 'right' | 'top';
tooltipSide?: "bottom" | "left" | "right" | "top";
variant?: ButtonVariants;
}
@@ -23,8 +23,8 @@ const props = withDefaults(defineProps<Props>(), {
disabled: false,
onClick: () => {},
tooltipDelayDuration: 200,
tooltipSide: 'bottom',
variant: 'icon',
tooltipSide: "bottom",
variant: "icon",
});
const slots = useSlots();
@@ -33,30 +33,13 @@ const showTooltip = computed(() => !!slots.tooltip || !!props.tooltip);
</script>
<template>
<VbenButton
v-if="!showTooltip"
:class="cn('rounded-full', props.class)"
:disabled="disabled"
:variant="variant"
size="icon"
@click="onClick"
>
<VbenButton v-if="!showTooltip" :class="cn('rounded-full', props.class)" :disabled="disabled" :variant="variant" size="icon" @click="onClick">
<slot></slot>
</VbenButton>
<VbenTooltip
v-else
:delay-duration="tooltipDelayDuration"
:side="tooltipSide"
>
<VbenTooltip v-else :delay-duration="tooltipDelayDuration" :side="tooltipSide">
<template #trigger>
<VbenButton
:class="cn('rounded-full', props.class)"
:disabled="disabled"
:variant="variant"
size="icon"
@click="onClick"
>
<VbenButton :class="cn('rounded-full', props.class)" :disabled="disabled" :variant="variant" size="icon" @click="onClick">
<slot></slot>
</VbenButton>
</template>

View File

@@ -11,13 +11,13 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
return "access";
});
const AccessTypeDictRef = dict({
url: "/pi/access/accessTypeDict"
url: "/pi/access/accessTypeDict",
});
const defaultPluginConfig = {
component: {
name: "a-input",
vModel: "value"
}
vModel: "value",
},
};
function buildDefineFields(define: any, form: any, mode: string) {
@@ -34,7 +34,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
const key = "access." + mapKey;
const field = {
...value,
key
key,
};
const column = merge({ title: key }, defaultPluginConfig, field);
@@ -77,13 +77,13 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
type: "dict-select",
dict: AccessTypeDictRef,
search: {
show: true
show: true,
},
column: {
width: 200,
component: {
color: "auto"
}
color: "auto",
},
},
form: {
component: {
@@ -100,7 +100,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
{item.label}
</span>
);
}
},
},
rules: [{ required: true, message: "请选择类型" }],
valueChange: {
@@ -116,7 +116,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
form.access = {};
}
buildDefineFields(define, form, mode);
}
},
},
helper: {
render: () => {
@@ -125,12 +125,12 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
return "";
}
return <div innerHTML={utils.transformLink(define.desc)}></div>;
}
}
},
},
},
addForm: {
value: typeRef
}
value: typeRef,
},
} as ColumnCompositionProps,
setting: {
column: { show: false },
@@ -149,8 +149,8 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
valueResolve({ form }) {
const setting = form.access;
form.setting = JSON.stringify(setting);
}
}
} as ColumnCompositionProps
},
},
} as ColumnCompositionProps,
};
}

View File

@@ -21,7 +21,7 @@ async function batchUpdateGroupRequest(groupId: number) {
const pipelineGroupDictRef = dict({
url: "/pi/pipeline/group/all",
value: "id",
label: "name"
label: "name",
});
const { openCrudFormDialog } = useFormWrapper();
@@ -33,9 +33,9 @@ async function openGroupSelectDialog() {
type: "dict-select",
dict: pipelineGroupDictRef,
form: {
rules: [{ required: true, message: "请选择分组" }]
}
}
rules: [{ required: true, message: "请选择分组" }],
},
},
},
form: {
mode: "edit",
@@ -44,18 +44,18 @@ async function openGroupSelectDialog() {
await batchUpdateGroupRequest(form.groupId);
},
col: {
span: 22
span: 22,
},
labelCol: {
style: {
width: "100px"
}
width: "100px",
},
},
wrapper: {
title: "批量修改分组",
width: 600
}
}
width: 600,
},
},
} as any;
await openCrudFormDialog({ crudOptions });
}

View File

@@ -22,6 +22,11 @@
</div>
</a-tab-pane>
</a-tabs>
<template #footer>
<fs-button key="aiChat" type="primary" icon="ion:color-wand-outline" @click="taskModal.onAiChat">AI分析</fs-button>
<fs-button key="cancel" icon="ion:close-circle-outline" @click="taskModal.onOk">关闭</fs-button>
<fs-button key="submit" icon="ion:checkmark-circle-outline" type="primary" @click="taskModal.onOk">确定</fs-button>
</template>
</a-modal>
</template>
@@ -37,11 +42,15 @@ export default {
props: {},
emits: ["run"],
setup(props: any, ctx: any) {
const openAiChat: any = inject("fn:ai.open", (q: string) => {});
const taskModal = ref({
open: false,
onOk() {
taskViewClose();
},
onAiChat() {
onAiChat();
},
cancelText: "关闭",
});
const { isMobile } = usePreferences();
@@ -52,6 +61,24 @@ export default {
return "left";
});
function onAiChat() {
const logs = currentHistory.value?.logs[activeKey.value];
if (!logs || logs.length === 0) {
return;
}
let logText = "";
for (let log of logs) {
logText += log + "\n";
}
const maxLength = 5000;
if (logText.length > maxLength) {
logText = logText.substring(logText.length - maxLength);
}
if (openAiChat) {
openAiChat(logText);
}
}
const detail = ref({ nodes: [] });
const activeKey = ref();
const currentHistory: Ref<RunHistory> | undefined = inject("currentHistory");

View File

@@ -255,7 +255,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, provide, Ref, ref, watch } from "vue";
import { defineComponent, onMounted, onUnmounted, provide, Ref, ref, watch } from "vue";
import { useRouter } from "vue-router";
import PiTaskForm from "./component/task-form/index.vue";
import PiTriggerForm from "./component/trigger-form/index.vue";
@@ -274,6 +274,7 @@ import { useSettingStore } from "/@/store/settings";
import { useUserStore } from "/@/store/user";
import TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
import { eachSteps, findStep } from "../utils";
import { PluginGroups } from "/@/store/plugin";
export default defineComponent({
name: "PipelineEdit",
@@ -365,9 +366,14 @@ export default defineComponent({
return true;
}
const intervalLoadHistoryRef = ref();
const isLoadingHistory = ref(false);
function watchNewHistoryList() {
intervalLoadHistoryRef.value = setTimeout(async () => {
intervalLoadHistoryRef.value = setInterval(async () => {
if (isLoadingHistory.value) {
return;
}
try {
isLoadingHistory.value = true;
if (currentHistory.value == null) {
await loadHistoryList();
}
@@ -375,16 +381,21 @@ export default defineComponent({
if (currentHistory.value != null) {
if (currentHistory.value.pipeline?.status?.status === "start") {
await loadCurrentHistoryDetail();
} else {
return;
}
}
} catch (e) {
console.error(e);
} finally {
isLoadingHistory.value = false;
}
watchNewHistoryList();
}, 3000);
}
onMounted(() => {
watchNewHistoryList();
});
onUnmounted(() => {
clearInterval(intervalLoadHistoryRef.value);
});
watch(
() => {
@@ -632,7 +643,6 @@ export default defineComponent({
async onOk() {
//@ts-ignore
await changeCurrentHistory(null);
watchNewHistoryList();
await props.options.doTrigger({ pipelineId: pipeline.value.id, stepId: stepId });
notification.success({ message: "管道已经开始运行" });
},
@@ -1081,7 +1091,7 @@ export default defineComponent({
}
.layout-right {
position: "relative";
position: relative;
&.collapsed {
margin-right: -350px;
}

View File

@@ -9,7 +9,10 @@
<a-col :span="6">
<statistic-card title="用户总数" :count="count.userCount">
<template #footer>
<router-link to="/sys/authority/user" class="flex"><fs-icon icon="ion:settings-outline" class="mr-5 fs-16" /> 管理用户</router-link>
<router-link to="/sys/authority/user" class="flex">
<fs-icon icon="ion:settings-outline" class="mr-5 fs-16" />
管理用户
</router-link>
</template>
</statistic-card>
</a-col>
@@ -21,7 +24,10 @@
<a-col :span="6">
<statistic-card title="全站流水线总数" :count="count.pipelineCount">
<template #footer>
<router-link to="/certd/pipeline" class="flex"><fs-icon icon="ion:settings-outline" class="mr-5 fs-16" /> 管理流水线</router-link>
<router-link to="/certd/pipeline" class="flex">
<fs-icon icon="ion:settings-outline" class="mr-5 fs-16" />
管理流水线
</router-link>
</template>
</statistic-card>
</a-col>
@@ -42,21 +48,23 @@
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { onMounted, ref, Ref } from "vue";
import { FsIcon } from "@fast-crud/fast-crud";
import StatisticCard from "/@/views/framework/home/dashboard/statistic-card.vue";
import DayCount from "/@/views/framework/home/dashboard/charts/day-count.vue";
import { GetStatisticCount } from "./api";
const count = ref({});
function transformCountPerDayToChartData(key) {
count.value[key] = count.value[key].map((item) => {
const count: Ref = ref({});
function transformCountPerDayToChartData(key: string) {
count.value[key] = count.value[key].map((item:any) => {
return {
name: item.date,
value: item.count
value: item.count,
};
});
}
async function loadCount() {
count.value = await GetStatisticCount();
transformCountPerDayToChartData("userRegisterCountPerDay");

View File

@@ -4,7 +4,13 @@
<a-form-item label="ICP备案号" :name="['public', 'icpNo']">
<a-input v-model:value="formState.public.icpNo" placeholder="粤ICP备xxxxxxx号" />
</a-form-item>
<a-form-item label="网安备案号" :name="['public', 'mpsNo']">
<a-input v-model:value="formState.public.mpsNo" placeholder="京公网安备xxxxxxx号" />
</a-form-item>
<a-form-item label="开启小助手" :name="['public', 'aiChatEnabled']">
<a-switch v-model:checked="formState.public.aiChatEnabled" />
</a-form-item>
<a-form-item label="允许爬虫" :name="['public', 'robots']">
<a-switch v-model:checked="formState.public.robots" />
</a-form-item>
@@ -53,19 +59,20 @@ import { notification } from "ant-design-vue";
import { util } from "/@/utils";
defineOptions({
name: "SettingBase"
name: "SettingBase",
});
const formState = reactive<Partial<SysSettings>>({
public: {
icpNo: ""
icpNo: "",
mpsNo: "",
},
private: {}
private: {},
});
const urlRules = ref({
type: "url",
message: "请输入正确的URL"
message: "请输入正确的URL",
});
async function loadSysSettings() {
@@ -82,7 +89,7 @@ const onFinish = async (form: any) => {
await api.SysSettingsSave(form);
await settingsStore.loadSysSettings();
notification.success({
message: "保存成功"
message: "保存成功",
});
} finally {
saveLoading.value = false;
@@ -96,7 +103,7 @@ const onFinishFailed = (errorInfo: any) => {
async function stopOtherUserTimer() {
await api.stopOtherUserTimer();
notification.success({
message: "停止成功"
message: "停止成功",
});
}
@@ -120,13 +127,13 @@ async function testProxy() {
if (!success) {
notification.error({
message: "测试失败",
description: content
description: content,
});
return;
}
notification.success({
message: "测试完成",
description: content
description: content,
});
} finally {
testProxyLoading.value = false;

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.3](https://github.com/certd/certd/compare/v1.34.2...v1.34.3) (2025-05-15)
### Performance Improvements
* 添加 FlexCDN 更新证书插件 ([bf040d4](https://github.com/certd/certd/commit/bf040d4c428d29c06fbaca5e29100e0c583b2b0b))
## [1.34.2](https://github.com/certd/certd/compare/v1.34.1...v1.34.2) (2025-05-11)
### Bug Fixes
* 修复部署到又拍云强制https无效的bug ([2397097](https://github.com/certd/certd/commit/2397097e4ddcb6f593210598e8779ffd44ac3f8f))
## [1.34.1](https://github.com/certd/certd/compare/v1.34.0...v1.34.1) (2025-05-05)
### Bug Fixes

View File

@@ -0,0 +1,107 @@
import "./dist/plugins/index.js";
import { accessRegistry, notificationRegistry, pluginGroups, pluginRegistry } from "@certd/pipeline";
import { dnsProviderRegistry } from "@certd/plugin-cert";
import fs from "fs";
function genPluginMd() {
const plugins = {
access: [],
deploy: [],
dnsProvider: [],
notification: []
};
plugins.access = accessRegistry.getDefineList();
plugins.deploy = pluginRegistry.getDefineList();
plugins.dnsProvider = dnsProviderRegistry.getDefineList();
plugins.notification = notificationRegistry.getDefineList();
// function genMd(list) {
// let mdContent = `<table style='width:100%'>
// <thead style='width:100%'>
// <tr >
// <th width='70'>序号</th><th width='265'>名称</th><th>说明</th>
// </tr>
// </thead>
// <tbody>
// `;
// let i = 0;
// for (const x of list) {
// i++
// mdContent += `<tr> <td>${i}.</td> <td style='font-weight: bold'>${x.title}</td> <td>${x.desc||''}</td> </tr>`;
// }
// mdContent += `</tbody></table>`;
// return mdContent;
// }
// function genMd(list) {
// let mdContent = ``;
// let i = 0;
// for (const x of list) {
// i++
// mdContent += `${i}. **${x.title}** \n${x.desc||''} \n\n\n`;
// }
// return mdContent;
// }
function genMd(list) {
let mdContent = `
| 序号 | 名称 | 说明 |
|-----|-----|-----|
`;
let i = 0;
for (const x of list) {
i++
mdContent += `| ${i}.| **${x.title}** | ${x.desc||''} | \n`;
}
return mdContent;
}
function addTableStyle(){
return `
<style module>
table th:first-of-type {
width: 65px;
}
table th:nth-of-type(2) {
width: 240px;
}
</style>
`
}
let mdContent = "";
mdContent = "# 授权列表\n";
mdContent += genMd(plugins.access);
mdContent += addTableStyle()
fs.writeFileSync("../../../docs/guide/plugins/access.md", mdContent);
mdContent = "# DNS提供商\n";
mdContent += genMd(plugins.dnsProvider);
mdContent += addTableStyle()
fs.writeFileSync("../../../docs/guide/plugins/dns-provider.md", mdContent);
mdContent = "# 通知插件\n";
mdContent += genMd(plugins.notification);
mdContent += addTableStyle()
fs.writeFileSync("../../../docs/guide/plugins/notification.md", mdContent);
mdContent = "# 任务插件\n";
mdContent += `\`${plugins.deploy.length}\` 款任务插件 \n`
let index =0
for (const key in pluginGroups) {
index++
const group = pluginGroups[key];
mdContent += `## ${index}. ${group.title}\n`;
mdContent += genMd(group.plugins);
}
mdContent += addTableStyle()
fs.writeFileSync("../../../docs/guide/plugins/deploy.md", mdContent);
process.exit()
}
genPluginMd()

View File

@@ -50,49 +50,52 @@ export default async function loadModules(dir) {
function isPrototypeOf(value,cls){
return cls.prototype.isPrototypeOf(value.prototype)
}
async function genMetadata(){
const modules = await loadModules('./dist/plugins');
const modules = await loadModules('./dist/plugins');
fs.rmSync("./metadata", { recursive: true });
fs.mkdirSync("./metadata", { recursive: true });
for (const key in modules) {
console.log(key)
const module = modules[key]
const entry = Object.entries(module)
for (const [name, value] of entry) {
//如果有define属性
if(value.define){
//那么就是插件
let location = key.substring(4)
location = location.substring(0, location.length - 3)
location = location.replaceAll("\\","/")
location += ".js"
location = `../../..${location}` // 从modules/plugin/plugin-service 加载 ../../plugins目录下的文件
fs.rmSync("./metadata", { recursive: true });
fs.mkdirSync("./metadata", { recursive: true });
for (const key in modules) {
console.log(key)
const module = modules[key]
const entry = Object.entries(module)
for (const [name, value] of entry) {
//如果有define属性
if(value.define){
//那么就是插件
let location = key.substring(4)
location = location.substring(0, location.length - 3)
location = location.replaceAll("\\","/")
location += ".js"
location = `../../..${location}` // 从modules/plugin/plugin-service 加载 ../../plugins目录下的文件
const pluginDefine = {
...value.define
}
pluginDefine.type = "builtIn"
if(pluginDefine.accessType){
pluginDefine.pluginType = "dnsProvider"
}else if(isPrototypeOf(value,AbstractTaskPlugin)){
pluginDefine.pluginType = "deploy"
}else if(isPrototypeOf(value,BaseNotification)){
pluginDefine.pluginType = "notification"
}else if(isPrototypeOf(value,BaseAccess)){
pluginDefine.pluginType = "access"
}else{
console.log(`[warning] 未知的插件类型:${pluginDefine.name}`)
}
const pluginDefine = {
...value.define
const filePath = path.join(`./metadata/${pluginDefine.pluginType}_${pluginDefine.name}.yaml`)
pluginDefine.scriptFilePath = location
const data = yaml.dump(pluginDefine)
fs.writeFileSync(filePath,data ,'utf8')
}
pluginDefine.type = "builtIn"
if(pluginDefine.accessType){
pluginDefine.pluginType = "dnsProvider"
}else if(isPrototypeOf(value,AbstractTaskPlugin)){
pluginDefine.pluginType = "deploy"
}else if(isPrototypeOf(value,BaseNotification)){
pluginDefine.pluginType = "notification"
}else if(isPrototypeOf(value,BaseAccess)){
pluginDefine.pluginType = "access"
}else{
console.log(`[warning] 未知的插件类型:${pluginDefine.name}`)
}
const filePath = path.join(`./metadata/${pluginDefine.pluginType}_${pluginDefine.name}.yaml`)
pluginDefine.scriptFilePath = location
const data = yaml.dump(pluginDefine)
fs.writeFileSync(filePath,data ,'utf8')
}
}
process.exit()
}
// import why from 'why-is-node-running'
// setTimeout(() => why(), 100); // 延迟打印原因
genMetadata()
process.exit()

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.34.1",
"version": "1.34.3",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -22,13 +22,15 @@
"ci": "npm run cov",
"build": "mwtsc --cleanOutDir --skipLibCheck",
"export-metadata": "node export-plugin-yaml.js",
"export-md": "node export-plugin-md.js",
"dev-build": "echo 1",
"build-on-docker": "node ./before-build.js && npm run build",
"up-mw-deps": "npx midway-version -u -w",
"heap": "cross-env NODE_ENV=local clinic heapprofiler -- node ./bootstrap.js",
"flame": "clinic flame -- node ./bootstrap.js",
"tsc": "tsc --skipLibCheck",
"slimming": "node ./slimming.js"
"slimming": "node ./slimming.js",
"pub": "echo 1"
},
"dependencies": {
"@alicloud/fc20230330": "^4.1.7",
@@ -39,19 +41,19 @@
"@aws-sdk/client-acm": "^3.699.0",
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.34.1",
"@certd/basic": "^1.34.1",
"@certd/commercial-core": "^1.34.1",
"@certd/jdcloud": "^1.34.1",
"@certd/lib-huawei": "^1.34.1",
"@certd/lib-k8s": "^1.34.1",
"@certd/lib-server": "^1.34.1",
"@certd/midway-flyway-js": "^1.34.1",
"@certd/pipeline": "^1.34.1",
"@certd/plugin-cert": "^1.34.1",
"@certd/plugin-lib": "^1.34.1",
"@certd/plugin-plus": "^1.34.1",
"@certd/plus-core": "^1.34.1",
"@certd/acme-client": "^1.34.3",
"@certd/basic": "^1.34.3",
"@certd/commercial-core": "^1.34.3",
"@certd/jdcloud": "^1.34.3",
"@certd/lib-huawei": "^1.34.3",
"@certd/lib-k8s": "^1.34.3",
"@certd/lib-server": "^1.34.3",
"@certd/midway-flyway-js": "^1.34.3",
"@certd/pipeline": "^1.34.3",
"@certd/plugin-cert": "^1.34.3",
"@certd/plugin-lib": "^1.34.3",
"@certd/plugin-plus": "^1.34.3",
"@certd/plus-core": "^1.34.3",
"@corsinvest/cv4pve-api-javascript": "^8.3.0",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",

View File

@@ -1,16 +1,16 @@
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, PageReq} from "@certd/lib-server";
import {PluginEntity} from "../entity/plugin.js";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {Repository} from "typeorm";
import {isComm} from "@certd/plus-core";
import {BuiltInPluginService} from "../../pipeline/service/builtin-plugin-service.js";
import {merge} from "lodash-es";
import {accessRegistry, notificationRegistry, pluginRegistry} from "@certd/pipeline";
import {dnsProviderRegistry} from "@certd/plugin-cert";
import {logger} from "@certd/basic";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, PageReq } from "@certd/lib-server";
import { PluginEntity } from "../entity/plugin.js";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { isComm } from "@certd/plus-core";
import { BuiltInPluginService } from "../../pipeline/service/builtin-plugin-service.js";
import { merge } from "lodash-es";
import { accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline";
import { dnsProviderRegistry } from "@certd/plugin-cert";
import { logger } from "@certd/basic";
import yaml from "js-yaml";
import {getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin} from "./default-plugin.js";
import { getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin } from "./default-plugin.js";
import fs from "fs";
import path from "path";

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