mirror of
https://github.com/certd/certd.git
synced 2026-04-07 08:20:54 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f81305232 | ||
|
|
79bc22d8ce | ||
|
|
1c634a702a | ||
|
|
909a9e4050 | ||
|
|
b5cc794061 | ||
|
|
73b8e85976 | ||
|
|
282b5d6893 | ||
|
|
c6628e7311 | ||
|
|
6b109d172f | ||
|
|
6b29972399 | ||
|
|
0fcd3c09fd | ||
|
|
af503442b8 | ||
|
|
c875971b71 | ||
|
|
d1a65922d7 | ||
|
|
6ef34f95d5 | ||
|
|
8b79022179 | ||
|
|
21aec77e5c | ||
|
|
74c5259af8 | ||
|
|
a3e7d4414d | ||
|
|
986d32eb81 | ||
|
|
de0ae14544 | ||
|
|
6b52276fb6 | ||
|
|
a19ea7489c | ||
|
|
14229c2f00 | ||
|
|
6eb20a1f2e | ||
|
|
8debac2edf | ||
|
|
a68301e4dc | ||
|
|
c6a988bc92 | ||
|
|
fe02ce7b64 | ||
|
|
df012dec90 | ||
|
|
5969425a6f | ||
|
|
b17b1e6463 | ||
|
|
c99e61c402 | ||
|
|
f4aaec8b3c |
@@ -163,6 +163,16 @@ async doRequest(req: { action: string, data?: any }) {
|
||||
}
|
||||
```
|
||||
|
||||
--- 开发技巧:实现统一的 API 请求封装
|
||||
|
||||
**好处:**
|
||||
- **代码复用**:避免在每个 API 方法中重复编写相同的 header 设置和错误处理逻辑
|
||||
- **错误处理一致**:统一捕获和处理各种错误情况,确保错误信息格式统一
|
||||
- **日志记录完善**:集中记录详细的错误信息,便于调试和问题排查
|
||||
- **接口调用简化**:调用方只需关注业务逻辑,无需关心底层请求细节
|
||||
- **易于维护**:统一修改 API 调用方式时,只需修改一处代码
|
||||
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **插件命名**:插件名称应简洁明了,反映其功能。
|
||||
@@ -170,9 +180,12 @@ async doRequest(req: { action: string, data?: any }) {
|
||||
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`。
|
||||
4. **错误处理**:API 调用失败时应抛出明确的错误信息。
|
||||
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
|
||||
6. **统一接口调用方法**:封装统一的 API 请求方法,避免在每个 API 方法调用中重复编写错误处理逻辑。
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例 1: 通用授权插件
|
||||
|
||||
```typescript
|
||||
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||
import { DomainRecord } from '@certd/plugin-lib';
|
||||
|
||||
@@ -6,9 +6,8 @@ Access:存储用户的第三放应用的授权数据,比如用户名密码
|
||||
Task: 部署任务插件,它继承AbstractTaskPlugin类,被流水线调用execute方法,将证书部署到对应的应用上
|
||||
DnsProvider: DNS提供商插件,它用于在ACME申请证书时给域名添加txt解析记录。
|
||||
|
||||
在开始工作前,请阅读并加载.trae/skills下面的技能,根据skills进行相应的插件开发
|
||||
当开发过程中遇到问题,需要参考plugins目录下的其他插件,或者用户提醒你更好的做法时,你需要总结经验,更新相应的skills,让skills越来越完善,能够在以后得新插件开发中具备指导意义。
|
||||
|
||||
一般调用的api接口文档会比较复杂,你不知道接口是什么时,请务必询问用户,让用户提供API接口文档
|
||||
|
||||
完成开发后无需测试,通知用户自己去测试
|
||||
注意事项:
|
||||
1、使用技能:在开始工作前,请阅读并加载.trae/skills下面的技能,根据skills进行相应的插件开发
|
||||
2、迭代技能:当开发过程用户提醒你更好的做法时,你需要总结经验,更新相应的skills,让skills越来越完善,能够在以后得新插件开发中具备指导意义。
|
||||
3、一般调用的api接口文档会比较复杂,你不知道接口是什么时,请务必询问用户,让用户提供API接口文档
|
||||
4、完成开发后无需测试,通知用户自己去测试
|
||||
@@ -126,6 +126,8 @@ if (isDev()) {
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例:通用 DNS Provider
|
||||
|
||||
```typescript
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import { DemoAccess } from './access.js';
|
||||
|
||||
@@ -89,16 +89,55 @@ certDomains!: string[];
|
||||
accessId!: string;
|
||||
```
|
||||
|
||||
### 4. 实现插件方法
|
||||
### 4. 动态显隐配置(mergeScript)
|
||||
|
||||
#### 4.1 插件实例化时执行的方法
|
||||
使用 `mergeScript` 可以实现根据其他输入值动态控制当前输入项的显隐状态。
|
||||
|
||||
```typescript
|
||||
@TaskInput({
|
||||
title: '匹配模式',
|
||||
component: {
|
||||
name: 'select',
|
||||
options: [
|
||||
{ label: '手动选择', value: 'manual' },
|
||||
{ label: '根据证书匹配', value: 'auto' },
|
||||
],
|
||||
},
|
||||
default: 'manual',
|
||||
})
|
||||
domainMatchMode!: 'manual' | 'auto';
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: 'DCDN加速域名',
|
||||
helper: '你在阿里云上配置的DCDN加速域名',
|
||||
action: DeployCertToAliyunDCDN.prototype.onGetDomainList.name,
|
||||
watches: ['certDomains', 'accessId'],
|
||||
required: true,
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return domainMatchMode === "manual"
|
||||
})
|
||||
}
|
||||
`,
|
||||
})
|
||||
)
|
||||
domainName!: string | string[];
|
||||
```
|
||||
|
||||
`mergeScript` 中的 `ctx.compute` 函数接收一个回调函数,通过 `form` 参数可以访问表单中的其他字段值。
|
||||
|
||||
### 5. 实现插件方法
|
||||
|
||||
#### 5.1 插件实例化时执行的方法
|
||||
|
||||
```typescript
|
||||
// 插件实例化时执行的方法
|
||||
async onInstance() {}
|
||||
```
|
||||
|
||||
#### 4.2 插件执行方法
|
||||
#### 5.2 插件执行方法
|
||||
|
||||
```typescript
|
||||
// 插件执行方法
|
||||
@@ -130,7 +169,9 @@ async execute(): Promise<void> {
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 后端获取选项方法
|
||||
#### 5.3 后端获取选项方法
|
||||
|
||||
使用 `createRemoteSelectInputDefine` 创建远程选择输入项,`action` 指向的方法接收 `PageSearch` 参数并返回 `{ list, total }` 格式。
|
||||
|
||||
```typescript
|
||||
@TaskInput(
|
||||
@@ -145,8 +186,8 @@ async execute(): Promise<void> {
|
||||
)
|
||||
siteName!: string | string[];
|
||||
|
||||
// 从后端获取选项的方法
|
||||
async onGetSiteList(req: PageSearch) {
|
||||
// 从后端获取选项的方法,接收PageSearch参数
|
||||
async onGetSiteList(data: PageSearch) {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择Access授权');
|
||||
}
|
||||
@@ -154,7 +195,7 @@ async onGetSiteList(req: PageSearch) {
|
||||
// @ts-ignore
|
||||
const access = await this.getAccess(this.accessId);
|
||||
|
||||
// const siteRes = await access.GetDomainList(req);
|
||||
// const siteRes = await access.GetDomainList(data);
|
||||
// 以下是模拟数据
|
||||
const siteRes = [
|
||||
{ id: 1, siteName: 'site1.com' },
|
||||
@@ -169,8 +210,12 @@ async onGetSiteList(req: PageSearch) {
|
||||
domain: item.siteName,
|
||||
};
|
||||
});
|
||||
// 将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
|
||||
return optionsUtils.buildGroupOptions(options, this.certDomains);
|
||||
|
||||
// 返回{list, total}格式
|
||||
return {
|
||||
list: optionsUtils.buildGroupOptions(options, this.certDomains),
|
||||
total: siteRes.length,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
@@ -383,7 +428,10 @@ export class DemoTest extends AbstractTaskPlugin {
|
||||
};
|
||||
});
|
||||
//将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
|
||||
return optionsUtils.buildGroupOptions(options, this.certDomains);
|
||||
return {
|
||||
list: optionsUtils.buildGroupOptions(options, this.certDomains),
|
||||
total: siteRes.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
|
||||
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
|
||||
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
|
||||
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
|
||||
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
|
||||
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
|
||||
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
|
||||
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
|
||||
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
|
||||
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
|
||||
* dcdn自动匹配部署,支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -211,3 +211,4 @@ https://certd.handfree.work/
|
||||
| --------- |--------- |----------- |
|
||||
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
|
||||
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具,无需FQ,解决github无法访问的问题 |
|
||||
| [winsvc-manager](https://github.com/greper/winsvc-manager/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/greper/winsvc-manager?logo=github"/> | 可视化包装应用成为一个Windows服务,使其后台运行 |
|
||||
|
||||
@@ -3,6 +3,47 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
|
||||
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
|
||||
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
|
||||
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
|
||||
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
|
||||
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
|
||||
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
|
||||
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
|
||||
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
|
||||
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
|
||||
* dcdn自动匹配部署,支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复cname校验报该授权无权限的bug ([b1eb706](https://github.com/certd/certd/commit/b1eb7069258d6ff2b128091911fa448eaffc5f33))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持部署到火山云tos自定义域名证书 ([af6deb9](https://github.com/certd/certd/commit/af6deb99cd24a69a189b1fdd1df51c8f7816dcda))
|
||||
* 支持部署证书到火山引擎vod ([f91d591](https://github.com/certd/certd/commit/f91d591b03c50166d9fa352ba11c62d963869aa5))
|
||||
|
||||
## [1.39.6](https://github.com/certd/certd/compare/v1.39.5...v1.39.6) (2026-03-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -28,51 +28,53 @@
|
||||
| 24.| **中国移动CND授权** | |
|
||||
| 25.| **授权插件示例** | 这是一个示例授权插件,用于演示如何实现一个授权插件 |
|
||||
| 26.| **dns.la授权** | |
|
||||
| 27.| **多吉云** | |
|
||||
| 28.| **Dokploy授权** | |
|
||||
| 29.| **farcdn授权** | |
|
||||
| 30.| **FlexCDN授权** | |
|
||||
| 31.| **Gcore** | Gcore |
|
||||
| 32.| **Github授权** | |
|
||||
| 33.| **godaddy授权** | |
|
||||
| 34.| **金山云授权** | |
|
||||
| 35.| **FTP授权** | |
|
||||
| 36.| **七牛OSS授权** | |
|
||||
| 37.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
|
||||
| 38.| **s3/minio授权** | S3/minio oss授权 |
|
||||
| 39.| **namesilo授权** | |
|
||||
| 40.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
|
||||
| 41.| **1panel授权** | 账号和密码 |
|
||||
| 42.| **支付宝** | |
|
||||
| 43.| **白山云授权** | |
|
||||
| 44.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
|
||||
| 45.| **cdnfly授权** | |
|
||||
| 46.| **k8s授权** | |
|
||||
| 47.| **括彩云cdn授权** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 48.| **LeCDN授权** | |
|
||||
| 49.| **lucky** | |
|
||||
| 50.| **猫云授权** | |
|
||||
| 51.| **plesk授权** | |
|
||||
| 52.| **长亭雷池授权** | |
|
||||
| 53.| **群晖登录授权** | |
|
||||
| 54.| **uniCloud** | unicloud授权 |
|
||||
| 55.| **微信支付** | |
|
||||
| 56.| **易盾rcdn授权** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 57.| **易发云短信** | sms.yfyidc.cn/ |
|
||||
| 58.| **易盾DCDN授权** | https://user.yiduncdn.com |
|
||||
| 59.| **易支付** | |
|
||||
| 60.| **proxmox** | |
|
||||
| 61.| **UCloud授权** | 优刻得授权 |
|
||||
| 62.| **又拍云** | |
|
||||
| 63.| **网宿授权** | |
|
||||
| 64.| **西部数码授权** | |
|
||||
| 65.| **我爱云授权** | 我爱云CDN |
|
||||
| 66.| **新网授权(代理方式)** | |
|
||||
| 67.| **新网授权** | |
|
||||
| 68.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 |
|
||||
| 69.| **Zenlayer授权** | Zenlayer授权 |
|
||||
| 70.| **GoEdge授权** | |
|
||||
| 71.| **雨云授权** | https://app.rainyun.com/ |
|
||||
| 27.| **彩虹DNS** | 彩虹DNS管理系统授权 |
|
||||
| 28.| **多吉云** | |
|
||||
| 29.| **Dokploy授权** | |
|
||||
| 30.| **farcdn授权** | |
|
||||
| 31.| **FlexCDN授权** | |
|
||||
| 32.| **Gcore** | Gcore |
|
||||
| 33.| **Github授权** | |
|
||||
| 34.| **godaddy授权** | |
|
||||
| 35.| **金山云授权** | |
|
||||
| 36.| **FTP授权** | |
|
||||
| 37.| **七牛OSS授权** | |
|
||||
| 38.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
|
||||
| 39.| **s3/minio授权** | S3/minio oss授权 |
|
||||
| 40.| **namesilo授权** | |
|
||||
| 41.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
|
||||
| 42.| **1panel授权** | 账号和密码 |
|
||||
| 43.| **支付宝** | |
|
||||
| 44.| **白山云授权** | |
|
||||
| 45.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
|
||||
| 46.| **cdnfly授权** | |
|
||||
| 47.| **k8s授权** | |
|
||||
| 48.| **括彩云cdn授权** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 49.| **LeCDN授权** | |
|
||||
| 50.| **lucky** | |
|
||||
| 51.| **猫云授权** | |
|
||||
| 52.| **plesk授权** | |
|
||||
| 53.| **长亭雷池授权** | |
|
||||
| 54.| **群晖登录授权** | |
|
||||
| 55.| **uniCloud** | unicloud授权 |
|
||||
| 56.| **微信支付** | |
|
||||
| 57.| **易盾rcdn授权** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
|
||||
| 58.| **易发云短信** | sms.yfyidc.cn/ |
|
||||
| 59.| **易盾DCDN授权** | https://user.yiduncdn.com |
|
||||
| 60.| **易支付** | |
|
||||
| 61.| **proxmox** | |
|
||||
| 62.| **Spaceship.com 授权** | Spaceship.com API 授权插件 |
|
||||
| 63.| **UCloud授权** | 优刻得授权 |
|
||||
| 64.| **又拍云** | |
|
||||
| 65.| **网宿授权** | |
|
||||
| 66.| **西部数码授权** | |
|
||||
| 67.| **我爱云授权** | 我爱云CDN |
|
||||
| 68.| **新网授权(代理方式)** | |
|
||||
| 69.| **新网授权** | |
|
||||
| 70.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 |
|
||||
| 71.| **Zenlayer授权** | Zenlayer授权 |
|
||||
| 72.| **GoEdge授权** | |
|
||||
| 73.| **雨云授权** | https://app.rainyun.com/ |
|
||||
|
||||
<style module>
|
||||
table th:first-of-type {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 任务插件
|
||||
共 `128` 款任务插件
|
||||
共 `129` 款任务插件
|
||||
## 1. 证书申请
|
||||
|
||||
| 序号 | 名称 | 说明 |
|
||||
@@ -155,8 +155,9 @@
|
||||
| 序号 | 名称 | 说明 |
|
||||
|-----|-----|-----|
|
||||
| 1.| **百度云-部署证书到负载均衡** | 部署到百度云负载均衡,包括BLB、APPBLB |
|
||||
| 2.| **百度云-部署证书到CDN** | 部署到百度云CDN |
|
||||
| 3.| **百度云-上传到证书托管** | 上传证书到百度云证书托管中心 |
|
||||
| 2.| **百度云-部署到CCE** | 部署到百度云CCE集群Ingress等通过Secret管理证书的应用 |
|
||||
| 3.| **百度云-部署证书到CDN** | 部署到百度云CDN |
|
||||
| 4.| **百度云-上传到证书托管** | 上传证书到百度云证书托管中心 |
|
||||
## 12. 七牛云
|
||||
|
||||
| 序号 | 名称 | 说明 |
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
| 16.| **腾讯云EO DNS** | 腾讯云EO DNS解析提供者 |
|
||||
| 17.| **西部数码** | west dns provider |
|
||||
| 18.| **Dns提供商Demo** | dns provider示例 |
|
||||
| 19.| **51dns** | 51DNS |
|
||||
| 20.| **新网互联** | 新网互联 |
|
||||
| 19.| **彩虹DNS** | 彩虹DNS管理系统 |
|
||||
| 20.| **Spaceship** | Spaceship 域名解析 |
|
||||
| 21.| **51dns** | 51DNS |
|
||||
| 22.| **新网互联** | 新网互联 |
|
||||
|
||||
<style module>
|
||||
table th:first-of-type {
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.39.7"
|
||||
"version": "1.39.9"
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/publishlab/node-acme-client/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.39.8](https://github.com/publishlab/node-acme-client/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.39.7](https://github.com/publishlab/node-acme-client/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"type": "module",
|
||||
"module": "scr/index.js",
|
||||
"main": "src/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.39.7",
|
||||
"@certd/basic": "^1.39.9",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.9.0",
|
||||
@@ -70,5 +70,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -588,7 +588,8 @@ class AcmeClient {
|
||||
|
||||
if (invalidStates.includes(resp.data.status)) {
|
||||
abort();
|
||||
throw new Error(util.formatResponseError(resp));
|
||||
this.log(`[${d}] : 检查状态 = ${resp.data.status} : ${JSON.stringify(resp.data)}`);
|
||||
throw new Error("校验失败:" + util.formatResponseError(resp));
|
||||
}
|
||||
else if (pendingStates.includes(resp.data.status)) {
|
||||
throw new Error(`[${d}] Operation is pending or processing(当前仍然在等待状态)`);
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
@@ -1 +1 @@
|
||||
01:02
|
||||
01:21
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -47,5 +47,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
||||
}
|
||||
|
||||
const originalRequest = error.config || {};
|
||||
logger.info(`config`, originalRequest);
|
||||
// logger.info(`config`, originalRequest);
|
||||
const retry = originalRequest.retry || {};
|
||||
if (retry.status && retry.status.includes(status)) {
|
||||
if (retry.max > 0 && retry.count < retry.max) {
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
|
||||
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
|
||||
* dcdn自动匹配部署,支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -18,8 +18,8 @@
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.39.7",
|
||||
"@certd/plus-core": "^1.39.7",
|
||||
"@certd/basic": "^1.39.9",
|
||||
"@certd/plus-core": "^1.39.9",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
@@ -45,5 +45,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ export class Executor {
|
||||
//参数没有变化
|
||||
inputChanged = false;
|
||||
}
|
||||
if (step.strategy?.runStrategy === RunStrategy.SkipWhenSucceed) {
|
||||
if (step.strategy?.runStrategy === RunStrategy.SkipWhenSucceed && define.runStrategy !== RunStrategy.AlwaysRun) {
|
||||
if (lastResult != null && lastResult === ResultType.success && !inputChanged) {
|
||||
step.status!.output = lastNode?.status?.output;
|
||||
step.status!.files = lastNode?.status?.files;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IEmailService } from "../service/index.js";
|
||||
|
||||
export type NotificationBody = {
|
||||
userId?: number;
|
||||
projectId?: number;
|
||||
title: string;
|
||||
content: string;
|
||||
pipeline?: Pipeline;
|
||||
@@ -20,6 +21,7 @@ export type NotificationBody = {
|
||||
pipelineResult?: string;
|
||||
pipelineTitle?: string;
|
||||
errors?: string;
|
||||
[key: string]: any; // 其他templateData
|
||||
};
|
||||
|
||||
export type NotificationRequestHandleReqInput<T = any> = {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Registrable } from "../registry/index.js";
|
||||
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
|
||||
import { FileStore } from "../core/file-store.js";
|
||||
import { accessRegistry, IAccessService } from "../access/index.js";
|
||||
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
|
||||
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
|
||||
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
|
||||
import { HttpClient } from "@certd/basic";
|
||||
import { domainUtils, HttpClient, HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
|
||||
import dayjs from "dayjs";
|
||||
import { IPluginConfigService } from "../service/config.js";
|
||||
import { upperFirst } from "lodash-es";
|
||||
import { cloneDeep, upperFirst } from "lodash-es";
|
||||
import { accessRegistry, IAccessService } from "../access/index.js";
|
||||
import { PageSearch } from "../context/index.js";
|
||||
import { FileStore } from "../core/file-store.js";
|
||||
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
|
||||
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
|
||||
import { INotificationService } from "../notification/index.js";
|
||||
import { Registrable } from "../registry/index.js";
|
||||
import { IPluginConfigService } from "../service/config.js";
|
||||
import { TaskEmitter } from "../service/emit.js";
|
||||
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
|
||||
|
||||
export type PluginRequestHandleReq<T = any> = {
|
||||
typeName: string;
|
||||
@@ -64,6 +64,7 @@ export type PluginDefine = Registrable & {
|
||||
onlyAdmin?: boolean;
|
||||
needPlus?: boolean;
|
||||
showRunStrategy?: boolean;
|
||||
runStrategy?: any;
|
||||
pluginType?: string; //类型
|
||||
type?: string; //来源
|
||||
};
|
||||
@@ -81,6 +82,12 @@ export type TaskResult = {
|
||||
pipelineVars: Record<string, any>;
|
||||
pipelinePrivateVars?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type CertTargetItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
domain: string | string[];
|
||||
};
|
||||
export type TaskInstanceContext = {
|
||||
//流水线定义
|
||||
pipeline: Pipeline;
|
||||
@@ -315,6 +322,103 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||
getLastOutput(key: string) {
|
||||
return this.getLastStatus().status?.output?.[key];
|
||||
}
|
||||
|
||||
isDomainMatched(domainList: string | string[], certDomains: string[]): boolean {
|
||||
const matched = domainUtils.match(domainList, certDomains);
|
||||
return matched;
|
||||
}
|
||||
|
||||
isNotChanged() {
|
||||
const lastResult = this.ctx?.lastStatus?.status?.status;
|
||||
return !this.ctx.inputChanged && lastResult === "success";
|
||||
}
|
||||
|
||||
async getAutoMatchedTargets(req: {
|
||||
targetName: string;
|
||||
certDomains: string[];
|
||||
pageSize: number;
|
||||
getDeployTargetList: (req: PageSearch) => Promise<{ list: CertTargetItem[]; total: number }>;
|
||||
}): Promise<CertTargetItem[]> {
|
||||
const matchedDomains: CertTargetItem[] = [];
|
||||
let pageNo = 1;
|
||||
const { certDomains } = req;
|
||||
|
||||
const pageSize = req.pageSize || 100;
|
||||
while (true) {
|
||||
const result = await req.getDeployTargetList({
|
||||
pageNo,
|
||||
pageSize,
|
||||
});
|
||||
const pageData = result.list;
|
||||
this.logger.info(`获取到 ${pageData.length} 个 ${req.targetName}`);
|
||||
|
||||
if (!pageData || pageData.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const item of pageData) {
|
||||
const domainName = item.domain;
|
||||
if (this.isDomainMatched(domainName, certDomains)) {
|
||||
matchedDomains.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount = result.total || 0;
|
||||
if (pageNo * pageSize >= totalCount || matchedDomains.length == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
pageNo++;
|
||||
}
|
||||
|
||||
return matchedDomains;
|
||||
}
|
||||
|
||||
async autoMatchedDeploy(req: {
|
||||
targetName: string;
|
||||
getCertDomains: () => Promise<string[]>;
|
||||
uploadCert: () => Promise<any>;
|
||||
deployOne: (req: { target: CertTargetItem; cert: any }) => Promise<void>;
|
||||
getDeployTargetList: (req: PageSearch) => Promise<{ list: CertTargetItem[]; total: number }>;
|
||||
}): Promise<{ result: string; deployedList: string[] }> {
|
||||
this.logger.info("证书匹配模式部署");
|
||||
const certDomains = await req.getCertDomains();
|
||||
const certTargetList = await this.getAutoMatchedTargets({
|
||||
targetName: req.targetName,
|
||||
pageSize: 200,
|
||||
certDomains,
|
||||
getDeployTargetList: req.getDeployTargetList,
|
||||
});
|
||||
if (certTargetList.length === 0) {
|
||||
this.logger.warn(`未找到匹配的${req.targetName}`);
|
||||
return { result: "skip", deployedList: [] };
|
||||
}
|
||||
this.logger.info(`找到 ${certTargetList.length} 个匹配的${req.targetName}`);
|
||||
|
||||
//开始部署,检查是否已经部署过
|
||||
const deployedList = cloneDeep(this.getLastStatus()?.status?.output?.deployedList || []);
|
||||
const unDeployedTargets = certTargetList.filter(item => !deployedList.includes(item.value));
|
||||
const count = unDeployedTargets.length;
|
||||
const deployedCount = certTargetList.length - count;
|
||||
if (deployedCount > 0) {
|
||||
this.logger.info(`跳过 ${deployedCount} 个已部署过的${req.targetName}`);
|
||||
}
|
||||
this.logger.info(`需要部署 ${count} 个${req.targetName}`);
|
||||
if (count === 0) {
|
||||
return { result: "skip", deployedList };
|
||||
}
|
||||
this.logger.info(`开始部署`);
|
||||
const aliCrtId = await req.uploadCert();
|
||||
for (const target of unDeployedTargets) {
|
||||
await req.deployOne({
|
||||
cert: aliCrtId,
|
||||
target,
|
||||
});
|
||||
deployedList.push(target.value);
|
||||
}
|
||||
this.logger.info(`本次成功部署 ${count} 个${req.targetName}`);
|
||||
return { result: "success", deployedList };
|
||||
}
|
||||
}
|
||||
|
||||
export type OutputVO = {
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
@@ -24,5 +24,5 @@
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -31,5 +31,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
@@ -56,5 +56,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -14,10 +14,11 @@
|
||||
"build3": "rollup -c",
|
||||
"build2": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"pub": "npm publish"
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.39.7",
|
||||
"@certd/basic": "^1.39.9",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -32,5 +33,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ export class K8sClient {
|
||||
const yml = loadYaml<KubernetesObject>(manifest);
|
||||
const client = this.getKubeClient();
|
||||
try {
|
||||
this.logger.info("apply yaml:", yml);
|
||||
await client.create(yml);
|
||||
} catch (e) {
|
||||
this.logger.error("apply error", e.response?.body);
|
||||
if (e.response?.body?.reason === "AlreadyExists") {
|
||||
//patch
|
||||
this.logger.info("patch existing resource: ", yml.metadata?.name);
|
||||
@@ -70,13 +70,26 @@ export class K8sClient {
|
||||
yml.metadata = {};
|
||||
}
|
||||
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
|
||||
await client.patch(yml);
|
||||
return;
|
||||
const res = await client.patch(yml);
|
||||
return res?.body;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async applyPatch(manifest: string) {
|
||||
const yml = loadYaml<KubernetesObject>(manifest);
|
||||
const client = this.getKubeClient();
|
||||
this.logger.info("patch yaml:", yml);
|
||||
const existing = await client.read(yml as any);
|
||||
if (!yml.metadata) {
|
||||
yml.metadata = {};
|
||||
}
|
||||
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
|
||||
const res = await client.patch(yml);
|
||||
return res?.body;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param localRecords { [domain]:{ip:'xxx.xx.xxx'} }
|
||||
@@ -112,6 +125,7 @@ export class K8sClient {
|
||||
*/
|
||||
async createSecret(opts: { namespace: string; body: V1Secret }) {
|
||||
const namespace = opts.namespace || "default";
|
||||
this.logger.info("create secret:", opts.body.metadata);
|
||||
const created = await this.client.createNamespacedSecret(namespace, opts.body);
|
||||
this.logger.info("new secrets:", opts.body.metadata);
|
||||
return created.body;
|
||||
@@ -152,6 +166,8 @@ export class K8sClient {
|
||||
this.logger.info(`secret ${secretName} 已创建`);
|
||||
return res;
|
||||
}
|
||||
|
||||
throw new Error(`secret ${secretName} 不存在`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -28,11 +28,11 @@
|
||||
],
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.39.7",
|
||||
"@certd/basic": "^1.39.7",
|
||||
"@certd/pipeline": "^1.39.7",
|
||||
"@certd/plugin-lib": "^1.39.7",
|
||||
"@certd/plus-core": "^1.39.7",
|
||||
"@certd/acme-client": "^1.39.9",
|
||||
"@certd/basic": "^1.39.9",
|
||||
"@certd/pipeline": "^1.39.9",
|
||||
"@certd/plugin-lib": "^1.39.9",
|
||||
"@certd/plus-core": "^1.39.9",
|
||||
"@midwayjs/cache": "3.14.0",
|
||||
"@midwayjs/core": "3.20.11",
|
||||
"@midwayjs/i18n": "3.20.13",
|
||||
@@ -64,5 +64,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -46,5 +46,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -17,10 +17,10 @@
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.39.7",
|
||||
"@certd/basic": "^1.39.7",
|
||||
"@certd/pipeline": "^1.39.7",
|
||||
"@certd/plugin-lib": "^1.39.7",
|
||||
"@certd/acme-client": "^1.39.9",
|
||||
"@certd/basic": "^1.39.9",
|
||||
"@certd/pipeline": "^1.39.9",
|
||||
"@certd/plugin-lib": "^1.39.9",
|
||||
"psl": "^1.9.0",
|
||||
"punycode.js": "^2.3.1"
|
||||
},
|
||||
@@ -38,5 +38,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -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.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* dcdn自动匹配部署,支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -22,10 +22,10 @@
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@alicloud/tea-util": "^1.4.11",
|
||||
"@aws-sdk/client-s3": "^3.964.0",
|
||||
"@certd/acme-client": "^1.39.7",
|
||||
"@certd/basic": "^1.39.7",
|
||||
"@certd/pipeline": "^1.39.7",
|
||||
"@certd/plus-core": "^1.39.7",
|
||||
"@certd/acme-client": "^1.39.9",
|
||||
"@certd/basic": "^1.39.9",
|
||||
"@certd/pipeline": "^1.39.9",
|
||||
"@certd/plus-core": "^1.39.9",
|
||||
"@kubernetes/client-node": "0.21.0",
|
||||
"ali-oss": "^6.22.0",
|
||||
"basic-ftp": "^5.0.5",
|
||||
@@ -57,5 +57,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "b0ccab41e17cb16c87bfc0ec3b052d5e8a5d8435"
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@ const formats = {
|
||||
jks: ["jks"],
|
||||
p7b: ["p7b", "key"],
|
||||
};
|
||||
|
||||
export type SimpleCertDetail = {
|
||||
notBefore: Date;
|
||||
notAfter: Date;
|
||||
domains: string[];
|
||||
};
|
||||
export class CertReader {
|
||||
cert: CertInfo;
|
||||
|
||||
@@ -116,6 +122,15 @@ export class CertReader {
|
||||
return CertReader.readCertDetail(crt);
|
||||
}
|
||||
|
||||
getSimpleDetail() {
|
||||
const { detail } = this.getCrtDetail();
|
||||
return {
|
||||
notBefore: detail.notBefore,
|
||||
notAfter: detail.notAfter,
|
||||
domains: this.getAllDomains(),
|
||||
};
|
||||
}
|
||||
|
||||
static readCertDetail(crt: string) {
|
||||
const detail = crypto.readCertificateInfo(crt.toString());
|
||||
const effective = detail.notBefore;
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
|
||||
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
|
||||
* dcdn自动匹配部署,支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-client
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -106,8 +106,8 @@
|
||||
"zod-defaults": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/lib-iframe": "^1.39.7",
|
||||
"@certd/pipeline": "^1.39.7",
|
||||
"@certd/lib-iframe": "^1.39.9",
|
||||
"@certd/pipeline": "^1.39.9",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -235,7 +235,6 @@ watch(
|
||||
const { form } = value;
|
||||
const oldForm: any = oldValue?.form;
|
||||
let changed = oldForm == null || optionsRef.value.length == 0;
|
||||
debugger;
|
||||
if (props.watches && props.watches.length > 0) {
|
||||
for (const key of props.watches) {
|
||||
if (oldForm && JSON.stringify(form[key]) != JSON.stringify(oldForm[key])) {
|
||||
|
||||
@@ -221,6 +221,7 @@ export default {
|
||||
projectJoin: "Join Project",
|
||||
currentProject: "Current Project",
|
||||
projectMemberManager: "Project Member",
|
||||
domainMonitorSetting: "Domain Monitor Settings",
|
||||
},
|
||||
certificateRepo: {
|
||||
title: "Certificate Repository",
|
||||
|
||||
@@ -64,10 +64,41 @@ export default {
|
||||
dnsServerHelper: "Use a custom domain name resolution server, such as: 1.1.1.1 , support multiple",
|
||||
certValidDays: "Certificate Valid Days",
|
||||
certValidDaysHelper: "Number of days before expiration to send a notification",
|
||||
|
||||
domain: {
|
||||
monitorSettings: "Domain Monitor Settings",
|
||||
enabled: "Enable Domain Monitor",
|
||||
enabledHelper: "Enable to monitor all domain registration expiration time",
|
||||
notificationChannel: "Notification Channel",
|
||||
setNotificationChannel: "Set the notification channel",
|
||||
willExpireDays: "Will Expire Days",
|
||||
willExpireDaysHelper: "Number of days before expiration to send a notification",
|
||||
monitorCronSetting: "Monitoring Schedule",
|
||||
cronTrigger: "Scheduled trigger for monitoring",
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
expired: "Expired",
|
||||
expiring: "Expiring",
|
||||
noExpired: "Not Expired",
|
||||
},
|
||||
history: {
|
||||
title: "Monitoring Execution Records",
|
||||
description: "Monitoring execution records",
|
||||
resultTitle: "Status",
|
||||
contentTitle: "Content",
|
||||
titleTitle: "Title",
|
||||
jobTypeTitle: "Job Type",
|
||||
startAtTitle: "Start Time",
|
||||
endAtTitle: "End Time",
|
||||
jobResultTitle: "Result",
|
||||
jobResult: {
|
||||
done: "Done",
|
||||
start: "Start",
|
||||
},
|
||||
jobType: {
|
||||
domainExpirationCheck: "Domain Expiration Check",
|
||||
siteCertMonitor: "Site Certificate Monitor",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -226,6 +226,8 @@ export default {
|
||||
projectJoin: "加入项目",
|
||||
currentProject: "当前项目",
|
||||
projectMemberManager: "项目成员管理",
|
||||
domainMonitorSetting: "域名监控设置",
|
||||
jobHistory: "监控执行记录",
|
||||
},
|
||||
certificateRepo: {
|
||||
title: "证书仓库",
|
||||
@@ -589,11 +591,11 @@ export default {
|
||||
userValidityPeriodHelper: "有效期内用户可正常使用,失效后用户的流水线将被停用",
|
||||
enableUsernameRegistration: "开启用户名注册",
|
||||
enableEmailRegistration: "开启邮箱注册",
|
||||
proFeature: "专业版功能",
|
||||
proFeature: "Certd专业版功能",
|
||||
emailServerSetup: "设置邮箱服务器",
|
||||
enableSmsLoginRegister: "开启手机号登录、注册",
|
||||
defaultLoginType: "默认登录方式",
|
||||
commFeature: "商业版功能",
|
||||
commFeature: "Certd商业版功能",
|
||||
smsProvider: "短信提供商",
|
||||
aliyunSms: "阿里云短信",
|
||||
tencentSms: "腾讯云短信",
|
||||
@@ -813,7 +815,7 @@ export default {
|
||||
},
|
||||
domain: {
|
||||
domainManager: "域名管理",
|
||||
domainDescription: "管理域名的校验方式,用于申请证书时自动选择验证方式",
|
||||
domainDescription: "流水线校验方式选择“自动选择”时此处配置才有用,支持自动导入;注意:这里只需要管理主域名即可,子域名不要填写(子域名托管和免费二级子域名除外)",
|
||||
domain: "域名",
|
||||
challengeType: "校验类型",
|
||||
dnsProviderType: "DNS提供商类型",
|
||||
|
||||
@@ -68,10 +68,40 @@ export default {
|
||||
dnsServerHelper: "使用自定义的域名解析服务器,如:1.1.1.1 , 支持多个",
|
||||
certValidDays: "证书到期前天数",
|
||||
certValidDaysHelper: "证书到期前多少天发送通知",
|
||||
domain: {
|
||||
monitorSettings: "域名监控设置",
|
||||
enabled: "启用域名监控",
|
||||
enabledHelper: "启用后,监控“域名管理”中域名的过期时间,到期前通知提醒",
|
||||
notificationChannel: "通知渠道",
|
||||
setNotificationChannel: "设置通知渠道",
|
||||
willExpireDays: "到期前天数",
|
||||
willExpireDaysHelper: "域名有效期到期前多少天发送通知",
|
||||
monitorCronSetting: "监控定时设置",
|
||||
cronTrigger: "定时触发监控",
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
expired: "已过期",
|
||||
expiring: "即将过期",
|
||||
noExpired: "未过期",
|
||||
},
|
||||
history: {
|
||||
title: "监控执行记录",
|
||||
description: "站点证书、域名等监控任务的执行记录",
|
||||
resultTitle: "状态",
|
||||
contentTitle: "内容",
|
||||
titleTitle: "标题",
|
||||
jobTypeTitle: "任务类型",
|
||||
startAtTitle: "开始时间",
|
||||
endAtTitle: "结束时间",
|
||||
jobResultTitle: "任务结果",
|
||||
jobResult: {
|
||||
done: "完成",
|
||||
start: "开始",
|
||||
},
|
||||
jobType: {
|
||||
domainExpirationCheck: "域名到期检查",
|
||||
siteCertMonitor: "站点证书监控",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -88,13 +88,13 @@ export default {
|
||||
activation_code_one_use: "激活码使用过一次之后,不可再次使用,如果要更换站点,请",
|
||||
bind_account: "绑定账号",
|
||||
transfer_vip: '然后"转移VIP"即可',
|
||||
needVipTip: "此为专业版功能,请先开通专业版",
|
||||
needVipTip: "此为Certd专业版功能,请先开通Certd专业版",
|
||||
manual_activation: "激活码手动激活",
|
||||
close: "关闭",
|
||||
have_activation_code: "已经有激活码了?",
|
||||
buy: "立即购买",
|
||||
already_plus: "已经是专业版了,是否升级为商业版?注意:专业版时长将被覆盖",
|
||||
already_comm: "已经是商业版了,不能降级为专业版",
|
||||
already_plus: "已经是Certd专业版了,是否升级为商业版?注意:Certd专业版时长将被覆盖",
|
||||
already_comm: "已经是Certd商业版了,不能降级为专业版",
|
||||
already_perpetual_plus: "您已经是永久专业版了,无法继续升级",
|
||||
confirm: "确认",
|
||||
not_effective: "VIP没有生效/时长未同步?",
|
||||
|
||||
@@ -241,6 +241,28 @@ export const certdResources = [
|
||||
isMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.sysResources.domainMonitorSetting",
|
||||
name: "DomainMonitorSetting",
|
||||
path: "/certd/cert/domain/setting",
|
||||
component: "/certd/cert/domain/setting/index.vue",
|
||||
meta: {
|
||||
icon: "ion:stopwatch-outline",
|
||||
auth: true,
|
||||
isMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.sysResources.jobHistory",
|
||||
name: "JobHistory",
|
||||
path: "/certd/monitor/history",
|
||||
component: "/certd/monitor/history/index.vue",
|
||||
meta: {
|
||||
icon: "ion:barcode-outline",
|
||||
auth: true,
|
||||
isMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.userSecurity",
|
||||
name: "UserSecurity",
|
||||
|
||||
@@ -21,7 +21,8 @@ import { defineComponent, reactive, ref, watch, inject } from "vue";
|
||||
import CertAccessModal from "./access/index.vue";
|
||||
import { createAccessApi } from "../api";
|
||||
import { message } from "ant-design-vue";
|
||||
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useProjectStore } from "/@/store/project";
|
||||
export default defineComponent({
|
||||
name: "AccessSelector",
|
||||
components: { CertAccessModal },
|
||||
@@ -71,11 +72,27 @@ export default defineComponent({
|
||||
emitValue(null);
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
const projectStore = useProjectStore();
|
||||
|
||||
async function emitValue(value) {
|
||||
if (pipeline && pipeline?.value && target?.value && pipeline.value.userId !== target.value.userId) {
|
||||
message.error("对不起,您不能修改他人流水线的授权");
|
||||
return;
|
||||
const userId = userStore.userInfo.id;
|
||||
const isEnterprice = projectStore.isEnterprise;
|
||||
if (pipeline?.value) {
|
||||
if (isEnterprice) {
|
||||
const projectId = projectStore.currentProjectId;
|
||||
if (pipeline?.value?.projectId !== projectId) {
|
||||
message.error(`对不起,您不能修改其他项目流水线的授权`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||
message.error(`对不起,您不能修改他人流水线的授权`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
selectedId.value = "";
|
||||
target.value = null;
|
||||
|
||||
@@ -48,6 +48,7 @@ import createCrudOptions from "../crud";
|
||||
import { addonProvide } from "../common";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useI18n } from "/src/locales";
|
||||
import { useProjectStore } from "/@/store/project";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -127,13 +128,24 @@ function clear() {
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const projectStore = useProjectStore();
|
||||
async function emitValue(value: any) {
|
||||
// target.value = optionsDictRef.dataMap[value];
|
||||
const userId = userStore.userInfo.id;
|
||||
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
|
||||
return;
|
||||
if (pipeline.value) {
|
||||
const userId = userStore.userInfo.id;
|
||||
const isEnterprice = projectStore.isEnterprise;
|
||||
if (isEnterprice) {
|
||||
const projectId = projectStore.currentProjectId;
|
||||
if (pipeline?.value?.projectId !== projectId) {
|
||||
message.error(`对不起,您不能修改其他项目流水线的${props.addonType}设置`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
emit("change", value);
|
||||
emit("update:modelValue", value);
|
||||
|
||||
@@ -128,6 +128,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
}, 2000);
|
||||
},
|
||||
},
|
||||
monitorSettingSave: {
|
||||
show: hasActionPermission("write"),
|
||||
title: "域名过期监控设置",
|
||||
type: "primary",
|
||||
icon: "ion:save-outline",
|
||||
text: "域名过期监控设置",
|
||||
click: async () => {
|
||||
router.push({
|
||||
path: "/certd/cert/domain/setting",
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// @ts-ignore
|
||||
import { request } from "/src/api/service";
|
||||
const apiPrefix = "/cert/domain/setting";
|
||||
export type UserDomainMonitorSetting = {
|
||||
enabled?: boolean;
|
||||
notificationId?: number;
|
||||
cron?: string;
|
||||
willExpireDays?: number;
|
||||
};
|
||||
|
||||
export async function DomainMonitorSettingsGet() {
|
||||
const res = await request({
|
||||
url: apiPrefix + "/get",
|
||||
method: "post",
|
||||
});
|
||||
if (!res) {
|
||||
return {};
|
||||
}
|
||||
return res as UserDomainMonitorSetting;
|
||||
}
|
||||
export async function DomainMonitorSettingsSave(data: UserDomainMonitorSetting) {
|
||||
await request({
|
||||
url: apiPrefix + "/save",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<fs-page class="page-user-settings page-domain-monitor-setting">
|
||||
<template #header>
|
||||
<div class="title">{{ t("monitor.setting.domain.monitorSettings") }}</div>
|
||||
</template>
|
||||
<div class="user-settings-form settings-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<a-form-item :label="t('monitor.setting.domain.enabled')" :name="['enabled']">
|
||||
<div class="flex flex-baseline">
|
||||
<a-switch v-model:checked="formState.enabled" :disabled="!settingsStore.isPlus" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">{{ t("monitor.setting.domain.enabledHelper") }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.notificationChannel')" :name="['notificationId']">
|
||||
<div class="flex">
|
||||
<NotificationSelector v-model="formState.notificationId" />
|
||||
</div>
|
||||
<div class="helper">{{ t("monitor.setting.domain.setNotificationChannel") }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.willExpireDays')" :name="['willExpireDays']">
|
||||
<div class="flex">
|
||||
<a-input-number v-model:value="formState.willExpireDays" />
|
||||
</div>
|
||||
<div class="helper">{{ t("monitor.setting.domain.willExpireDaysHelper") }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.monitorCronSetting')" :name="['cron']">
|
||||
<div class="flex flex-baseline">
|
||||
<cron-editor v-model="formState.cron" :disabled="!settingsStore.isPlus" :allow-every-min="userStore.isAdmin" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">{{ t("monitor.setting.domain.cronTrigger") }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<loading-button type="primary" html-type="button" :click="doSave" :disabled="!hasActionPermission('write')">{{ t("certd.save") }}</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { notification } from "ant-design-vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { reactive } from "vue";
|
||||
import * as api from "./api";
|
||||
import { UserDomainMonitorSetting } from "./api";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { utils } from "/@/utils";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
import { useI18n } from "/src/locales";
|
||||
import { useSettingStore } from "/src/store/settings";
|
||||
import { useCrudPermission } from "/@/plugin/permission";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const settingsStore = useSettingStore();
|
||||
const userStore = useUserStore();
|
||||
defineOptions({
|
||||
name: "DomainMonitorSetting",
|
||||
});
|
||||
|
||||
const randomHour = Math.floor(Math.random() * 9);
|
||||
const randomMin = Math.floor(Math.random() * 59);
|
||||
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
||||
|
||||
const formState = reactive<Partial<UserDomainMonitorSetting>>({
|
||||
enabled: false,
|
||||
notificationId: 0,
|
||||
cron: randomCron,
|
||||
willExpireDays: 30,
|
||||
});
|
||||
|
||||
async function loadUserSettings() {
|
||||
const data: any = await api.DomainMonitorSettingsGet();
|
||||
merge(formState, data);
|
||||
}
|
||||
|
||||
const { hasActionPermission } = useCrudPermission({ permission: { isProjectPermission: true } });
|
||||
|
||||
loadUserSettings();
|
||||
const doSave = async (form: any) => {
|
||||
await utils.sleep(300);
|
||||
await api.DomainMonitorSettingsSave({
|
||||
...formState,
|
||||
});
|
||||
notification.success({
|
||||
message: t("certd.saveSuccess"),
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-domain-monitor-setting {
|
||||
.settings-form {
|
||||
width: 700px;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -66,7 +66,7 @@ export function useUserProfile() {
|
||||
width: "auto",
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `api/basic/file/download?&key=` + key;
|
||||
return `api/basic/file/download?token=${userStore.getToken}&key=` + key;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -82,7 +82,7 @@ export function useUserProfile() {
|
||||
onReady: null,
|
||||
uploader: {
|
||||
type: "form",
|
||||
action: "/basic/file/upload",
|
||||
action: "/basic/file/upload?token=" + userStore.getToken,
|
||||
name: "file",
|
||||
headers: {
|
||||
Authorization: "Bearer " + userStore.getToken,
|
||||
@@ -92,7 +92,7 @@ export function useUserProfile() {
|
||||
},
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `api/basic/file/download?&key=` + key;
|
||||
return `api/basic/file/download?token=${userStore.getToken}&key=` + key;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -147,6 +147,7 @@ import { isEmpty } from "lodash-es";
|
||||
import { dict } from "@fast-crud/fast-crud";
|
||||
import dayjs from "dayjs";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -351,7 +352,7 @@ const checkPasskeySupport = () => {
|
||||
passkeySupported.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const userAvatar = computed(() => {
|
||||
if (isEmpty(userInfo.value.avatar)) {
|
||||
return "";
|
||||
@@ -360,7 +361,7 @@ const userAvatar = computed(() => {
|
||||
return userInfo.value.avatar;
|
||||
}
|
||||
|
||||
return "api/basic/file/download?&key=" + userInfo.value.avatar;
|
||||
return `api/basic/file/download?token=${userStore.getToken}&key=${userInfo.value.avatar}`;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/monitor/job-history";
|
||||
|
||||
export const jobHistoryApi = {
|
||||
async GetList(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query,
|
||||
});
|
||||
},
|
||||
|
||||
async DelObj(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
method: "post",
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
|
||||
async BatchDelObj(ids: number[]) {
|
||||
return await request({
|
||||
url: apiPrefix + "/batchDelete",
|
||||
method: "post",
|
||||
data: { ids },
|
||||
});
|
||||
},
|
||||
|
||||
async GetObj(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/info",
|
||||
method: "post",
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,257 @@
|
||||
// @ts-ignore
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { ref } from "vue";
|
||||
import { createGroupDictRef } from "../../basic/group/api";
|
||||
import { useDicts } from "../../dicts";
|
||||
import { jobHistoryApi } from "./api";
|
||||
import { useCrudPermission } from "/@/plugin/permission";
|
||||
import { useProjectStore } from "/@/store/project";
|
||||
import { useI18n } from "/src/locales";
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { t } = useI18n();
|
||||
const api = jobHistoryApi;
|
||||
const { crudBinding } = crudExpose;
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async (req: EditReq) => {};
|
||||
const delRequest = async (req: DelReq) => {
|
||||
const { row } = req;
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
|
||||
const addRequest = async (req: AddReq) => {};
|
||||
const { myProjectDict } = useDicts();
|
||||
|
||||
const historyResultDict = dict({
|
||||
data: [
|
||||
{ label: t("monitor.history.jobResult.done"), value: "done", color: "green" },
|
||||
{ label: t("monitor.history.jobResult.start"), value: "start", color: "blue" },
|
||||
],
|
||||
});
|
||||
|
||||
const jobTypeDict = dict({
|
||||
data: [
|
||||
{ label: t("monitor.history.jobType.domainExpirationCheck"), value: "domainExpirationCheck", color: "green" },
|
||||
{ label: t("monitor.history.jobType.siteCertMonitor"), value: "siteCertMonitor", color: "blue" },
|
||||
],
|
||||
});
|
||||
|
||||
const selectedRowKeys = ref([]);
|
||||
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
|
||||
async onOk() {
|
||||
await api.BatchDelObj(selectedRowKeys.value);
|
||||
message.info("删除成功");
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error("请先勾选记录");
|
||||
}
|
||||
};
|
||||
|
||||
context.handleBatchDelete = handleBatchDelete;
|
||||
|
||||
const GroupTypeSite = "site";
|
||||
const groupDictRef = createGroupDictRef(GroupTypeSite);
|
||||
|
||||
function getDefaultGroupId() {
|
||||
const searchFrom = crudExpose.getSearchValidatedFormData();
|
||||
if (searchFrom.groupId) {
|
||||
return searchFrom.groupId;
|
||||
}
|
||||
}
|
||||
|
||||
const projectStore = useProjectStore();
|
||||
const { hasActionPermission } = useCrudPermission({ permission: context.permission });
|
||||
return {
|
||||
id: "jobHistoryCrud",
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
// tabs: {
|
||||
// name: "groupId",
|
||||
// show: true,
|
||||
// },
|
||||
toolbar: {
|
||||
buttons: {
|
||||
export: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
pageSizeOptions: ["10", "20", "50", "100", "200"],
|
||||
},
|
||||
settings: {
|
||||
plugins: {
|
||||
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
|
||||
rowSelection: {
|
||||
enabled: true,
|
||||
props: {
|
||||
multiple: true,
|
||||
crossPage: false,
|
||||
selectedRowKeys: () => {
|
||||
return selectedRowKeys;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right",
|
||||
width: 280,
|
||||
buttons: {
|
||||
edit: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
// tabs: {
|
||||
// name: "disabled",
|
||||
// show: true,
|
||||
// },
|
||||
search: {
|
||||
initialForm: {
|
||||
...projectStore.getSearchForm(),
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center",
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
type: {
|
||||
title: t("monitor.history.jobTypeTitle"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: jobTypeDict,
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
title: t("monitor.history.titleTitle"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
column: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
content: {
|
||||
title: t("monitor.history.contentTitle"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
column: {
|
||||
width: 460,
|
||||
ellipsis: true,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
title: t("monitor.history.resultTitle"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: historyResultDict,
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
sorter: true,
|
||||
cellRender({ value, row }) {
|
||||
return (
|
||||
<a-tooltip title={row.error}>
|
||||
<fs-values-format v-model={value} dict={historyResultDict}></fs-values-format>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
startAt: {
|
||||
title: t("monitor.history.startAtTitle"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "datetime",
|
||||
column: {
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
endAt: {
|
||||
title: t("monitor.history.endAtTitle"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "datetime",
|
||||
column: {
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
projectId: {
|
||||
title: t("certd.fields.projectName"),
|
||||
type: "dict-select",
|
||||
dict: myProjectDict,
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title flex items-center">
|
||||
{{ t("monitor.history.title") }}
|
||||
<div class="sub flex-1">
|
||||
<div>
|
||||
{{ t("monitor.history.description") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip title="批量删除">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import createCrudOptions from "./crud";
|
||||
import { useI18n } from "/src/locales";
|
||||
const { t } = useI18n();
|
||||
defineOptions({
|
||||
name: "JobHistory",
|
||||
});
|
||||
const context: any = {
|
||||
permission: {
|
||||
isProjectPermission: true,
|
||||
},
|
||||
};
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
||||
|
||||
const handleBatchDelete = context.handleBatchDelete;
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
@@ -42,6 +42,7 @@ import createCrudOptions from "../crud";
|
||||
import { notificationProvide } from "/@/views/certd/notification/common";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useI18n } from "/src/locales";
|
||||
import { useProjectStore } from "/@/store/project";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -127,13 +128,23 @@ function clear() {
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const projectStore = useProjectStore();
|
||||
async function emitValue(value: any) {
|
||||
// target.value = optionsDictRef.dataMap[value];
|
||||
const userId = userStore.userInfo.id;
|
||||
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||
message.error("对不起,您不能修改他人流水线的通知");
|
||||
return;
|
||||
const isEnterprice = projectStore.isEnterprise;
|
||||
|
||||
if (isEnterprice) {
|
||||
const projectId = projectStore.currentProjectId;
|
||||
if (pipeline?.value?.projectId !== projectId) {
|
||||
message.error("对不起,您不能修改其他项目流水线的通知");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (pipeline?.value?.userId !== userId) {
|
||||
message.error("对不起,您不能修改他人流水线的通知");
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit("change", value);
|
||||
emit("update:modelValue", value);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="header-profile flex-wrap bg-white dark:bg-black">
|
||||
<div class="flex flex-1">
|
||||
<div class="avatar">
|
||||
<a-avatar v-if="userInfo.avatar" size="large" :src="'api/basic/file/download?&key=' + userInfo.avatar" style="background-color: #eee"> </a-avatar>
|
||||
<a-avatar v-if="userInfo.avatar" size="large" :src="avatar" style="background-color: #eee"> </a-avatar>
|
||||
<a-avatar v-else size="large" style="background-color: #00b4f5">
|
||||
{{ userInfo.username }}
|
||||
</a-avatar>
|
||||
@@ -228,6 +228,16 @@ const userStore = useUserStore();
|
||||
const userInfo: ComputedRef<UserInfoRes> = computed(() => {
|
||||
return userStore.getUserInfo;
|
||||
});
|
||||
const avatar = computed(() => {
|
||||
const avt = userStore.getUserInfo?.avatar;
|
||||
if (!avt) {
|
||||
return "";
|
||||
}
|
||||
if (avt.startsWith("http")) {
|
||||
return avt;
|
||||
}
|
||||
return `/api/basic/file/download?key=${avt}`;
|
||||
});
|
||||
const now = computed(() => {
|
||||
const serverTime = Date.now() - settingStore.app.deltaTime;
|
||||
return dayjs(serverTime).format("YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
@@ -80,7 +80,7 @@ onMounted(() => {
|
||||
await settingStore.doBindUrl();
|
||||
notification.success({
|
||||
message: "更新成功",
|
||||
description: "专业版/商业版已激活",
|
||||
description: "Certd专业版/商业版已激活",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
|
||||
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
|
||||
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
|
||||
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
|
||||
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
|
||||
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
|
||||
|
||||
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
|
||||
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
|
||||
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
|
||||
* dcdn自动匹配部署,支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
|
||||
|
||||
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
CREATE TABLE `cd_job_history`
|
||||
(
|
||||
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||
`user_id` bigint NOT NULL,
|
||||
`project_id` bigint ,
|
||||
`type` varchar(100) NOT NULL,
|
||||
`title` varchar(512) NOT NULL,
|
||||
`related_id` varchar(100),
|
||||
`result` varchar(100) NOT NULL,
|
||||
`content` longtext ,
|
||||
`start_at` bigint NOT NULL,
|
||||
`end_at` bigint ,
|
||||
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE INDEX `index_job_history_user_id` ON `cd_job_history` (`user_id`);
|
||||
CREATE INDEX `index_job_history_project_id` ON `cd_job_history` (`project_id`);
|
||||
CREATE INDEX `index_job_history_type` ON `cd_job_history` (`type`);
|
||||
|
||||
ALTER TABLE `cd_job_history` ENGINE = InnoDB;
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
CREATE TABLE "cd_job_history"
|
||||
(
|
||||
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
"user_id" bigint NOT NULL,
|
||||
"project_id" bigint ,
|
||||
"type" varchar(100) NOT NULL,
|
||||
"title" varchar(512) NOT NULL,
|
||||
"related_id" varchar(100),
|
||||
"result" varchar(100) NOT NULL,
|
||||
"content" text ,
|
||||
"start_at" bigint NOT NULL,
|
||||
"end_at" bigint ,
|
||||
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE INDEX "index_job_history_user_id" ON "cd_job_history" ("user_id");
|
||||
CREATE INDEX "index_job_history_project_id" ON "cd_job_history" ("project_id");
|
||||
CREATE INDEX "index_job_history_type" ON "cd_job_history" ("type");
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
CREATE TABLE "cd_job_history"
|
||||
(
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"project_id" integer ,
|
||||
"type" varchar(100) NOT NULL,
|
||||
"title" varchar(512) NOT NULL,
|
||||
"related_id" varchar(100),
|
||||
"result" varchar(100) NOT NULL,
|
||||
"content" text ,
|
||||
"start_at" integer NOT NULL,
|
||||
"end_at" integer ,
|
||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE INDEX "index_job_history_user_id" ON "cd_job_history" ("user_id");
|
||||
CREATE INDEX "index_job_history_project_id" ON "cd_job_history" ("project_id");
|
||||
CREATE INDEX "index_job_history_type" ON "cd_job_history" ("type");
|
||||
32
packages/ui/certd-server/metadata/access_dnsmgr.yaml
Normal file
32
packages/ui/certd-server/metadata/access_dnsmgr.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
name: dnsmgr
|
||||
title: 彩虹DNS
|
||||
icon: clarity:plugin-line
|
||||
desc: 彩虹DNS管理系统授权
|
||||
input:
|
||||
endpoint:
|
||||
title: 系统地址
|
||||
component:
|
||||
name: a-input
|
||||
allowClear: true
|
||||
placeholder: https://dnsmgr.example.com
|
||||
required: true
|
||||
uid:
|
||||
title: 用户ID
|
||||
component:
|
||||
name: a-input
|
||||
allowClear: true
|
||||
placeholder: '123456'
|
||||
required: true
|
||||
key:
|
||||
title: API密钥
|
||||
required: true
|
||||
encrypt: true
|
||||
testRequest:
|
||||
title: 测试
|
||||
component:
|
||||
name: api-test
|
||||
action: TestRequest
|
||||
helper: 点击测试接口是否正常
|
||||
pluginType: access
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-dnsmgr/access.js
|
||||
29
packages/ui/certd-server/metadata/access_spaceship.yaml
Normal file
29
packages/ui/certd-server/metadata/access_spaceship.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: spaceship
|
||||
title: Spaceship.com 授权
|
||||
icon: clarity:plugin-line
|
||||
desc: Spaceship.com API 授权插件
|
||||
input:
|
||||
apiKey:
|
||||
title: API Key
|
||||
component:
|
||||
placeholder: 请输入 API Key
|
||||
required: true
|
||||
encrypt: true
|
||||
helper: 前往 [获取 API Key](https://www.spaceship.com/application/api-manager/)
|
||||
apiSecret:
|
||||
title: API Secret
|
||||
component:
|
||||
name: a-input-password
|
||||
vModel: value
|
||||
placeholder: 请输入 API Secret
|
||||
required: true
|
||||
encrypt: true
|
||||
testRequest:
|
||||
title: 测试
|
||||
component:
|
||||
name: api-test
|
||||
action: TestRequest
|
||||
helper: 测试 API 连接是否正常
|
||||
pluginType: access
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-spaceship/access.js
|
||||
@@ -7,6 +7,7 @@ title: 阿里云-部署证书至CDN
|
||||
icon: svg:icon-aliyun
|
||||
group: aliyun
|
||||
desc: 自动部署域名证书至阿里云CDN
|
||||
runStrategy: 0
|
||||
input:
|
||||
endpoint:
|
||||
title: 证书服务接入点
|
||||
@@ -59,36 +60,6 @@ input:
|
||||
type: aliyun
|
||||
required: true
|
||||
order: 0
|
||||
domainName:
|
||||
title: CDN加速域名
|
||||
component:
|
||||
name: remote-select
|
||||
vModel: value
|
||||
mode: tags
|
||||
type: plugin
|
||||
typeName: DeployCertToAliyunCDN
|
||||
action: onGetDomainList
|
||||
search: false
|
||||
pager: false
|
||||
multi: true
|
||||
watches:
|
||||
- certDomains
|
||||
- accessId
|
||||
- certDomains
|
||||
- accessId
|
||||
required: true
|
||||
mergeScript: |2-
|
||||
|
||||
return {
|
||||
component:{
|
||||
form: ctx.compute(({form})=>{
|
||||
return form
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
helper: 你在阿里云上配置的CDN加速域名,比如:certd.docmirror.cn
|
||||
order: 0
|
||||
certRegion:
|
||||
title: 证书所在地域
|
||||
helper: cn-hangzhou和ap-southeast-1,默认cn-hangzhou。国际站用户建议使用ap-southeast-1。
|
||||
@@ -106,7 +77,49 @@ input:
|
||||
title: 证书名称
|
||||
helper: 上传后将以此名称作为前缀备注
|
||||
order: 0
|
||||
output: {}
|
||||
domainMatchMode:
|
||||
title: 域名匹配模式
|
||||
helper: 根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署
|
||||
component:
|
||||
name: a-select
|
||||
options:
|
||||
- label: 手动选择
|
||||
value: manual
|
||||
- label: 根据证书匹配
|
||||
value: auto
|
||||
value: manual
|
||||
order: 0
|
||||
domainName:
|
||||
title: CDN加速域名
|
||||
component:
|
||||
name: remote-select
|
||||
vModel: value
|
||||
mode: tags
|
||||
type: plugin
|
||||
typeName: DeployCertToAliyunCDN
|
||||
action: onGetDomainList
|
||||
search: false
|
||||
pager: true
|
||||
multi: true
|
||||
watches:
|
||||
- certDomains
|
||||
- accessId
|
||||
- certDomains
|
||||
- accessId
|
||||
required: true
|
||||
mergeScript: |2-
|
||||
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.domainMatchMode === "manual"
|
||||
})
|
||||
}
|
||||
|
||||
helper: 你在阿里云上配置的CDN加速域名,比如:certd.docmirror.cn
|
||||
order: 0
|
||||
output:
|
||||
deployedList:
|
||||
title: 已部署过的DCDN加速域名
|
||||
pluginType: deploy
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-aliyun/plugin/deploy-to-cdn/index.js
|
||||
|
||||
@@ -7,6 +7,7 @@ title: 阿里云-部署证书至DCDN
|
||||
icon: svg:icon-aliyun
|
||||
group: aliyun
|
||||
desc: 依赖证书申请前置任务,自动部署域名证书至阿里云DCDN
|
||||
runStrategy: 0
|
||||
input:
|
||||
cert:
|
||||
title: 域名证书
|
||||
@@ -47,6 +48,18 @@ input:
|
||||
title: 证书名称
|
||||
helper: 上传后将以此名称作为前缀备注
|
||||
order: 0
|
||||
domainMatchMode:
|
||||
title: 域名匹配模式
|
||||
helper: 根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署
|
||||
component:
|
||||
name: a-select
|
||||
options:
|
||||
- label: 手动选择
|
||||
value: manual
|
||||
- label: 根据证书匹配
|
||||
value: auto
|
||||
value: manual
|
||||
order: 0
|
||||
domainName:
|
||||
title: DCDN加速域名
|
||||
component:
|
||||
@@ -56,7 +69,7 @@ input:
|
||||
type: plugin
|
||||
action: onGetDomainList
|
||||
search: false
|
||||
pager: false
|
||||
pager: true
|
||||
multi: true
|
||||
watches:
|
||||
- certDomains
|
||||
@@ -66,17 +79,17 @@ input:
|
||||
required: true
|
||||
mergeScript: |2-
|
||||
|
||||
return {
|
||||
component:{
|
||||
form: ctx.compute(({form})=>{
|
||||
return form
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.domainMatchMode === "manual"
|
||||
})
|
||||
}
|
||||
|
||||
helper: 你在阿里云上配置的DCDN加速域名,比如:certd.docmirror.cn
|
||||
order: 0
|
||||
output: {}
|
||||
output:
|
||||
deployedList:
|
||||
title: 已部署过的DCDN加速域名
|
||||
pluginType: deploy
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.js
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
showRunStrategy: false
|
||||
default:
|
||||
strategy:
|
||||
runStrategy: 1
|
||||
name: DeployCertToBaiduCce
|
||||
title: 百度云-部署到CCE
|
||||
icon: ant-design:cloud-outlined
|
||||
desc: 部署到百度云CCE集群Ingress等通过Secret管理证书的应用
|
||||
group: baidu
|
||||
needPlus: true
|
||||
input:
|
||||
cert:
|
||||
title: 域名证书
|
||||
helper: 请选择前置任务输出的域名证书
|
||||
component:
|
||||
name: output-selector
|
||||
from:
|
||||
- ':cert:'
|
||||
required: true
|
||||
order: 0
|
||||
accessId:
|
||||
title: Access授权
|
||||
helper: 百度云授权AccessKey、SecretKey
|
||||
component:
|
||||
name: access-selector
|
||||
type: baidu
|
||||
required: true
|
||||
order: 0
|
||||
regionId:
|
||||
title: 大区
|
||||
component:
|
||||
name: a-auto-complete
|
||||
vModel: value
|
||||
options:
|
||||
- value: bj
|
||||
label: 北京
|
||||
- value: gz
|
||||
label: 广州
|
||||
- value: su
|
||||
label: 苏州
|
||||
- value: bd
|
||||
label: 保定
|
||||
- value: fwh
|
||||
label: 武汉
|
||||
- value: hkg
|
||||
label: 香港
|
||||
- value: yq
|
||||
label: 阳泉
|
||||
- value: cd
|
||||
label: 成都
|
||||
- value: nj
|
||||
label: 南京
|
||||
placeholder: 集群所属大区
|
||||
required: true
|
||||
order: 0
|
||||
clusterId:
|
||||
title: 集群id
|
||||
component:
|
||||
placeholder: 集群id
|
||||
required: true
|
||||
order: 0
|
||||
secretName:
|
||||
title: 保密字典Id
|
||||
component:
|
||||
placeholder: 保密字典Id
|
||||
helper: 原本存储证书的secret的name
|
||||
required: true
|
||||
order: 0
|
||||
namespace:
|
||||
title: 命名空间
|
||||
value: default
|
||||
component:
|
||||
placeholder: 命名空间
|
||||
required: true
|
||||
order: 0
|
||||
kubeconfigType:
|
||||
title: Kubeconfig类型
|
||||
value: public
|
||||
component:
|
||||
name: a-auto-complete
|
||||
vModel: value
|
||||
options:
|
||||
- value: vpc
|
||||
label: VPC私网IP (BLB VPCIP)
|
||||
- value: public
|
||||
label: 公网IP (BLB EIP)
|
||||
placeholder: 选择集群连接端点类型
|
||||
helper: VPC类型使用私网IP连接,需要certd运行在同一网络环境;public类型使用公网IP连接
|
||||
required: true
|
||||
order: 0
|
||||
skipTLSVerify:
|
||||
title: 忽略证书校验
|
||||
required: false
|
||||
helper: 是否忽略证书校验
|
||||
component:
|
||||
name: a-switch
|
||||
vModel: checked
|
||||
order: 0
|
||||
createOnNotFound:
|
||||
title: Secret自动创建
|
||||
helper: 如果Secret不存在,则创建,百度云的自动创建secret有问题
|
||||
value: false
|
||||
component:
|
||||
name: a-switch
|
||||
vModel: checked
|
||||
order: 0
|
||||
output: {}
|
||||
pluginType: deploy
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-plus/baidu/plugins/plugin-deploy-to-cce.js
|
||||
@@ -8,6 +8,24 @@ icon: svg:icon-tencentcloud
|
||||
group: tencent
|
||||
desc: 暂时只支持单向认证证书,暂时只支持通用负载均衡
|
||||
input:
|
||||
cert:
|
||||
title: 域名证书
|
||||
helper: 请选择前置任务输出的域名证书
|
||||
component:
|
||||
name: output-selector
|
||||
from:
|
||||
- ':cert:'
|
||||
- UploadCertToTencent
|
||||
required: true
|
||||
order: 0
|
||||
accessId:
|
||||
title: Access提供者
|
||||
helper: access授权
|
||||
component:
|
||||
name: access-selector
|
||||
type: tencent
|
||||
required: true
|
||||
order: 0
|
||||
region:
|
||||
title: 大区
|
||||
component:
|
||||
@@ -33,14 +51,12 @@ input:
|
||||
- value: na-siliconvalley
|
||||
- value: na-toronto
|
||||
- value: sa-saopaulo
|
||||
- value: ap-taipei
|
||||
helper: 如果列表中没有,您可以手动输入
|
||||
required: true
|
||||
order: 0
|
||||
certName:
|
||||
title: 证书名称前缀
|
||||
order: 0
|
||||
loadBalancerId:
|
||||
title: 负载均衡ID
|
||||
helper: 如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)
|
||||
required: true
|
||||
order: 0
|
||||
listenerId:
|
||||
@@ -57,22 +73,8 @@ input:
|
||||
mode: tags
|
||||
helper: 如果开启了sni,则此项必须填写,未开启,则不要填写
|
||||
order: 0
|
||||
cert:
|
||||
title: 域名证书
|
||||
helper: 请选择前置任务输出的域名证书
|
||||
component:
|
||||
name: output-selector
|
||||
from:
|
||||
- ':cert:'
|
||||
required: true
|
||||
order: 0
|
||||
accessId:
|
||||
title: Access提供者
|
||||
helper: access授权
|
||||
component:
|
||||
name: access-selector
|
||||
type: tencent
|
||||
required: true
|
||||
certName:
|
||||
title: 证书名称前缀
|
||||
order: 0
|
||||
output: {}
|
||||
pluginType: deploy
|
||||
|
||||
@@ -35,6 +35,19 @@ input:
|
||||
type: k8s
|
||||
required: true
|
||||
order: 0
|
||||
strategy:
|
||||
title: 应用策略
|
||||
helper: 选择使用apply(创建或更新)还是patch(补丁更新)
|
||||
component:
|
||||
name: a-select
|
||||
options:
|
||||
- label: apply(创建)
|
||||
value: apply
|
||||
- label: patch(更新)
|
||||
value: patch
|
||||
value: apply
|
||||
required: true
|
||||
order: 0
|
||||
yamlContent:
|
||||
title: yaml
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
name: dnsmgr
|
||||
title: 彩虹DNS
|
||||
desc: 彩虹DNS管理系统
|
||||
icon: clarity:plugin-line
|
||||
accessType: dnsmgr
|
||||
order: 99
|
||||
pluginType: dnsProvider
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-dnsmgr/dns-provider.js
|
||||
@@ -0,0 +1,9 @@
|
||||
name: spaceship
|
||||
title: Spaceship
|
||||
desc: Spaceship 域名解析
|
||||
icon: clarity:plugin-line
|
||||
accessType: spaceship
|
||||
order: 99
|
||||
pluginType: dnsProvider
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-spaceship/dns-provider.js
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.39.7",
|
||||
"version": "1.39.9",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -50,20 +50,20 @@
|
||||
"@aws-sdk/client-route-53": "^3.964.0",
|
||||
"@aws-sdk/client-s3": "^3.964.0",
|
||||
"@aws-sdk/client-sts": "^3.990.0",
|
||||
"@certd/acme-client": "^1.39.7",
|
||||
"@certd/basic": "^1.39.7",
|
||||
"@certd/commercial-core": "^1.39.7",
|
||||
"@certd/acme-client": "^1.39.9",
|
||||
"@certd/basic": "^1.39.9",
|
||||
"@certd/commercial-core": "^1.39.9",
|
||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||
"@certd/jdcloud": "^1.39.7",
|
||||
"@certd/lib-huawei": "^1.39.7",
|
||||
"@certd/lib-k8s": "^1.39.7",
|
||||
"@certd/lib-server": "^1.39.7",
|
||||
"@certd/midway-flyway-js": "^1.39.7",
|
||||
"@certd/pipeline": "^1.39.7",
|
||||
"@certd/plugin-cert": "^1.39.7",
|
||||
"@certd/plugin-lib": "^1.39.7",
|
||||
"@certd/plugin-plus": "^1.39.7",
|
||||
"@certd/plus-core": "^1.39.7",
|
||||
"@certd/jdcloud": "^1.39.9",
|
||||
"@certd/lib-huawei": "^1.39.9",
|
||||
"@certd/lib-k8s": "^1.39.9",
|
||||
"@certd/lib-server": "^1.39.9",
|
||||
"@certd/midway-flyway-js": "^1.39.9",
|
||||
"@certd/pipeline": "^1.39.9",
|
||||
"@certd/plugin-cert": "^1.39.9",
|
||||
"@certd/plugin-lib": "^1.39.9",
|
||||
"@certd/plugin-plus": "^1.39.9",
|
||||
"@certd/plus-core": "^1.39.9",
|
||||
"@google-cloud/publicca": "^1.3.0",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.185",
|
||||
@@ -141,6 +141,7 @@
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^10.0.0",
|
||||
"wechatpay-node-v3": "^2.2.1",
|
||||
"whoiser": "2.0.0-beta.10",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -20,7 +20,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
|
||||
@Post('/page', { description: Constants.per.authOnly, summary: "查询域名分页列表" })
|
||||
async page(@Body(ALL) body: any) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
body.query = body.query ?? {};
|
||||
body.query.projectId = projectId;
|
||||
body.query.userId = userId;
|
||||
@@ -44,7 +44,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
|
||||
@Post('/list', { description: Constants.per.authOnly, summary: "查询域名列表" })
|
||||
async list(@Body(ALL) body: any) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
body.query = body.query ?? {};
|
||||
body.query.projectId = projectId;
|
||||
body.query.userId = userId;
|
||||
@@ -54,7 +54,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
|
||||
@Post('/add', { description: Constants.per.authOnly, summary: "添加域名" })
|
||||
async add(@Body(ALL) bean: any) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
bean.projectId = projectId;
|
||||
bean.userId = userId;
|
||||
return super.add(bean);
|
||||
@@ -82,7 +82,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
|
||||
@Post('/deleteByIds', { description: Constants.per.authOnly, summary: "批量删除域名" })
|
||||
async deleteByIds(@Body(ALL) body: any) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
await this.service.delete(body.ids, {
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
@@ -94,10 +94,10 @@ export class DomainController extends CrudController<DomainService> {
|
||||
@Post('/import/start', { description: Constants.per.authOnly, summary: "开始域名导入任务" })
|
||||
async importStart(@Body(ALL) body: any) {
|
||||
checkPlus();
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
const { key } = body;
|
||||
const req = {
|
||||
key,
|
||||
key,
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
|
||||
@Post('/import/status', { description: Constants.per.authOnly, summary: "查询域名导入任务状态" })
|
||||
async importStatus() {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
const req = {
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
@@ -119,7 +119,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
|
||||
@Post('/import/delete', { description: Constants.per.authOnly, summary: "删除域名导入任务" })
|
||||
async importDelete(@Body(ALL) body: any) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
const { key } = body;
|
||||
const req = {
|
||||
userId: userId,
|
||||
@@ -133,12 +133,12 @@ export class DomainController extends CrudController<DomainService> {
|
||||
@Post('/import/save', { description: Constants.per.authOnly, summary: "保存域名导入任务" })
|
||||
async importSave(@Body(ALL) body: any) {
|
||||
checkPlus();
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
const { dnsProviderType, dnsProviderAccessId, key } = body;
|
||||
const req = {
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
dnsProviderType, dnsProviderAccessId, key
|
||||
dnsProviderType, dnsProviderAccessId, key
|
||||
}
|
||||
const item = await this.service.saveDomainImportTask(req);
|
||||
return this.ok(item);
|
||||
@@ -147,7 +147,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
|
||||
@Post('/sync/expiration/start', { description: Constants.per.authOnly, summary: "开始同步域名过期时间任务" })
|
||||
async syncExpirationStart(@Body(ALL) body: any) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
await this.service.startSyncExpirationTask({
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
@@ -156,7 +156,7 @@ export class DomainController extends CrudController<DomainService> {
|
||||
}
|
||||
@Post('/sync/expiration/status', { description: Constants.per.authOnly, summary: "查询同步域名过期时间任务状态" })
|
||||
async syncExpirationStatus(@Body(ALL) body: any) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead();
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
const status = await this.service.getSyncExpirationTaskStatus({
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
@@ -165,4 +165,26 @@ export class DomainController extends CrudController<DomainService> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Post('/setting/save', { description: Constants.per.authOnly, summary: "保存域名监控设置" })
|
||||
async settingSave(@Body(ALL) body: any) {
|
||||
const { projectId, userId } = await this.getProjectUserIdWrite();
|
||||
await this.service.monitorSettingSave({
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
setting: {...body},
|
||||
})
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
@Post('/setting/get', { description: Constants.per.authOnly, summary: "查询域名监控设置" })
|
||||
async settingGet() {
|
||||
const { projectId, userId } = await this.getProjectUserIdRead();
|
||||
const setting = await this.service.monitorSettingGet({
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
})
|
||||
return this.ok(setting);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export class UserTwoFactorSettingController extends BaseController {
|
||||
@Post("/save", { description: Constants.per.authOnly, summary: "保存双因子认证设置" })
|
||||
async save(@Body(ALL) bean: any) {
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通专业版')
|
||||
throw new Error('本功能需要开通Certd专业版')
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
const setting = new UserTwoFactorSetting();
|
||||
@@ -57,7 +57,7 @@ export class UserTwoFactorSettingController extends BaseController {
|
||||
@Post("/authenticator/save", { description: Constants.per.authOnly, summary: "保存验证器设置" })
|
||||
async authenticatorSave(@Body(ALL) bean: any) {
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通专业版')
|
||||
throw new Error('本功能需要开通Certd专业版')
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
await this.twoFactorService.saveAuthenticator({
|
||||
|
||||
@@ -81,7 +81,7 @@ export class UserSettingsController extends CrudController<UserSettingsService>
|
||||
@Post("/grant/save", { description: Constants.per.authOnly, summary: "保存授权设置" })
|
||||
async grantSettingsSave(@Body(ALL) bean: UserGrantSetting) {
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通专业版')
|
||||
throw new Error('本功能需要开通Certd专业版')
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
const setting = new UserGrantSetting();
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Constants, CrudController } from "@certd/lib-server";
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||
import { ApiTags } from "@midwayjs/swagger";
|
||||
import { SiteInfoService } from "../../../modules/monitor/index.js";
|
||||
import { JobHistoryService } from "../../../modules/monitor/service/job-history-service.js";
|
||||
import { AuthService } from "../../../modules/sys/authority/service/auth-service.js";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/monitor/job-history')
|
||||
@ApiTags(['monitor'])
|
||||
export class JobHistoryController extends CrudController<JobHistoryService> {
|
||||
@Inject()
|
||||
service: JobHistoryService;
|
||||
@Inject()
|
||||
authService: AuthService;
|
||||
@Inject()
|
||||
siteInfoService: SiteInfoService;
|
||||
|
||||
getService(): JobHistoryService {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
@Post('/page', { description: Constants.per.authOnly, summary: "查询监控运行历史分页列表" })
|
||||
async page(@Body(ALL) body: any) {
|
||||
const { projectId, userId } = await this.getProjectUserIdRead()
|
||||
body.query = body.query ?? {};
|
||||
body.query.userId = userId;
|
||||
body.query.projectId = projectId
|
||||
const res = await this.service.page({
|
||||
query: body.query,
|
||||
page: body.page,
|
||||
sort: body.sort,
|
||||
});
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@Post('/list', { description: Constants.per.authOnly, summary: "查询监控运行历史列表" })
|
||||
async list(@Body(ALL) body: any) {
|
||||
body.query = body.query ?? {};
|
||||
const { projectId, userId } = await this.getProjectUserIdRead()
|
||||
body.query.userId = userId;
|
||||
body.query.projectId = projectId
|
||||
return await super.list(body);
|
||||
}
|
||||
|
||||
@Post('/info', { description: Constants.per.authOnly, summary: "查询监控运行历史详情" })
|
||||
async info(@Query('id') id: number) {
|
||||
await this.checkOwner(this.service,id,"read");
|
||||
return await super.info(id);
|
||||
}
|
||||
|
||||
@Post('/delete', { description: Constants.per.authOnly, summary: "删除监控运行历史" })
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.checkOwner(this.service,id,"write");
|
||||
return await super.delete(id);
|
||||
}
|
||||
@Post('/batchDelete', { description: Constants.per.authOnly, summary: "批量删除监控运行历史" })
|
||||
async batchDelete(@Body('ids') ids: number[]) {
|
||||
const { projectId, userId } = await this.getProjectUserIdWrite()
|
||||
await this.service.batchDelete(ids,userId,projectId);
|
||||
return this.ok();
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ export class AutoCRegisterCron {
|
||||
await this.registerUserExpireCheckCron();
|
||||
|
||||
await this.registerDomainExpireCheckCron();
|
||||
|
||||
}
|
||||
|
||||
async registerSiteMonitorCron() {
|
||||
@@ -211,11 +212,11 @@ export class AutoCRegisterCron {
|
||||
if (!isPlus()){
|
||||
return
|
||||
}
|
||||
// 添加域名即将到期检查任务
|
||||
// 添加域名即将到期同步任务
|
||||
const randomWeek = Math.floor(Math.random() * 7) + 1
|
||||
const randomHour = Math.floor(Math.random() * 24)
|
||||
const randomMinute = Math.floor(Math.random() * 60)
|
||||
logger.info(`注册域名注册过期时间检查任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`)
|
||||
logger.info(`注册域名注册过期时间同步任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`)
|
||||
this.cron.register({
|
||||
name: 'domain-expire-check',
|
||||
cron: `0 ${randomMinute} ${randomHour} ? * ${randomWeek}`, // 每周随机一天检查一次
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { http, logger, utils } from '@certd/basic';
|
||||
import { AccessService, BaseService } from '@certd/lib-server';
|
||||
import { AccessService, BaseService, isEnterprise } from '@certd/lib-server';
|
||||
import { doPageTurn, Pager, PageRes } from '@certd/pipeline';
|
||||
import { DomainVerifiers } from "@certd/plugin-cert";
|
||||
import { createDnsProvider, dnsProviderRegistry, DomainParser, parseDomainByPsl } from "@certd/plugin-lib";
|
||||
import { createDnsProvider, dnsProviderRegistry, DomainParser } from "@certd/plugin-lib";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import dayjs from 'dayjs';
|
||||
import { In, Not, Repository } from 'typeorm';
|
||||
import { merge } from 'lodash-es';
|
||||
import { In, LessThan, Not, Repository } from 'typeorm';
|
||||
import { BackTask, taskExecutor } from '../../basic/service/task-executor.js';
|
||||
import { CnameRecordEntity } from "../../cname/entity/cname-record.js";
|
||||
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
||||
import { UserDomainImportSetting } from '../../mine/service/models.js';
|
||||
import { Cron } from '../../cron/cron.js';
|
||||
import { UserDomainImportSetting, UserDomainMonitorSetting } from '../../mine/service/models.js';
|
||||
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
||||
import { JobHistoryService } from '../../monitor/service/job-history-service.js';
|
||||
import { TaskServiceBuilder } from '../../pipeline/service/getter/task-service-getter.js';
|
||||
import { SubDomainService } from "../../pipeline/service/sub-domain-service.js";
|
||||
import { DomainEntity } from '../entity/domain.js';
|
||||
import { TldClient } from './tld-client.js';
|
||||
|
||||
export interface SyncFromProviderReq {
|
||||
userId: number;
|
||||
@@ -27,6 +31,8 @@ export interface SyncFromProviderReq {
|
||||
const DOMAIN_IMPORT_TASK_TYPE = 'domainImportTask'
|
||||
const DOMAIN_EXPIRE_TASK_TYPE = 'domainExpirationSyncTask'
|
||||
|
||||
const DOMAIN_EXPIRE_CHECK_TYPE = 'domainExpirationCheck'
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -51,6 +57,14 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
@Inject()
|
||||
userSettingService: UserSettingsService;
|
||||
|
||||
@Inject()
|
||||
jobHistoryService: JobHistoryService;
|
||||
|
||||
@Inject()
|
||||
cron: Cron;
|
||||
|
||||
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
@@ -320,9 +334,9 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
logger.info(`从域名提供商${dnsProviderType}导入域名完成(${key}),共导入${task.total}个域名,跳过${task.getSkipCount()}个域名,成功${task.getSuccessCount()}个域名,失败${task.getErrorCount()}个域名`)
|
||||
}
|
||||
|
||||
async getDomainImportTaskStatus(req: { userId?: number ,projectId?: number}) {
|
||||
async getDomainImportTaskStatus(req: { userId?: number, projectId?: number }) {
|
||||
const userId = req.userId || 0
|
||||
const projectId = req.projectId
|
||||
const projectId = req.projectId
|
||||
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting)
|
||||
const list = setting?.domainImportList || []
|
||||
@@ -429,8 +443,6 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
await this.deleteDomainImportTask({ userId, projectId, key })
|
||||
}
|
||||
|
||||
|
||||
|
||||
return await this.addDomainImportTask({ userId, projectId, dnsProviderType, dnsProviderAccessId, index })
|
||||
}
|
||||
|
||||
@@ -441,7 +453,7 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
const userId = req.userId ?? 'all'
|
||||
const projectId = req.projectId
|
||||
let key = `user_${userId}`
|
||||
if (projectId!=null) {
|
||||
if (projectId != null) {
|
||||
key += `_${projectId}`
|
||||
}
|
||||
const task = taskExecutor.get(DOMAIN_EXPIRE_TASK_TYPE, key)
|
||||
@@ -452,7 +464,7 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
const userId = req.userId
|
||||
const projectId = req.projectId
|
||||
let key = `user_${userId ?? 'all'}`
|
||||
if (projectId!=null) {
|
||||
if (projectId != null) {
|
||||
key += `_${projectId}`
|
||||
}
|
||||
taskExecutor.start(new BackTask({
|
||||
@@ -461,61 +473,22 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
title: `同步注册域名过期时间(${key}))`,
|
||||
run: async (task: BackTask) => {
|
||||
await this._syncDomainsExpirationDate({ userId, projectId, task })
|
||||
if (userId != null) {
|
||||
await this.startCheckDomainExpiration({ userId, projectId })
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private async _syncDomainsExpirationDate(req: { userId?: number, projectId?: number, task: BackTask }) {
|
||||
|
||||
//同步所有域名的过期时间
|
||||
const pager = new Pager({
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
})
|
||||
|
||||
const dnsJson = await http.request({
|
||||
url: "https://data.iana.org/rdap/dns.json",
|
||||
method: "GET",
|
||||
})
|
||||
const rdapMap: Record<string, string> = {}
|
||||
for (const item of dnsJson.services) {
|
||||
// [["store","work"], ["https://rdap.centralnic.com/store/"]],
|
||||
const suffixes = item[0]
|
||||
const urls = item[1]
|
||||
for (const suffix of suffixes) {
|
||||
rdapMap[suffix] = urls[0]
|
||||
}
|
||||
}
|
||||
|
||||
const getDomainExpirationDate = async (domain: string) => {
|
||||
const parsed = parseDomainByPsl(domain)
|
||||
const mainDomain = parsed.domain || ''
|
||||
if (mainDomain !== domain) {
|
||||
req.task.addError(`【${domain}】为子域名,跳过同步`)
|
||||
return
|
||||
}
|
||||
const suffix = parsed.tld || ''
|
||||
const rdapUrl = rdapMap[suffix]
|
||||
if (!rdapUrl) {
|
||||
req.task.addError(`【${domain}】未找到${suffix}的rdap地址`)
|
||||
return
|
||||
}
|
||||
// https://rdap.nic.work/domain/handsfree.work
|
||||
const rdap = await http.request({
|
||||
url: `${rdapUrl}domain/${domain}`,
|
||||
method: "GET",
|
||||
})
|
||||
|
||||
let res: any = {}
|
||||
const events = rdap.events || []
|
||||
for (const item of events) {
|
||||
if (item.eventAction === 'expiration') {
|
||||
res.expirationDate = dayjs(item.eventDate).valueOf()
|
||||
} else if (item.eventAction === 'registration') {
|
||||
res.registrationDate = dayjs(item.eventDate).valueOf()
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
const tldClient = new TldClient();
|
||||
const query: any = {
|
||||
challengeType: "dns",
|
||||
}
|
||||
@@ -546,10 +519,7 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
const itemHandle = async (item: any) => {
|
||||
req.task.incrementCurrent()
|
||||
try {
|
||||
const res = await getDomainExpirationDate(item.domain)
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
const res = await tldClient.getDomainExpirationDate(item.domain)
|
||||
const { expirationDate, registrationDate } = res
|
||||
if (!expirationDate) {
|
||||
req.task.addError(`【${item.domain}】获取域名${item.domain}过期时间失败`)
|
||||
@@ -566,6 +536,7 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
} catch (error) {
|
||||
const errorMsg = `【${item.domain}】${error.message ?? error}`
|
||||
req.task.addError(errorMsg)
|
||||
logger.error(errorMsg)
|
||||
} finally {
|
||||
await utils.sleep(1000)
|
||||
}
|
||||
@@ -573,7 +544,151 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
|
||||
await doPageTurn({ pager, getPage: getDomainPage, itemHandle: itemHandle })
|
||||
const key = `user_${req.userId || 'all'}`
|
||||
logger.info(`同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)`)
|
||||
const log = `同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)`
|
||||
logger.info(log)
|
||||
}
|
||||
|
||||
|
||||
public async startCheckDomainExpiration(req: { userId?: number, projectId?: number }) {
|
||||
const { userId, projectId } = req
|
||||
if (userId == null) {
|
||||
throw new Error('userId is required');
|
||||
}
|
||||
|
||||
if (projectId && !isEnterprise()) {
|
||||
logger.warn(`当前未开启企业模式,跳过检查项目(${projectId})的域名过期时间`)
|
||||
return
|
||||
}
|
||||
|
||||
const setting = await this.monitorSettingGet({ userId, projectId })
|
||||
if (!setting || !setting.enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const jobHistory: any = {
|
||||
userId,
|
||||
projectId,
|
||||
type: DOMAIN_EXPIRE_CHECK_TYPE,
|
||||
title: `检查注册域名过期时间`,
|
||||
startAt: dayjs().valueOf(),
|
||||
result: "start",
|
||||
}
|
||||
await this.jobHistoryService.add(jobHistory)
|
||||
|
||||
const expireDays = setting.willExpireDays || 30
|
||||
const ltTime = dayjs().add(expireDays, 'day').valueOf()
|
||||
|
||||
const total = await this.repository.count({
|
||||
where:{
|
||||
userId,
|
||||
projectId,
|
||||
disabled: false,
|
||||
}
|
||||
})
|
||||
//开始检查域名过期时间
|
||||
const list = await this.repository.find({
|
||||
where: {
|
||||
userId,
|
||||
projectId,
|
||||
disabled: false,
|
||||
expirationDate: LessThan(ltTime)
|
||||
}
|
||||
})
|
||||
|
||||
const now = dayjs().valueOf()
|
||||
let willExpireDomains = []
|
||||
let hasExpireDomains = []
|
||||
|
||||
for (const item of list) {
|
||||
const { expirationDate } = item
|
||||
const leftDays = dayjs(expirationDate).diff(dayjs(), 'day')
|
||||
//@ts-ignore
|
||||
item.leftDays = leftDays
|
||||
if (expirationDate < now) {
|
||||
hasExpireDomains.push(item)
|
||||
} else {
|
||||
willExpireDomains.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
const title = `域名过期检查:即将过期 ${willExpireDomains.length} 个域名,已过期 ${hasExpireDomains.length} 个域名,共 ${total} 个域名`
|
||||
|
||||
try {
|
||||
await this.jobHistoryService.update({
|
||||
id: jobHistory.id,
|
||||
content: title,
|
||||
result: "done",
|
||||
endAt: dayjs().valueOf(),
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`更新域名过期检查任务状态失败:${error.message ?? error}`)
|
||||
}
|
||||
|
||||
if (list.length == 0) {
|
||||
//没有过期域名 不发通知
|
||||
return
|
||||
}
|
||||
|
||||
//发送通知
|
||||
const willExpireDomainsStr = willExpireDomains.map(item => `${item.domain} (剩余${item.leftDays}天)`).join('\n ')
|
||||
const hasExpireDomainsStr = hasExpireDomains.map(item => `${item.domain} (已过期${item.leftDays}天)`).join('\n ')
|
||||
const content = `您有域名即将过期,请尽快续费
|
||||
|
||||
即将过期域名: ${willExpireDomains.length} 个 (有效期<${expireDays}天)
|
||||
${willExpireDomainsStr}
|
||||
|
||||
已过期域名: ${hasExpireDomains.length} 个
|
||||
${hasExpireDomainsStr}`
|
||||
const taskService = this.taskServiceBuilder.create({ userId: userId, projectId: projectId });
|
||||
|
||||
const notificationService = await taskService.getNotificationService()
|
||||
const url = await notificationService.getBindUrl("#/certd/cert/domain");
|
||||
await notificationService.send({
|
||||
id: setting.notificationId,
|
||||
useDefault: true,
|
||||
logger: logger,
|
||||
body: {
|
||||
title: title,
|
||||
content: content,
|
||||
url: url,
|
||||
errorMessage: title,
|
||||
notificationType: DOMAIN_EXPIRE_CHECK_TYPE,
|
||||
willExpireDomains,
|
||||
hasExpireDomains,
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async monitorSettingGet(req: { userId?: number, projectId?: number }) {
|
||||
const { userId, projectId } = req
|
||||
const setting = await this.userSettingService.getSetting<UserDomainMonitorSetting>(userId, projectId, UserDomainMonitorSetting)
|
||||
return setting || {}
|
||||
}
|
||||
|
||||
public async monitorSettingSave(req: { userId?: number, projectId?: number, setting?: any }) {
|
||||
const { userId, projectId, setting } = req
|
||||
const bean: UserDomainMonitorSetting = new UserDomainMonitorSetting()
|
||||
merge(bean, setting)
|
||||
await this.userSettingService.saveSetting<UserDomainMonitorSetting>(userId, projectId, bean)
|
||||
await this.registerMonitorCron({ userId, projectId })
|
||||
}
|
||||
|
||||
public async registerMonitorCron(req: { userId?: number, projectId?: number }) {
|
||||
const { userId, projectId } = req
|
||||
const setting = await this.monitorSettingGet(req)
|
||||
const key = `${DOMAIN_EXPIRE_CHECK_TYPE}:${userId}_${projectId || ''}`
|
||||
this.cron.remove(key)
|
||||
if (setting.enabled) {
|
||||
this.cron.register({
|
||||
cron: setting.cron,
|
||||
name: key,
|
||||
job: async () => {
|
||||
await this.startCheckDomainExpiration({ userId, projectId })
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
128
packages/ui/certd-server/src/modules/cert/service/tld-client.ts
Normal file
128
packages/ui/certd-server/src/modules/cert/service/tld-client.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
import { http, logger } from '@certd/basic';
|
||||
import { parseDomainByPsl } from "@certd/plugin-lib";
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export interface DomainInfo {
|
||||
expirationDate?: number;
|
||||
registrationDate?: number;
|
||||
}
|
||||
|
||||
export class TldClient {
|
||||
private rdapMap: Record<string, string> = {}
|
||||
private isInitialized = false;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
const dnsJson = await http.request({
|
||||
url: "https://data.iana.org/rdap/dns.json",
|
||||
method: "GET",
|
||||
})
|
||||
for (const item of dnsJson.services) {
|
||||
const suffixes = item[0]
|
||||
const urls = item[1]
|
||||
for (const suffix of suffixes) {
|
||||
this.rdapMap[suffix] = urls[0]
|
||||
}
|
||||
}
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
async getDomainExpirationDate(domain: string): Promise<DomainInfo> {
|
||||
await this.init();
|
||||
|
||||
const parsed = parseDomainByPsl(domain)
|
||||
const mainDomain = parsed.domain || ''
|
||||
if (mainDomain !== domain) {
|
||||
const message= `【${domain}】为子域名,无法获取过期时间`
|
||||
logger.warn(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.getDomainExpirationByRdap(domain, parsed.tld || '')
|
||||
} catch (error) {
|
||||
logger.error(error.message)
|
||||
return await this.getDomainExpirationByWhoiser(domain)
|
||||
}
|
||||
}
|
||||
|
||||
private async getDomainExpirationByRdap(domain: string, suffix: string): Promise<DomainInfo> {
|
||||
const rdapUrl = this.rdapMap[suffix]
|
||||
if (!rdapUrl) {
|
||||
throw new Error(`【${domain}】未找到${suffix}的rdap地址`)
|
||||
}
|
||||
|
||||
const rdap = await http.request({
|
||||
url: `${rdapUrl}domain/${domain}`,
|
||||
method: "GET",
|
||||
})
|
||||
|
||||
let res: DomainInfo = {}
|
||||
const events = rdap.events || []
|
||||
for (const item of events) {
|
||||
if (item.eventAction === 'expiration') {
|
||||
res.expirationDate = dayjs(item.eventDate).valueOf()
|
||||
} else if (item.eventAction === 'registration') {
|
||||
res.registrationDate = dayjs(item.eventDate).valueOf()
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private async getDomainExpirationByWhoiser(domain: string): Promise<DomainInfo> {
|
||||
const whoiser = await import("whoiser")
|
||||
const result = await whoiser.whoisDomain(domain, {
|
||||
follow: 2,
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
let res: DomainInfo = {}
|
||||
/**
|
||||
* {
|
||||
"Domain Status": [
|
||||
"ok",
|
||||
],
|
||||
"Name Server": [
|
||||
"dns21.hichina.com",
|
||||
"dns22.hichina.com",
|
||||
],
|
||||
text: [
|
||||
"",
|
||||
],
|
||||
"Domain Name": "docmirror.cn",
|
||||
ROID: "20200907s10001s31265717-cn",
|
||||
"Registrant Name": "肖君诺",
|
||||
"Registrant Email": "252959493@qq.com",
|
||||
Registrar: "阿里巴巴云计算(北京)有限公司",
|
||||
"Created Date": "2020-09-07 09:22:54",
|
||||
"Expiry Date": "2026-09-07 09:22:54",
|
||||
DNSSEC: "unsigned",
|
||||
}
|
||||
*/
|
||||
|
||||
for (const server in result) {
|
||||
const data = result[server] as any
|
||||
if (data['Expiry Date']) {
|
||||
res.expirationDate = dayjs(data['Expiry Date']).valueOf()
|
||||
}
|
||||
if (data['Created Date']) {
|
||||
res.registrationDate = dayjs(data['Created Date']).valueOf()
|
||||
}
|
||||
if (res.expirationDate && res.registrationDate) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!res.expirationDate) {
|
||||
throw new Error(`【${domain}】whois查询未找到过期时间`)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Config, Configuration, Logger } from '@midwayjs/core';
|
||||
import { ILogger } from '@midwayjs/logger';
|
||||
import { IMidwayContainer } from '@midwayjs/core';
|
||||
import { logger } from '@certd/basic';
|
||||
import { Config, Configuration, IMidwayContainer } from '@midwayjs/core';
|
||||
import { Cron } from './cron.js';
|
||||
|
||||
// ... (see below) ...
|
||||
@@ -11,18 +10,15 @@ import { Cron } from './cron.js';
|
||||
export class CronConfiguration {
|
||||
@Config()
|
||||
config;
|
||||
@Logger()
|
||||
logger: ILogger;
|
||||
|
||||
cron: Cron;
|
||||
async onReady(container: IMidwayContainer) {
|
||||
this.logger.info('cron start');
|
||||
logger.info('cron start');
|
||||
this.cron = new Cron({
|
||||
logger: this.logger,
|
||||
logger: logger,
|
||||
...this.config,
|
||||
});
|
||||
container.registerObject('cron', this.cron);
|
||||
this.cron.start();
|
||||
this.logger.info('cron started');
|
||||
logger.info('cron started');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ export class LoginService {
|
||||
async loginByTwoFactor(req: { loginId: string; verifyCode: string }) {
|
||||
//检查是否开启多重认证
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通专业版')
|
||||
throw new Error('本功能需要开通Certd专业版')
|
||||
}
|
||||
const userId = cache.get(`login_2fa_code:${req.loginId}`)
|
||||
if (!userId) {
|
||||
|
||||
@@ -31,6 +31,18 @@ export class UserSiteMonitorSetting extends BaseSettings {
|
||||
certValidDays?:number = 14;
|
||||
}
|
||||
|
||||
|
||||
export class UserDomainMonitorSetting extends BaseSettings {
|
||||
static __title__ = "域名到期监控设置";
|
||||
static __key__ = "user.domain.monitor";
|
||||
|
||||
enabled?:boolean = false;
|
||||
notificationId?:number= 0;
|
||||
cron?:string = undefined;
|
||||
willExpireDays?:number = 30;
|
||||
}
|
||||
|
||||
|
||||
export class UserEmailSetting extends BaseSettings {
|
||||
static __title__ = "用户邮箱设置";
|
||||
static __key__ = "user.email";
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
|
||||
|
||||
@Entity('cd_job_history')
|
||||
export class JobHistoryEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
projectId: number;
|
||||
|
||||
|
||||
@Column({ name: 'type', comment: '类型' })
|
||||
type: string;
|
||||
|
||||
@Column({ name: 'title', comment: '标题' })
|
||||
title: string;
|
||||
|
||||
@Column({ name: 'content', comment: '内容' })
|
||||
content: string;
|
||||
|
||||
@Column({ name: 'related_id', comment: '关联id' })
|
||||
relatedId: string;
|
||||
|
||||
@Column({ name: 'result', comment: '结果' })
|
||||
result: string;
|
||||
|
||||
@Column({ name: 'start_at', comment: '开始时间' })
|
||||
startAt: number;
|
||||
|
||||
@Column({ name: 'end_at', comment: '结束时间' })
|
||||
endAt: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
comment: '创建时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
comment: '修改时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
|
||||
pipeline?: PipelineEntity;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { BaseService } from "@certd/lib-server";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { JobHistoryEntity } from "../entity/job-history.js";
|
||||
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class JobHistoryService extends BaseService<JobHistoryEntity> {
|
||||
@InjectEntityModel(JobHistoryEntity)
|
||||
repository: Repository<JobHistoryEntity>;
|
||||
|
||||
|
||||
@Inject()
|
||||
userSettingsService: UserSettingsService;
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import {SiteIpEntity} from "../entity/site-ip.js";
|
||||
import {Cron} from "../../cron/cron.js";
|
||||
import { dnsContainer } from "./dns-custom.js";
|
||||
import { merge } from "lodash-es";
|
||||
import { JobHistoryService } from "./job-history-service.js";
|
||||
import { JobHistoryEntity } from "../entity/job-history.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
@@ -39,6 +41,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
@Inject()
|
||||
siteIpService: SiteIpService;
|
||||
|
||||
@Inject()
|
||||
jobHistoryService: JobHistoryService;
|
||||
|
||||
|
||||
@Inject()
|
||||
cron: Cron;
|
||||
@@ -352,10 +357,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
if (userId==null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
const sites = await this.repository.find({
|
||||
where: {userId,projectId}
|
||||
});
|
||||
this.checkList(sites,false);
|
||||
// const sites = await this.repository.find({
|
||||
// where: {userId,projectId}
|
||||
// });
|
||||
// this.checkList(sites,false);
|
||||
await this.triggerJobOnce(userId,projectId);
|
||||
}
|
||||
|
||||
async checkList(sites: SiteInfoEntity[],isCommon: boolean) {
|
||||
@@ -516,6 +522,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
async triggerJobOnce(userId?:number,projectId?:number) {
|
||||
logger.info(`站点证书检查开始执行[${userId??'所有用户'}_${projectId??'所有项目'}]`);
|
||||
const query:any = { disabled: false };
|
||||
let jobEntity :Partial<JobHistoryEntity> = null;
|
||||
if(userId!=null){
|
||||
query.userId = userId;
|
||||
if(projectId){
|
||||
@@ -523,12 +530,22 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
//判断是否已关闭
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting);
|
||||
if (!setting.cron) {
|
||||
if (setting && !setting.cron) {
|
||||
return;
|
||||
}
|
||||
jobEntity = {
|
||||
userId,
|
||||
projectId,
|
||||
type:"siteCertMonitor",
|
||||
title: '站点证书检查',
|
||||
result:"start",
|
||||
startAt:new Date().getTime(),
|
||||
}
|
||||
await this.jobHistoryService.add(jobEntity);
|
||||
}
|
||||
let offset = 0;
|
||||
const limit = 50;
|
||||
let count = 0;
|
||||
while (true) {
|
||||
const res = await this.page({
|
||||
query: query,
|
||||
@@ -541,10 +558,20 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
offset += records.length;
|
||||
const isCommon = !userId;
|
||||
count += records.length;
|
||||
await this.checkList(records,isCommon);
|
||||
}
|
||||
|
||||
logger.info(`站点证书检查完成[${userId??'所有用户'}_${projectId??'所有项目'}]`);
|
||||
if(jobEntity){
|
||||
await this.jobHistoryService.update({
|
||||
id: jobEntity.id,
|
||||
result: "done",
|
||||
content:`共检查${count}个站点`,
|
||||
endAt:new Date().getTime(),
|
||||
updateTime:new Date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async batchDelete(ids: number[], userId: number,projectId?:number): Promise<void> {
|
||||
|
||||
@@ -23,4 +23,8 @@ export class NotificationGetter implements INotificationService {
|
||||
async send(req: NotificationSendReq): Promise<void> {
|
||||
return await this.notificationService.send(req, this.userId, this.projectId);
|
||||
}
|
||||
|
||||
async getBindUrl(url: string) {
|
||||
return await this.notificationService.getBindUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
const define = this.getDefineByType(type)
|
||||
//@ts-ignore
|
||||
if (define.needPlus && !isPlus()) {
|
||||
throw new NeedVIPException("此通知类型为专业版功能,请升级到专业版或以上级别");
|
||||
throw new NeedVIPException("此通知类型为Certd专业版功能,请升级到专业版或以上级别");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -941,7 +941,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
async batchDelete(ids: number[], userId?: number, projectId?: number) {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级专业版");
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
for (const id of ids) {
|
||||
if (userId && userId > 0) {
|
||||
@@ -956,7 +956,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
async batchUpdateGroup(ids: number[], groupId: number, userId: any, projectId?: number) {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级专业版");
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
const query: any = {}
|
||||
if (userId && userId > 0) {
|
||||
@@ -982,7 +982,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
*/
|
||||
async batchTransfer(ids: number[], projectId: number) {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级专业版");
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
if (!isEnterprise()) {
|
||||
throw new Error("当前为非企业模式,不允许转移到其他项目");
|
||||
@@ -1075,7 +1075,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
async batchUpdateTrigger(ids: number[], trigger: any, userId: any, projectId?: number) {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级专业版");
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
//允许管理员修改,userId=null
|
||||
const query: any = {}
|
||||
@@ -1128,7 +1128,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any, projectId?: number) {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级专业版");
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
//允许管理员修改,userId=null
|
||||
const query: any = {}
|
||||
@@ -1167,7 +1167,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
async batchRerun(ids: number[], force: boolean, userId: any, projectId?: number) {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级专业版");
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
//允许userId为空,为空则为管理员触发
|
||||
if (ids.length === 0) {
|
||||
|
||||
@@ -44,4 +44,5 @@
|
||||
// export * from './plugin-lib/index.js'
|
||||
// export * from './plugin-plus/index.js'
|
||||
// export * from './plugin-cert/index.js'
|
||||
// export * from './plugin-zenlayer/index.js'
|
||||
// export * from './plugin-zenlayer/index.js'
|
||||
export * from './plugin-dnsmgr/index.js'
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { optionsUtils } from '@certd/basic';
|
||||
import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
||||
import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
|
||||
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
||||
import { optionsUtils } from '@certd/basic';
|
||||
import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
|
||||
import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunCDN',
|
||||
@@ -10,11 +10,12 @@ import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/al
|
||||
icon: 'svg:icon-aliyun',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '自动部署域名证书至阿里云CDN',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
runStrategy: RunStrategy.AlwaysRun,
|
||||
// default: {
|
||||
// strategy: {
|
||||
// runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
// },
|
||||
// },
|
||||
})
|
||||
export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
@@ -40,10 +41,10 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
name: 'output-selector',
|
||||
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
|
||||
},
|
||||
template:false,
|
||||
template: false,
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo | CasCertId |number;
|
||||
cert!: CertInfo | CasCertId | number;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
@@ -59,32 +60,20 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: 'CDN加速域名',
|
||||
helper: '你在阿里云上配置的CDN加速域名,比如:certd.docmirror.cn',
|
||||
typeName: 'DeployCertToAliyunCDN',
|
||||
action: DeployCertToAliyunCDN.prototype.onGetDomainList.name,
|
||||
watches: ['certDomains', 'accessId'],
|
||||
required: true,
|
||||
})
|
||||
)
|
||||
domainName!: string | string[];
|
||||
|
||||
@TaskInput({
|
||||
title: '证书所在地域',
|
||||
helper: 'cn-hangzhou和ap-southeast-1,默认cn-hangzhou。国际站用户建议使用ap-southeast-1。',
|
||||
value:"cn-hangzhou",
|
||||
value: "cn-hangzhou",
|
||||
component: {
|
||||
name: 'a-select',
|
||||
options:[
|
||||
{value:'cn-hangzhou',label:'中国大陆'},
|
||||
{value:'ap-southeast-1',label:'新加坡'}
|
||||
options: [
|
||||
{ value: 'cn-hangzhou', label: '中国大陆' },
|
||||
{ value: 'ap-southeast-1', label: '新加坡' }
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
certRegion:string
|
||||
certRegion: string
|
||||
|
||||
@TaskInput({
|
||||
title: '证书名称',
|
||||
@@ -93,57 +82,131 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
certName!: string;
|
||||
|
||||
|
||||
@TaskInput({
|
||||
title: '域名匹配模式',
|
||||
helper: '根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
options: [
|
||||
{ label: '手动选择', value: 'manual' },
|
||||
{ label: '根据证书匹配', value: 'auto' },
|
||||
],
|
||||
},
|
||||
value: 'manual',
|
||||
})
|
||||
domainMatchMode!: 'manual' | 'auto';
|
||||
|
||||
async onInstance() {}
|
||||
async execute(): Promise<void> {
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: 'CDN加速域名',
|
||||
helper: '你在阿里云上配置的CDN加速域名,比如:certd.docmirror.cn',
|
||||
typeName: 'DeployCertToAliyunCDN',
|
||||
action: DeployCertToAliyunCDN.prototype.onGetDomainList.name,
|
||||
watches: ['certDomains', 'accessId'],
|
||||
required: true,
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.domainMatchMode === "manual"
|
||||
})
|
||||
}
|
||||
`,
|
||||
pager:true,
|
||||
})
|
||||
)
|
||||
domainName!: string | string[];
|
||||
|
||||
@TaskOutput({
|
||||
title: '已部署过的DCDN加速域名',
|
||||
})
|
||||
deployedList!: string[];
|
||||
|
||||
async onInstance() { }
|
||||
async execute(): Promise<any> {
|
||||
this.logger.info('开始部署证书到阿里云cdn');
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
|
||||
if (this.cert == null) {
|
||||
throw new Error('域名证书参数为空,请检查前置任务')
|
||||
}
|
||||
|
||||
const client = await this.getClient(access);
|
||||
const sslClient = new AliyunSslClient({
|
||||
access,
|
||||
logger: this.logger,
|
||||
endpoint: this.endpoint || 'cas.aliyuncs.com',
|
||||
});
|
||||
|
||||
if(this.cert == null){
|
||||
throw new Error('域名证书参数为空,请检查前置任务')
|
||||
if (this.domainMatchMode === 'auto') {
|
||||
|
||||
const { result, deployedList } = await this.autoMatchedDeploy({
|
||||
targetName: 'DCDN加速域名',
|
||||
uploadCert: async () => {
|
||||
return await sslClient.uploadCertOrGet(this.cert);
|
||||
},
|
||||
deployOne: async (req: { target: CertTargetItem, cert: any }) => {
|
||||
return await this.deployOne(client, req.target.value, req.cert);
|
||||
},
|
||||
getCertDomains:async ()=>{
|
||||
return sslClient.getCertDomains(this.cert);
|
||||
},
|
||||
getDeployTargetList: this.onGetDomainList.bind(this)
|
||||
});
|
||||
this.deployedList = deployedList;
|
||||
return result;
|
||||
|
||||
} else {
|
||||
if (this.isNotChanged()) {
|
||||
this.logger.info('输入参数未变更,跳过');
|
||||
return "skip";
|
||||
}
|
||||
const certId = await this.getOrUploadCasCert(sslClient);
|
||||
|
||||
if (typeof this.domainName === 'string') {
|
||||
this.domainName = [this.domainName];
|
||||
}
|
||||
for (const domain of this.domainName) {
|
||||
await this.deployOne(client, domain, certId );
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info('部署完成');
|
||||
}
|
||||
|
||||
async getOrUploadCasCert(sslClient: AliyunSslClient) {
|
||||
let certId: any = this.cert;
|
||||
|
||||
let certName = this.appendTimeSuffix(this.certName);
|
||||
|
||||
let certName = this.appendTimeSuffix(this.certName);
|
||||
if (typeof this.cert === 'object') {
|
||||
const certInfo = this.cert as CertInfo;
|
||||
const casCert = this.cert as CasCertId;
|
||||
if (casCert.certId) {
|
||||
certId = casCert.certId;
|
||||
} else if (certInfo.crt) {
|
||||
certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt))
|
||||
certName = CertReader.buildCertName(certInfo);
|
||||
const certIdRes = await sslClient.uploadCertificate({
|
||||
name:certName,
|
||||
name: certName,
|
||||
cert: certInfo,
|
||||
});
|
||||
certId = certIdRes.certId as any;
|
||||
}else{
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
} else {
|
||||
throw new Error('证书格式错误' + JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
|
||||
const client = await this.getClient(access);
|
||||
|
||||
if (typeof this.domainName === 'string') {
|
||||
this.domainName = [this.domainName];
|
||||
}
|
||||
for (const domain of this.domainName) {
|
||||
await this.SetCdnDomainSSLCertificate(client, {
|
||||
CertId: certId,
|
||||
DomainName: domain,
|
||||
CertName: certName,
|
||||
CertRegion:this.certRegion || 'cn-hangzhou',
|
||||
});
|
||||
return {
|
||||
certId,
|
||||
certName,
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info('部署完成');
|
||||
async deployOne(client: any, domain: string, cert: any ) {
|
||||
const { certId, certName } = cert;
|
||||
await this.SetCdnDomainSSLCertificate(client, {
|
||||
CertId: certId,
|
||||
DomainName: domain,
|
||||
CertName: certName,
|
||||
CertRegion: this.certRegion || 'cn-hangzhou',
|
||||
});
|
||||
}
|
||||
|
||||
async getClient(access: AliyunAccess) {
|
||||
@@ -157,8 +220,8 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
return client;
|
||||
}
|
||||
|
||||
async SetCdnDomainSSLCertificate(client: any, params: { CertId: number; DomainName: string,CertName:string,CertRegion:string }) {
|
||||
this.logger.info('设置CDN: ',JSON.stringify(params));
|
||||
async SetCdnDomainSSLCertificate(client: any, params: { CertId: number; DomainName: string, CertName: string, CertRegion: string }) {
|
||||
this.logger.info('设置CDN: ', JSON.stringify(params));
|
||||
const requestOption = {
|
||||
method: 'POST',
|
||||
formatParams: false,
|
||||
@@ -168,7 +231,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
'SetCdnDomainSSLCertificate',
|
||||
{
|
||||
SSLProtocol: 'on',
|
||||
CertType:"cas",
|
||||
CertType: "cas",
|
||||
...params,
|
||||
},
|
||||
requestOption
|
||||
@@ -183,7 +246,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
async onGetDomainList(data: any) {
|
||||
async onGetDomainList(data: PageSearch) {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择Access授权');
|
||||
}
|
||||
@@ -191,9 +254,11 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
|
||||
const client = await this.getClient(access);
|
||||
|
||||
const pager = new Pager(data)
|
||||
const params = {
|
||||
// 'DomainName': 'aaa',
|
||||
PageSize: 500,
|
||||
PageSize: pager.pageSize || 100,
|
||||
PageNumber: pager.pageNo || 1,
|
||||
};
|
||||
|
||||
const requestOption = {
|
||||
@@ -205,8 +270,12 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
this.checkRet(res);
|
||||
const pageData = res?.Domains?.PageData;
|
||||
if (!pageData || pageData.length === 0) {
|
||||
throw new Error('找不到CDN域名,您可以手动输入');
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
const total = res?.Domains?.TotalCount || 0;
|
||||
const options = pageData.map((item: any) => {
|
||||
return {
|
||||
value: item.DomainName,
|
||||
@@ -214,7 +283,10 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
domain: item.DomainName,
|
||||
};
|
||||
});
|
||||
return optionsUtils.buildGroupOptions(options, this.certDomains);
|
||||
return {
|
||||
list: optionsUtils.buildGroupOptions(options, this.certDomains),
|
||||
total,
|
||||
};
|
||||
}
|
||||
}
|
||||
new DeployCertToAliyunCDN();
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import dayjs from 'dayjs';
|
||||
import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
||||
import {
|
||||
createCertDomainGetterInputDefine,
|
||||
createRemoteSelectInputDefine
|
||||
} from "@certd/plugin-lib";
|
||||
import dayjs from 'dayjs';
|
||||
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
||||
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { CertApplyPluginNames } from '@certd/plugin-cert';
|
||||
import { optionsUtils } from "@certd/basic";
|
||||
import { AliyunClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
|
||||
import { CertApplyPluginNames, CertInfo } from '@certd/plugin-cert';
|
||||
import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunDCDN',
|
||||
title: '阿里云-部署证书至DCDN',
|
||||
icon: 'svg:icon-aliyun',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '依赖证书申请前置任务,自动部署域名证书至阿里云DCDN',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
runStrategy: RunStrategy.AlwaysRun,
|
||||
// default: {
|
||||
// strategy: {
|
||||
// runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
// },
|
||||
// },
|
||||
})
|
||||
export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
@@ -55,6 +55,19 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
})
|
||||
certName!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: '域名匹配模式',
|
||||
helper: '根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
options: [
|
||||
{ label: '手动选择', value: 'manual' },
|
||||
{ label: '根据证书匹配', value: 'auto' },
|
||||
],
|
||||
},
|
||||
value: 'manual',
|
||||
})
|
||||
domainMatchMode!: 'manual' | 'auto';
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
@@ -63,33 +76,78 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
action: DeployCertToAliyunDCDN.prototype.onGetDomainList.name,
|
||||
watches: ['certDomains', 'accessId'],
|
||||
required: true,
|
||||
pager:true,
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.domainMatchMode === "manual"
|
||||
})
|
||||
}
|
||||
`,
|
||||
})
|
||||
)
|
||||
domainName!: string | string[];
|
||||
|
||||
@TaskOutput({
|
||||
title: '已部署过的DCDN加速域名',
|
||||
})
|
||||
deployedList!: string[];
|
||||
|
||||
|
||||
async onInstance() { }
|
||||
async execute(): Promise<void> {
|
||||
async execute(): Promise<any> {
|
||||
this.logger.info('开始部署证书到阿里云DCDN');
|
||||
if (!this.domainName) {
|
||||
throw new Error('您还未选择DCDN域名');
|
||||
}
|
||||
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
|
||||
const client = await this.getClient(access);
|
||||
if (typeof this.domainName === 'string') {
|
||||
this.domainName = [this.domainName];
|
||||
}
|
||||
for (const domainName of this.domainName) {
|
||||
this.logger.info(`[${domainName}]开始部署`)
|
||||
const params = await this.buildParams(domainName);
|
||||
await this.doRequest(client, params);
|
||||
await this.ctx.utils.sleep(1000);
|
||||
this.logger.info(`[${domainName}]部署成功`)
|
||||
const sslClient = new AliyunSslClient({ access, logger: this.logger });
|
||||
|
||||
|
||||
if (this.domainMatchMode === 'auto') {
|
||||
const { result, deployedList } = await this.autoMatchedDeploy({
|
||||
targetName: 'CDN加速域名',
|
||||
uploadCert: async () => {
|
||||
return await sslClient.uploadCertOrGet(this.cert);
|
||||
},
|
||||
deployOne: async (req: { target: CertTargetItem, cert: any }) => {
|
||||
return await this.deployOne(client, req.target.value, req.cert);
|
||||
},
|
||||
getCertDomains: async ()=>{
|
||||
return sslClient.getCertDomains(this.cert);
|
||||
},
|
||||
getDeployTargetList: this.onGetDomainList.bind(this)
|
||||
});
|
||||
this.deployedList = deployedList;
|
||||
return result;
|
||||
|
||||
} else {
|
||||
if (this.isNotChanged()) {
|
||||
this.logger.info('输入参数未变更,跳过');
|
||||
return "skip";
|
||||
}
|
||||
|
||||
if (!this.domainName) {
|
||||
throw new Error('您还未选择DCDN域名');
|
||||
}
|
||||
let domains: string[] = [];
|
||||
domains = typeof this.domainName === 'string' ? [this.domainName] : this.domainName;
|
||||
const aliCrtId = await sslClient.uploadCertOrGet(this.cert);
|
||||
for (const domainName of domains) {
|
||||
await this.deployOne(client, domainName, aliCrtId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.logger.info('部署完成');
|
||||
}
|
||||
|
||||
async deployOne(client: any, domainName: string, aliCrtId: CasCertId) {
|
||||
this.logger.info(`[${domainName}]开始部署`)
|
||||
const params = await this.buildParams(domainName, aliCrtId);
|
||||
await this.doRequest(client, params);
|
||||
await this.ctx.utils.sleep(1000);
|
||||
this.logger.info(`[${domainName}]部署成功`)
|
||||
}
|
||||
|
||||
async getClient(access: AliyunAccess) {
|
||||
const client = new AliyunClient({ logger: this.logger });
|
||||
await client.init({
|
||||
@@ -101,30 +159,9 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
return client;
|
||||
}
|
||||
|
||||
async buildParams(domainName: string) {
|
||||
async buildParams(domainName: string, aliCrtId: CasCertId) {
|
||||
const CertName = (this.certName ?? 'certd') + '-' + dayjs().format('YYYYMMDDHHmmss');
|
||||
|
||||
let certId: any = this.cert
|
||||
if (typeof this.cert === 'object') {
|
||||
const certInfo = this.cert as CertInfo;
|
||||
const casCertId = this.cert as CasCertId;
|
||||
if (certInfo.crt) {
|
||||
this.logger.info('上传证书:', CertName);
|
||||
const cert: any = this.cert;
|
||||
return {
|
||||
DomainName: domainName,
|
||||
SSLProtocol: 'on',
|
||||
CertName: CertName,
|
||||
CertType: 'upload',
|
||||
SSLPub: cert.crt,
|
||||
SSLPri: cert.key,
|
||||
};
|
||||
}else if (casCertId.certId){
|
||||
certId = casCertId.certId;
|
||||
}else{
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
const certId = aliCrtId.certId;
|
||||
this.logger.info('使用已上传的证书:', certId);
|
||||
return {
|
||||
DomainName: domainName,
|
||||
@@ -152,7 +189,7 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
}
|
||||
|
||||
|
||||
async onGetDomainList(data: any) {
|
||||
async onGetDomainList(data: PageSearch): Promise<{ list: CertTargetItem[], total: number }> {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择Access授权');
|
||||
}
|
||||
@@ -161,7 +198,7 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
const client = await this.getClient(access);
|
||||
|
||||
const params = {
|
||||
// 'DomainName': 'aaa',
|
||||
PageNumber: data.pageNo || 1,
|
||||
PageSize: 500,
|
||||
};
|
||||
|
||||
@@ -172,10 +209,9 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
|
||||
const res = await client.request('DescribeDcdnUserDomains', params, requestOption);
|
||||
this.checkRet(res);
|
||||
const pageData = res?.Domains?.PageData;
|
||||
if (!pageData || pageData.length === 0) {
|
||||
throw new Error('找不到CDN域名,您可以手动输入');
|
||||
}
|
||||
const pageData = res?.Domains?.PageData || [];
|
||||
const total = res?.Domains?.TotalCount || 0;
|
||||
|
||||
const options = pageData.map((item: any) => {
|
||||
return {
|
||||
value: item.DomainName,
|
||||
@@ -183,7 +219,11 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
domain: item.DomainName,
|
||||
};
|
||||
});
|
||||
return optionsUtils.buildGroupOptions(options, this.certDomains);
|
||||
|
||||
return {
|
||||
list: optionsUtils.buildGroupOptions(options, this.certDomains),
|
||||
total: total,
|
||||
};
|
||||
}
|
||||
}
|
||||
new DeployCertToAliyunDCDN();
|
||||
|
||||
@@ -99,11 +99,13 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
endpoint,
|
||||
});
|
||||
let certName = ""
|
||||
const certReader = new CertReader(this.cert);
|
||||
if (this.name){
|
||||
certName = this.appendTimeSuffix(this.name)
|
||||
}else {
|
||||
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
|
||||
certName = this.buildCertName(certReader.getMainDomain())
|
||||
}
|
||||
|
||||
const certIdRes = await client.uploadCertificate({
|
||||
name: certName,
|
||||
cert: this.cert,
|
||||
|
||||
@@ -256,7 +256,7 @@ export class AcmeService {
|
||||
}
|
||||
if (providers.domainsVerifyPlan) {
|
||||
//按照计划执行
|
||||
const domainVerifyPlan = providers.domainsVerifyPlan[origFullDomain];
|
||||
const domainVerifyPlan = providers.domainsVerifyPlan[origFullDomain] || providers.domainsVerifyPlan[fullDomain];
|
||||
if (domainVerifyPlan) {
|
||||
if (domainVerifyPlan.type === "dns") {
|
||||
checkIpChallenge("dns");
|
||||
|
||||
@@ -156,7 +156,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
||||
if(maxDays < 2){
|
||||
maxDays = 2;
|
||||
}
|
||||
this.logger.warn(`为避免每次运行都更新证书,更新天数自动减半,调整为${maxDays}`);
|
||||
this.logger.warn(`为避免每次运行都更新证书,更新天数自动减半(即证书最大时长${totalDays}天减半),调整为${maxDays}`);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
144
packages/ui/certd-server/src/plugins/plugin-dnsmgr/access.ts
Normal file
144
packages/ui/certd-server/src/plugins/plugin-dnsmgr/access.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||
import { DomainRecord } from '@certd/plugin-lib';
|
||||
|
||||
@IsAccess({
|
||||
name: 'dnsmgr',
|
||||
title: '彩虹DNS',
|
||||
icon: 'clarity:plugin-line',
|
||||
desc: '彩虹DNS管理系统授权',
|
||||
})
|
||||
export class DnsmgrAccess extends BaseAccess {
|
||||
@AccessInput({
|
||||
title: '系统地址',
|
||||
component: {
|
||||
name: "a-input",
|
||||
allowClear: true,
|
||||
placeholder: 'https://dnsmgr.example.com',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
endpoint = '';
|
||||
|
||||
@AccessInput({
|
||||
title: '用户ID',
|
||||
component: {
|
||||
name: "a-input",
|
||||
allowClear: true,
|
||||
placeholder: '123456',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
uid = '';
|
||||
|
||||
@AccessInput({
|
||||
title: 'API密钥',
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
key = '';
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.GetDomainList({});
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
|
||||
const pager = new Pager(req);
|
||||
const resp = await this.doRequest({
|
||||
url: '/api/domain',
|
||||
data: {
|
||||
offset: pager.getOffset(),
|
||||
limit: pager.pageSize,
|
||||
kw: req.searchKey,
|
||||
},
|
||||
});
|
||||
const total = resp?.total || 0;
|
||||
let list = resp?.rows?.map((item: any) => {
|
||||
return {
|
||||
domain: item.name,
|
||||
...item,
|
||||
};
|
||||
});
|
||||
return {
|
||||
total,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
async createDnsRecord(domainId: string, record: string, value: string, type: string, domain: string) {
|
||||
this.ctx.logger.info(`创建DNS记录:${record} ${type} ${value}`);
|
||||
const resp = await this.doRequest({
|
||||
url: `/api/record/add/${domainId}`,
|
||||
data: {
|
||||
name: record.replace(`.${domain}`, ''),
|
||||
type,
|
||||
value,
|
||||
line: 'default',
|
||||
ttl: 600,
|
||||
},
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
|
||||
async getDnsRecords(domainId: string, type?: string, name?: string, value?: string) {
|
||||
this.ctx.logger.info(`获取DNS记录列表:domainId=${domainId}, type=${type}, name=${name}`);
|
||||
const resp = await this.doRequest({
|
||||
url: `/api/record/data/${domainId}`,
|
||||
data: {
|
||||
type,
|
||||
subdomain: name,
|
||||
value,
|
||||
},
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
|
||||
async deleteDnsRecord(domainId: string, recordId: string) {
|
||||
this.ctx.logger.info(`删除DNS记录:domainId=${domainId}, recordId=${recordId}`);
|
||||
const resp = await this.doRequest({
|
||||
url: `/api/record/delete/${domainId}`,
|
||||
data: {
|
||||
recordid: recordId,
|
||||
},
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
|
||||
async doRequest(req: { url: string; data?: any }) {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const sign = this.ctx.utils.hash.md5(`${this.uid}${timestamp}${this.key}`);
|
||||
const url = `${this.endpoint}${req.url}`;
|
||||
|
||||
const res = await this.ctx.http.request({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
data: {
|
||||
uid: this.uid,
|
||||
timestamp,
|
||||
sign,
|
||||
...req.data,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.code !== undefined && res.code !== 0) {
|
||||
throw new Error(res.msg || '请求失败');
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user